From f6da7fcee17cbc97451ced65a8895ec0646901b2 Mon Sep 17 00:00:00 2001 From: mohammed mahmoud Date: Tue, 4 Sep 2018 11:27:33 +0200 Subject: [PATCH 01/10] Add Initial Structure With MVP --- .../mindorks/waprofileimage/WAProfileImage.kt | 24 ++++++++++++++++++- .../waprofileimage/WAProfileImagePresenter.kt | 19 +++++++++++++++ .../WAProfileImagePresnterInterface.kt | 8 +++++++ .../waprofileimage/WAProfileImageView.kt | 10 ++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresenter.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresnterInterface.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImageView.kt diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt index 490bc50..a919141 100644 --- a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt @@ -1,3 +1,25 @@ package com.mindorks.waprofileimage -class WAProfileImage \ No newline at end of file +import android.os.Bundle +import android.support.v4.app.FragmentActivity +import android.support.v7.app.AppCompatActivity +import android.view.MotionEvent +import android.view.View + +class WAProfileImage : AppCompatActivity(), WAProfileImageView { + lateinit var presenter: WAProfileImagePresenter + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + presenter = WAProfileImagePresenter(this, this) + } + + override fun onTouch(p0: View?, p1: MotionEvent?): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun launch(context: FragmentActivity, requestCode: Int) { + presenter.launchOptionDialog(context, requestCode) + + } + +} \ No newline at end of file diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresenter.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresenter.kt new file mode 100644 index 0000000..3150c9a --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresenter.kt @@ -0,0 +1,19 @@ +package com.mindorks.waprofileimage + +import android.content.Context +import android.support.v4.app.FragmentActivity + +class WAProfileImagePresenter constructor(wAProfileImageView :WAProfileImageView ,ctx:Context):WAProfileImagePresnterInterface { + override fun launchOptionDialog(context: FragmentActivity, requestCode: Int) { + + + } + + var view : WAProfileImageView? = null + var context:Context + init { + view=wAProfileImageView + context=ctx + + } +} \ No newline at end of file diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresnterInterface.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresnterInterface.kt new file mode 100644 index 0000000..7ba38a2 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresnterInterface.kt @@ -0,0 +1,8 @@ +package com.mindorks.waprofileimage + +import android.support.v4.app.FragmentActivity + +interface WAProfileImagePresnterInterface { + fun launchOptionDialog(context: FragmentActivity, requestCode: Int) + +} \ No newline at end of file diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImageView.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImageView.kt new file mode 100644 index 0000000..3cd5b35 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImageView.kt @@ -0,0 +1,10 @@ +package com.mindorks.waprofileimage + +import android.support.v4.app.FragmentActivity +import android.view.MotionEvent +import android.view.View + +interface WAProfileImageView :View.OnTouchListener{ + fun launch(context: FragmentActivity, requestCode: Int) + override fun onTouch(p0: View?, p1: MotionEvent?): Boolean +} \ No newline at end of file From ceaf79286992e97c0de6ebcf53b3e9d834ec3236 Mon Sep 17 00:00:00 2001 From: mohammed mahmoud Date: Tue, 4 Sep 2018 17:08:33 +0200 Subject: [PATCH 02/10] impllment custom dialog like whatsapp --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 3 + .../com/sample/waprofileimage/MainActivity.kt | 4 +- .../src/main/AndroidManifest.xml | 8 +- .../waprofileimage/WAProfileImagePresenter.kt | 19 - .../waprofileimage/callback/TaskFinished.kt | 6 + .../customdialog/DialogBuilder.kt | 415 ++++++++++++++++++ .../customdialog/DialogCustomization.kt | 381 ++++++++++++++++ .../waprofileimage/customdialog/Holder.kt | 51 +++ .../customdialog/HolderAdapter.kt | 10 + .../waprofileimage/customdialog/ListHolder.kt | 102 +++++ .../customdialog/OnHolderListener.kt | 9 + .../customdialog/SimpleAdapter.kt | 66 +++ .../customdialog/ViewHolder.java | 112 +++++ .../customdialog/animation/HeightAnimation.kt | 22 + .../callback/ExpandTouchListener.kt | 181 ++++++++ .../callback/OnBackPressListener.kt | 15 + .../customdialog/callback/OnCancelListener.kt | 11 + .../customdialog/callback/OnClickListener.kt | 16 + .../callback/OnDismissListener.kt | 13 + .../callback/OnItemClickListener.kt | 11 + .../callback/SimpleAnimationListener.kt | 17 + .../customdialog/dialogutil/Utils.kt | 67 +++ .../mindorks/waprofileimage/util/PermUtil.kt | 41 ++ .../{ => waprofileImage}/WAProfileImage.kt | 15 +- .../waprofileImage/WAProfileImagePresenter.kt | 102 +++++ .../WAProfileImagePresnterInterface.kt | 3 +- .../WAProfileImageView.kt | 2 +- .../src/main/res/anim/slide_in_bottom.xml | 13 + .../src/main/res/anim/slide_out_bottom.xml | 13 + .../main/res/drawable/ic_google_maps_icon.png | Bin 0 -> 5823 bytes .../res/drawable/ic_google_messenger_icon.png | Bin 0 -> 2414 bytes .../main/res/drawable/ic_google_plus_icon.png | Bin 0 -> 3082 bytes .../src/main/res/drawable/ic_launcher.png | Bin 0 -> 9397 bytes .../src/main/res/layout/base_container.xml | 18 + .../src/main/res/layout/dialog_list.xml | 26 ++ .../src/main/res/layout/dialog_view.xml | 26 ++ .../src/main/res/layout/footer.xml | 31 ++ .../src/main/res/layout/header.xml | 26 ++ .../src/main/res/layout/simple_list_item.xml | 27 ++ .../src/main/res/layout/wa_profile_image.xml | 6 + .../src/main/res/values/colors.xml | 5 + .../src/main/res/values/dimen.xml | 10 + .../src/main/res/values/integers.xml | 4 + .../src/main/res/values/strings.xml | 6 + 45 files changed, 1883 insertions(+), 32 deletions(-) delete mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresenter.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/callback/TaskFinished.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogBuilder.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogCustomization.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/Holder.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/HolderAdapter.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ListHolder.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/OnHolderListener.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/SimpleAdapter.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ViewHolder.java create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/animation/HeightAnimation.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/ExpandTouchListener.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnBackPressListener.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnCancelListener.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnClickListener.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnDismissListener.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnItemClickListener.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/SimpleAnimationListener.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/dialogutil/Utils.kt create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/util/PermUtil.kt rename whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/{ => waprofileImage}/WAProfileImage.kt (60%) create mode 100644 whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImagePresenter.kt rename whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/{ => waprofileImage}/WAProfileImagePresnterInterface.kt (76%) rename whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/{ => waprofileImage}/WAProfileImageView.kt (84%) create mode 100644 whatsappprofileimage/src/main/res/anim/slide_in_bottom.xml create mode 100644 whatsappprofileimage/src/main/res/anim/slide_out_bottom.xml create mode 100644 whatsappprofileimage/src/main/res/drawable/ic_google_maps_icon.png create mode 100644 whatsappprofileimage/src/main/res/drawable/ic_google_messenger_icon.png create mode 100644 whatsappprofileimage/src/main/res/drawable/ic_google_plus_icon.png create mode 100644 whatsappprofileimage/src/main/res/drawable/ic_launcher.png create mode 100644 whatsappprofileimage/src/main/res/layout/base_container.xml create mode 100644 whatsappprofileimage/src/main/res/layout/dialog_list.xml create mode 100644 whatsappprofileimage/src/main/res/layout/dialog_view.xml create mode 100644 whatsappprofileimage/src/main/res/layout/footer.xml create mode 100644 whatsappprofileimage/src/main/res/layout/header.xml create mode 100644 whatsappprofileimage/src/main/res/layout/simple_list_item.xml create mode 100644 whatsappprofileimage/src/main/res/layout/wa_profile_image.xml create mode 100644 whatsappprofileimage/src/main/res/values/colors.xml create mode 100644 whatsappprofileimage/src/main/res/values/dimen.xml create mode 100644 whatsappprofileimage/src/main/res/values/integers.xml diff --git a/app/build.gradle b/app/build.gradle index f031253..094e8fa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,4 +30,6 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation project(':whatsappprofileimage') + } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 692c635..b4ac0a8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,9 @@ + + + + package="com.mindorks.waprofileimage"> + + + + + + diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresenter.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresenter.kt deleted file mode 100644 index 3150c9a..0000000 --- a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresenter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.mindorks.waprofileimage - -import android.content.Context -import android.support.v4.app.FragmentActivity - -class WAProfileImagePresenter constructor(wAProfileImageView :WAProfileImageView ,ctx:Context):WAProfileImagePresnterInterface { - override fun launchOptionDialog(context: FragmentActivity, requestCode: Int) { - - - } - - var view : WAProfileImageView? = null - var context:Context - init { - view=wAProfileImageView - context=ctx - - } -} \ No newline at end of file diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/callback/TaskFinished.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/callback/TaskFinished.kt new file mode 100644 index 0000000..e962897 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/callback/TaskFinished.kt @@ -0,0 +1,6 @@ +package com.mindorks.waprofileimage.callback + +interface TaskFinished { + fun onTaskFinished(check: Boolean?) + +} \ No newline at end of file diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogBuilder.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogBuilder.kt new file mode 100644 index 0000000..d3f3686 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogBuilder.kt @@ -0,0 +1,415 @@ +package com.mindorks.waprofileimage.customdialog + +import android.app.Activity +import android.content.Context +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.animation.Animation +import android.view.animation.AnimationUtils +import android.widget.BaseAdapter +import android.widget.FrameLayout + +import com.mindorks.waprofileimage.R +import com.mindorks.waprofileimage.customdialog.callback.OnBackPressListener +import com.mindorks.waprofileimage.customdialog.callback.OnCancelListener +import com.mindorks.waprofileimage.customdialog.callback.OnClickListener +import com.mindorks.waprofileimage.customdialog.callback.OnDismissListener +import com.mindorks.waprofileimage.customdialog.callback.OnItemClickListener +import com.mindorks.waprofileimage.customdialog.dialogutil.Utils + +import java.util.Arrays + +class DialogBuilder { + + private val margin = IntArray(4) + val contentPadding = IntArray(4) + private val outMostMargin = IntArray(4) + private val params = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM + ) + + private var adapter: BaseAdapter? = null + var context: Context? = null + private var footerView: View? = null + private var headerView: View? = null + private var holder: Holder? = null + private var gravity = Gravity.BOTTOM + private var onItemClickListener: OnItemClickListener? = null + private var onClickListener: OnClickListener? = null + private var onDismissListener: OnDismissListener? = null + private var onCancelListener: OnCancelListener? = null + private var onBackPressListener: OnBackPressListener? = null + + private var isCancelable = true + private var contentBackgroundResource = android.R.color.white + private var headerViewResourceId = INVALID + var isFixedHeader = false + private set + private var footerViewResourceId = INVALID + var isFixedFooter = false + private set + private var inAnimation = INVALID + private var outAnimation = INVALID + private var expanded: Boolean = false + private var defaultContentHeight: Int = 0 + private var overlayBackgroundResource = R.color.dialogcustomization_black_overlay + + val contentParams: FrameLayout.LayoutParams + get() { + if (expanded) { + params.height = getDefaultContentHeight() + } + return params + } + + val outmostLayoutParams: FrameLayout.LayoutParams + get() { + val params = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT + ) + params.setMargins(outMostMargin[0], outMostMargin[1], outMostMargin[2], outMostMargin[3]) + return params + } + + val contentMargin: IntArray + get() { + val minimumMargin = context!!.resources.getDimensionPixelSize( + R.dimen.dialogcustomization_default_center_margin) + for (i in margin.indices) { + margin[i] = getMargin(this.gravity, margin[i], minimumMargin) + } + return margin + } + + private constructor() {} + + /** + * Initialize the builder with a valid context in order to inflate the dialog + */ + internal constructor(context: Context) { + this.context = context + Arrays.fill(margin, INVALID) + } + + /** + * Set the adapter that will be used when ListHolder or GridHolder are passed + */ + fun setAdapter(adapter: BaseAdapter): DialogBuilder { + this.adapter = adapter + return this + } + + /** + * Set the footer view using the id of the layout resource + * + * @param fixed is used to determine whether footer should be fixed or not. Fixed if true, scrollable otherwise + */ + @JvmOverloads + fun setFooter(resourceId: Int, fixed: Boolean = false): DialogBuilder { + this.footerViewResourceId = resourceId + this.isFixedFooter = fixed + return this + } + + /** + * Sets the given view as footer. + * + * @param fixed is used to determine whether footer should be fixed or not. Fixed if true, scrollable otherwise + */ + @JvmOverloads + fun setFooter(view: View, fixed: Boolean = false): DialogBuilder { + this.footerView = view + this.isFixedFooter = fixed + return this + } + + /** + * Set the header view using the id of the layout resource + * + * @param fixed is used to determine whether header should be fixed or not. Fixed if true, scrollable otherwise + */ + @JvmOverloads + fun setHeader(resourceId: Int, fixed: Boolean = false): DialogBuilder { + this.headerViewResourceId = resourceId + this.isFixedHeader = fixed + return this + } + + /** + * Set the header view using a view + * + * @param fixed is used to determine whether header should be fixed or not. Fixed if true, scrollable otherwise + */ + @JvmOverloads + fun setHeader(view: View, fixed: Boolean = false): DialogBuilder { + this.headerView = view + this.isFixedHeader = fixed + return this + } + //DialogBuilder + /** + * Define if the dialog is cancelable and should be closed when back pressed or click outside is pressed + */ + fun setCancelable(isCancelable: Boolean): DialogBuilder { + this.isCancelable = isCancelable + return this + } + + /** + * Set the content of the dialog by passing one of the provided Holders + */ + fun setContentHolder(holder: Holder): DialogBuilder { + this.holder = holder + return this + } + + /** + * Use setBackgroundResource + */ + @Deprecated("") + fun setBackgroundColorResId(resourceId: Int): DialogBuilder { + return setContentBackgroundResource(resourceId) + } + + /** + * Set background color for your dialog. If no resource is passed 'white' will be used + */ + fun setContentBackgroundResource(resourceId: Int): DialogBuilder { + this.contentBackgroundResource = resourceId + return this + } + + fun setOverlayBackgroundResource(resourceId: Int): DialogBuilder { + this.overlayBackgroundResource = resourceId + return this + } + + /** + * Set the gravity you want the dialog to have among the ones that are provided + */ + fun setGravity(gravity: Int): DialogBuilder { + this.gravity = gravity + params.gravity = gravity + return this + } + + /** + * Customize the in animation by passing an animation resource + */ + fun setInAnimation(inAnimResource: Int): DialogBuilder { + this.inAnimation = inAnimResource + return this + } + + /** + * Customize the out animation by passing an animation resource + */ + fun setOutAnimation(outAnimResource: Int): DialogBuilder { + this.outAnimation = outAnimResource + return this + } + + /** + * Add margins to your outmost view which contains everything. As default they are 0 + * are applied + */ + fun setOutMostMargin(left: Int, top: Int, right: Int, bottom: Int): DialogBuilder { + this.outMostMargin[0] = left + this.outMostMargin[1] = top + this.outMostMargin[2] = right + this.outMostMargin[3] = bottom + return this + } + + /** + * Add margins to your dialog. They are set to 0 except when gravity is center. In that case basic margins + * are applied + */ + fun setMargin(left: Int, top: Int, right: Int, bottom: Int): DialogBuilder { + this.margin[0] = left + this.margin[1] = top + this.margin[2] = right + this.margin[3] = bottom + return this + } + + /** + * Set paddings for the dialog content + */ + fun setPadding(left: Int, top: Int, right: Int, bottom: Int): DialogBuilder { + this.contentPadding[0] = left + this.contentPadding[1] = top + this.contentPadding[2] = right + this.contentPadding[3] = bottom + return this + } + + /** + * Set an item click listener when list or grid holder is chosen. In that way you can have callbacks when one + * of your items is clicked + */ + fun setOnItemClickListener(listener: OnItemClickListener): DialogBuilder { + this.onItemClickListener = listener + return this + } + + /** + * Set a global click listener to you dialog in order to handle all the possible click events. You can then + * identify the view by using its id and handle the correct behaviour + */ + fun setOnClickListener(listener: OnClickListener): DialogBuilder { + this.onClickListener = listener + return this + } + + fun setOnDismissListener(listener: OnDismissListener?): DialogBuilder { + this.onDismissListener = listener + return this + } + + fun setOnCancelListener(listener: OnCancelListener?): DialogBuilder { + this.onCancelListener = listener + return this + } + + fun setOnBackPressListener(listener: OnBackPressListener?): DialogBuilder { + this.onBackPressListener = listener + return this + } + + fun setExpanded(expanded: Boolean): DialogBuilder { + this.expanded = expanded + return this + } + + fun setExpanded(expanded: Boolean, defaultContentHeight: Int): DialogBuilder { + this.expanded = expanded + this.defaultContentHeight = defaultContentHeight + return this + } + + fun setContentHeight(height: Int): DialogBuilder { + params.height = height + return this + } + + fun setContentWidth(width: Int): DialogBuilder { + params.width = width + return this + } + + /** + * Create the dialog using this builder + */ + fun create(): DialogCustomization { + getHolder().setBackgroundResource(getContentBackgroundResource()) + return DialogCustomization(this) + } + + fun getFooterView(): View? { + return Utils.getView(this.context!!, footerViewResourceId, footerView) + } + + fun getHeaderView(): View? { + return Utils.getView(this.context!!, headerViewResourceId, headerView) + } + + fun getHolder(): Holder { + if (holder == null) { + holder = ListHolder() + } + return holder as Holder + } + + fun getAdapter(): BaseAdapter? { + return adapter + } + + fun getInAnimation(): Animation { + val res = if (inAnimation == INVALID) Utils.getAnimationResource(this.gravity, true) else inAnimation + return AnimationUtils.loadAnimation(context, res) + } + + fun getOutAnimation(): Animation { + val res = if (outAnimation == INVALID) Utils.getAnimationResource(this.gravity, false) else outAnimation + return AnimationUtils.loadAnimation(context, res) + } + + fun isExpanded(): Boolean { + return expanded + } + + fun isCancelable(): Boolean { + return isCancelable + } + + fun getOnItemClickListener(): OnItemClickListener? { + return onItemClickListener + } + + fun getOnClickListener(): OnClickListener? { + return onClickListener + } + + fun getOnDismissListener(): OnDismissListener? { + return onDismissListener + } + + fun getOnCancelListener(): OnCancelListener? { + return onCancelListener + } + + fun getOnBackPressListener(): OnBackPressListener? { + return onBackPressListener + } + + fun getDefaultContentHeight(): Int { + val activity = context as Activity + val display = activity.windowManager.defaultDisplay + val displayHeight = display.height - Utils.getStatusBarHeight(activity) + if (defaultContentHeight == 0) { + defaultContentHeight = displayHeight * 2 / 5 + } + return defaultContentHeight + } + + fun getOverlayBackgroundResource(): Int { + return overlayBackgroundResource + } + + fun getContentBackgroundResource(): Int { + return contentBackgroundResource + } + + /** + * Get margins if provided or assign default values based on gravity + * + * @param gravity the gravity of the dialog + * @param margin the value defined in the builder + * @param minimumMargin the minimum margin when gravity center is selected + * @return the value of the margin + */ + private fun getMargin(gravity: Int, margin: Int, minimumMargin: Int): Int { + when (gravity) { + Gravity.CENTER -> return if (margin == INVALID) minimumMargin else margin + else -> return if (margin == INVALID) 0 else margin + } + } + + companion object { + private val INVALID = -1 + } +} +/** + * Set the footer view using the id of the layout resource + */ +/** + * Sets the given view as footer. + */ +/** + * Set the header view using the id of the layout resource + */ +/** + * Set the header view using a view + */ diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogCustomization.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogCustomization.kt new file mode 100644 index 0000000..e2cf772 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogCustomization.kt @@ -0,0 +1,381 @@ +package com.mindorks.waprofileimage.customdialog + +import android.app.Activity +import android.content.Context +import android.view.Display +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.view.animation.Animation +import android.widget.AbsListView +import android.widget.AdapterView +import android.widget.BaseAdapter +import android.widget.FrameLayout + +import com.mindorks.waprofileimage.R +import com.mindorks.waprofileimage.customdialog.callback.* +import com.mindorks.waprofileimage.customdialog.dialogutil.Utils + +class DialogCustomization internal constructor(builder: DialogBuilder) { + + /** + * DialogCustomization base layout root view + */ + private val rootView: ViewGroup + + /** + * DialogCustomization content container which is a different layout rather than base layout + */ + private val contentContainer: ViewGroup + + /** + * Determines whether dialog should be dismissed by back button or touch in the black overlay + */ + private val isCancelable: Boolean + + /** + * Determines whether dialog is showing dismissing animation and avoid to repeat it + */ + private var isDismissing: Boolean = false + + /** + * Listener for the user to take action by clicking any item + */ + private val onItemClickListener: OnItemClickListener? + + /** + * Listener for the user to take action by clicking views in header or footer + */ + private val onClickListener: OnClickListener? + + /** + * Listener to notify the user that dialog has been dismissed + */ + private val onDismissListener: OnDismissListener? + + /** + * Listener to notify the user that dialog has been canceled + */ + lateinit var onCancelListener: OnCancelListener + + /** + * Listener to notify back press + */ + private val onBackPressListener: OnBackPressListener? + + /** + * Content + */ + private val holder: Holder + + /** + * basically activity root view + */ + private val decorView: ViewGroup + + private val outAnim: Animation + private val inAnim: Animation + + /** + * Checks if the dialog is shown + */ + val isShowing: Boolean + get() { + val view = decorView.findViewById(R.id.dialogcustomization_outmost_container) + return view != null + } + + /** + * Returns header view if it was set. + */ + val headerView: View? + get() = holder.header + + /** + * Returns footer view if it was set. + */ + val footerView: View? + get() = holder.footer + + /** + * Returns holder view. + */ + val holderView: View + get() = holder.inflatedView + + /** + * Called when the user touch on black overlay in order to dismiss the dialog + */ + private val onCancelableTouchListener = View.OnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + onCancelListener.onCancel(this@DialogCustomization) + dismiss() + } + false + } + + init { + val layoutInflater = LayoutInflater.from(builder.context) + + val activity = builder.context as Activity? + + holder = builder.getHolder() + + onItemClickListener = builder.getOnItemClickListener() + onClickListener = builder.getOnClickListener() + onDismissListener = builder.getOnDismissListener() + onCancelListener = builder.getOnCancelListener()!! + onBackPressListener = builder.getOnBackPressListener() + isCancelable = builder.isCancelable() + + /* + * Avoid getting directly from the decor view because by doing that we are overlapping the black soft key on + * nexus device. I think it should be tested on different devices but in my opinion is the way to go. + * @link http://stackoverflow.com/questions/4486034/get-root-view-from-current-activity + */ + decorView = activity!!.window.decorView.findViewById(android.R.id.content) + rootView = layoutInflater.inflate(R.layout.base_container, decorView, false) as ViewGroup + rootView.layoutParams = builder.outmostLayoutParams + + val outmostView = rootView.findViewById(R.id.dialogcustomization_outmost_container) + outmostView.setBackgroundResource(builder.getOverlayBackgroundResource()) + + contentContainer = rootView.findViewById(R.id.dialogcustomization_content_container) + contentContainer.layoutParams = builder.contentParams + + outAnim = builder.getOutAnimation() + inAnim = builder.getInAnimation() + + initContentView( + layoutInflater, + builder.getHeaderView(), + builder.isFixedHeader, + builder.getFooterView(), + builder.isFixedFooter, + builder.getAdapter(), + builder.contentPadding, + builder.contentMargin + ) + + initCancelable() + if (builder.isExpanded()) { + initExpandAnimator(activity, builder.getDefaultContentHeight(), builder.contentParams.gravity) + } + } + + /** + * Displays the dialog if it is not shown already. + */ + fun show() { + if (isShowing) { + return + } + onAttached(rootView) + } + + /** + * Dismisses the displayed dialog. + */ + fun dismiss() { + if (isDismissing) { + return + } + + outAnim.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) { + + } + + override fun onAnimationEnd(animation: Animation) { + decorView.post { + decorView.removeView(rootView) + isDismissing = false + onDismissListener?.onDismiss(this@DialogCustomization) + } + } + + override fun onAnimationRepeat(animation: Animation) { + + } + }) + contentContainer.startAnimation(outAnim) + isDismissing = true + } + + /** + * Checks the given resource id and return the corresponding view if it exists. + * + * @return null if it is not found + */ + fun findViewById(resourceId: Int): View? { + return contentContainer.findViewById(resourceId) + } + + private fun initExpandAnimator(activity: Activity, defaultHeight: Int, gravity: Int) { + var defaultHeight = defaultHeight + val display = activity.windowManager.defaultDisplay + val displayHeight = display.height - Utils.getStatusBarHeight(activity) + if (defaultHeight == 0) { + defaultHeight = displayHeight * 2 / 5 + } + + val view = holder.inflatedView as? AbsListView ?: return + + view.setOnTouchListener(ExpandTouchListener.newListener( + activity, view, contentContainer, gravity, displayHeight, defaultHeight + )) + } + + /** + * It is called in order to create content + */ + private fun initContentView(inflater: LayoutInflater, header: View?, fixedHeader: Boolean, footer: View?, + fixedFooter: Boolean, adapter: BaseAdapter?, padding: IntArray, margin: IntArray) { + val contentView = createView(inflater, header, fixedHeader, footer, fixedFooter, adapter) + val params = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT + ) + params.setMargins(margin[0], margin[1], margin[2], margin[3]) + contentView.layoutParams = params + holderView.setPadding(padding[0], padding[1], padding[2], padding[3]) + contentContainer.addView(contentView) + } + + /** + * It is called to set whether the dialog is cancellable by pressing back button or + * touching the black overlay + */ + private fun initCancelable() { + if (!isCancelable) { + return + } + val view = rootView.findViewById(R.id.dialogcustomization_outmost_container) + view.setOnTouchListener(onCancelableTouchListener) + } + + /** + * it is called when the content view is created + * + * @param inflater used to inflate the content of the dialog + * @return any view which is passed + */ + private fun createView(inflater: LayoutInflater, headerView: View?, fixedHeader: Boolean, + footerView: View?, fixedFooter: Boolean, adapter: BaseAdapter?): View { + val view = holder.getView(inflater, rootView) + + if (holder is ViewHolder) { + assignClickListenerRecursively(view) + } + + if (headerView != null) { + assignClickListenerRecursively(headerView) + holder.addHeader(headerView, fixedHeader) + } + + if (footerView != null) { + assignClickListenerRecursively(footerView) + holder.addFooter(footerView, fixedFooter) + } + + if (adapter != null && holder is HolderAdapter) { + val holderAdapter = holder + holderAdapter.setAdapter(adapter) + holderAdapter.setOnItemClickListener(object : OnHolderListener { + override fun onItemClick(item: Any, view: View, position: Int) { + if (onItemClickListener == null) { + return + } + onItemClickListener.onItemClick(this@DialogCustomization, item, view, position) + } + }) + } + return view + } + + /** + * Loop among the views in the hierarchy and assign listener to them + */ + private fun assignClickListenerRecursively(parent: View?) { + if (parent == null) { + return + } + + if (parent is ViewGroup) { + val viewGroup = parent as ViewGroup? + val childCount = viewGroup!!.childCount + for (i in childCount - 1 downTo 0) { + val child = viewGroup.getChildAt(i) + assignClickListenerRecursively(child) + } + } + setClickListener(parent) + } + + /** + * It is used to setListener on view that have a valid id associated + */ + private fun setClickListener(view: View) { + if (view.id == INVALID) { + return + } + //adapterview does not support click listener + if (view is AdapterView<*>) { + return + } + + view.setOnClickListener(View.OnClickListener { v -> + if (onClickListener == null) { + return@OnClickListener + } + onClickListener.onClick(this@DialogCustomization, v) + }) + } + + /** + * It is called when the show() method is called + * + * @param view is the dialog plus view + */ + private fun onAttached(view: View) { + decorView.addView(view) + contentContainer.startAnimation(inAnim) + + contentContainer.requestFocus() + holder.setOnKeyListener(View.OnKeyListener { v, keyCode, event -> + when (event.action) { + KeyEvent.ACTION_UP -> if (keyCode == KeyEvent.KEYCODE_BACK) { + onBackPressListener?.onBackPressed(this@DialogCustomization) + if (isCancelable) { + onBackPressed(this@DialogCustomization) + } + return@OnKeyListener true + } + else -> { + } + } + false + }) + } + + /** + * Invoked when back button is pressed. Automatically dismiss the dialog. + */ + fun onBackPressed(dialogCustomization: DialogCustomization) { + onCancelListener?.onCancel(this@DialogCustomization) + dismiss() + } + + companion object { + + private val INVALID = -1 + + /** + * Creates a new dialog builder + */ + fun newDialog(context: Context): DialogBuilder { + return DialogBuilder(context) + } + } +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/Holder.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/Holder.kt new file mode 100644 index 0000000..6531233 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/Holder.kt @@ -0,0 +1,51 @@ +package com.mindorks.waprofileimage.customdialog + +import android.support.annotation.ColorRes +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup + +interface Holder { + + val inflatedView: View + + val header: View? + + val footer: View? + + /** + * Adds the given view as header to the top of holder + */ + fun addHeader(view: View) + + /** + * Adds the given view as header to the top of holder + * + * @param view will be shown as header. + * @param fixed fixed on top if it is true, scrollable otherwise + */ + fun addHeader(view: View, fixed: Boolean) + + /** + * Adds the given view as footer to the bottom of holder + */ + fun addFooter(view: View) + + /** + * Adds the given view as footer to the bottom of holder + * + * @param view will be shown as footer. + * @param fixed fixed at bottom if it is true, scrollable otherwise + */ + fun addFooter(view: View, fixed: Boolean) + + /** + * Sets the given color resource as background for the content + */ + fun setBackgroundResource(@ColorRes colorResource: Int) + + fun getView(inflater: LayoutInflater, parent: ViewGroup): View + + fun setOnKeyListener(keyListener: View.OnKeyListener) + +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/HolderAdapter.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/HolderAdapter.kt new file mode 100644 index 0000000..bda9523 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/HolderAdapter.kt @@ -0,0 +1,10 @@ +package com.mindorks.waprofileimage.customdialog + +import android.widget.BaseAdapter + +interface HolderAdapter : Holder { + + fun setAdapter(adapter: BaseAdapter) + + fun setOnItemClickListener(listener: OnHolderListener) +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ListHolder.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ListHolder.kt new file mode 100644 index 0000000..97d384e --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ListHolder.kt @@ -0,0 +1,102 @@ +package com.mindorks.waprofileimage.customdialog + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.BaseAdapter +import android.widget.LinearLayout +import android.widget.ListView +import com.mindorks.waprofileimage.R + +class ListHolder : HolderAdapter, AdapterView.OnItemClickListener { + private var backgroundResource: Int = 0 + + private var listView: ListView? = null + private var listener: OnHolderListener? = null + private var keyListener: View.OnKeyListener? = null + private var headerView: View? = null + private var footerView: View? = null + private var footerContainer: ViewGroup? = null + private var headerContainer: ViewGroup? = null + + override fun addHeader(view: View) { + addHeader(view, false) + } + + override fun addHeader(view: View, fixed: Boolean) { + if (fixed) { + headerContainer!!.addView(view) + } else { + listView!!.addHeaderView(view) + } + headerView = view + } + + override fun addFooter(view: View) { + addFooter(view, false) + } + + override fun addFooter(view: View, fixed: Boolean) { + if (fixed) { + footerContainer!!.addView(view) + } else { + listView!!.addFooterView(view) + } + footerView = view + } + + override fun setAdapter(adapter: BaseAdapter) { + listView!!.adapter = adapter + } + + override fun setBackgroundResource(colorResource: Int) { + this.backgroundResource = colorResource + } + + override fun getView(inflater: LayoutInflater, parent: ViewGroup): View { + val view = inflater.inflate(R.layout.dialog_list, parent, false) + val outMostView = view.findViewById(R.id.dialogcustomization_outmost_container) + outMostView.setBackgroundResource(backgroundResource) + listView = view.findViewById(R.id.dialogcustomization_list) + listView!!.onItemClickListener = this + listView!!.setOnKeyListener { v, keyCode, event -> + if (keyListener == null) { + throw NullPointerException("keyListener should not be null") + } + keyListener!!.onKey(v, keyCode, event) + } + headerContainer = view.findViewById(R.id.dialogcustomization_header_container) + footerContainer = view.findViewById(R.id.dialogcustomization_footer_container) + return view + } + + override fun setOnItemClickListener(listener: OnHolderListener) { + this.listener = listener + } + + override fun setOnKeyListener(keyListener: View.OnKeyListener) { + this.keyListener = keyListener + } + + override val inflatedView: View + get() = this.listView!! + + override val header: View? + get() = headerView + override val footer: View? + get() = footerView + + override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) { + var position = position + if (listener == null) { + return + } + //ListView counts header as position as well. For consistency we don't + listener!!.onItemClick( + parent.getItemAtPosition(position), + view, + if (headerView != null) --position else position + ) + } +} \ No newline at end of file diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/OnHolderListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/OnHolderListener.kt new file mode 100644 index 0000000..7012fd5 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/OnHolderListener.kt @@ -0,0 +1,9 @@ +package com.mindorks.waprofileimage.customdialog + +import android.view.View + +interface OnHolderListener { + + fun onItemClick(item: Any, view: View, position: Int) + +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/SimpleAdapter.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/SimpleAdapter.kt new file mode 100644 index 0000000..e667ecf --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/SimpleAdapter.kt @@ -0,0 +1,66 @@ +package com.mindorks.waprofileimage.customdialog + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.ImageView +import android.widget.TextView +import com.mindorks.waprofileimage.R + +class SimpleAdapter( + context: Context, + private val count: Int) : BaseAdapter() { + + private val layoutInflater: LayoutInflater = LayoutInflater.from(context) + + override fun getCount(): Int { + return count + } + + override fun getItem(position: Int): Any { + return position + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val viewHolder: ViewHolder + var view: View? = convertView + + if (view == null) { + view = layoutInflater.inflate(R.layout.simple_list_item, parent, false) + + viewHolder = ViewHolder( + view.findViewById(R.id.text_view), + view.findViewById(R.id.image_view) + ) + view.tag = viewHolder + } else { + viewHolder = view.tag as ViewHolder + } + + val context = parent.context + when (position) { + 0 -> { + viewHolder.textView.text = context.getString(R.string.google_plus_title) + viewHolder.imageView.setImageResource(R.drawable.ic_google_plus_icon) + } + 1 -> { + viewHolder.textView.text = context.getString(R.string.google_maps_title) + viewHolder.imageView.setImageResource(R.drawable.ic_google_maps_icon) + } + else -> { + viewHolder.textView.text = context.getString(R.string.google_messenger_title) + viewHolder.imageView.setImageResource(R.drawable.ic_google_messenger_icon) + } + } + + return view!! + } + + data class ViewHolder(val textView: TextView, val imageView: ImageView) +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ViewHolder.java b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ViewHolder.java new file mode 100644 index 0000000..7336bf0 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ViewHolder.java @@ -0,0 +1,112 @@ +package com.mindorks.waprofileimage.customdialog; + +import android.support.annotation.NonNull; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.mindorks.waprofileimage.R; + +public class ViewHolder implements Holder { + private static final int INVALID = -1; + private int backgroundResource; + private ViewGroup headerContainer; + private View headerView; + private ViewGroup footerContainer; + private View footerView; + private View.OnKeyListener keyListener; + private View contentView; + private int viewResourceId = INVALID; + + public ViewHolder(int viewResourceId) { + this.viewResourceId = viewResourceId; + } + + public ViewHolder(View contentView) { + this.contentView = contentView; + } + + @Override + public void addHeader(@NonNull View view) { + addHeader(view, false); + } + + @Override + public void addHeader(@NonNull View view, boolean fixed) { + headerContainer.addView(view); + headerView = view; + } + + @Override + public void addFooter(@NonNull View view) { + addFooter(view, false); + } + + @Override + public void addFooter(@NonNull View view, boolean fixed) { + footerContainer.addView(view); + footerView = view; + } + + @Override + public void setBackgroundResource(int colorResource) { + this.backgroundResource = colorResource; + } + + @Override + @NonNull + public View getView(@NonNull LayoutInflater inflater, ViewGroup parent) { + View view = inflater.inflate(R.layout.dialog_view, parent, false); + View outMostView = view.findViewById(R.id.dialogcustomization_outmost_container); + outMostView.setBackgroundResource(backgroundResource); + ViewGroup contentContainer = view.findViewById(R.id.dialogcustomization_view_container); + contentContainer.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyListener == null) { + throw new NullPointerException("keyListener should not be null"); + } + return keyListener.onKey(v, keyCode, event); + } + }); + addContent(inflater, parent, contentContainer); + headerContainer = view.findViewById(R.id.dialogcustomization_header_container); + footerContainer = view.findViewById(R.id.dialogcustomization_footer_container); + return view; + } + + private void addContent(LayoutInflater inflater, ViewGroup parent, ViewGroup container) { + if (viewResourceId != INVALID) { + contentView = inflater.inflate(viewResourceId, parent, false); + } else { + ViewGroup parentView = (ViewGroup) contentView.getParent(); + if (parentView != null) { + parentView.removeView(contentView); + } + } + + container.addView(contentView); + } + + @Override + public void setOnKeyListener(View.OnKeyListener keyListener) { + this.keyListener = keyListener; + } + + @Override + @NonNull + public View getInflatedView() { + return contentView; + } + + @Override + public View getHeader() { + return headerView; + } + + @Override + public View getFooter() { + return footerView; + } +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/animation/HeightAnimation.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/animation/HeightAnimation.kt new file mode 100644 index 0000000..d77701e --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/animation/HeightAnimation.kt @@ -0,0 +1,22 @@ +package com.mindorks.waprofileimage.customdialog.animation + +import android.view.View +import android.view.animation.Animation +import android.view.animation.Transformation + +internal class HeightAnimation(private val view: View, private val originalHeight: Int, toHeight: Int) : Animation() { + private val perValue: Float + + init { + this.perValue = (toHeight - originalHeight).toFloat() + } + + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + view.layoutParams.height = (originalHeight + perValue * interpolatedTime).toInt() + view.requestLayout() + } + + override fun willChangeBounds(): Boolean { + return true + } +} \ No newline at end of file diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/ExpandTouchListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/ExpandTouchListener.kt new file mode 100644 index 0000000..ae5d2e6 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/ExpandTouchListener.kt @@ -0,0 +1,181 @@ +package com.mindorks.waprofileimage.customdialog.callback + +import android.content.Context +import android.view.GestureDetector +import android.view.Gravity +import android.view.MotionEvent +import android.view.View +import android.view.animation.Animation +import android.widget.AbsListView +import android.widget.FrameLayout + +import com.mindorks.waprofileimage.customdialog.dialogutil.Utils + +internal class ExpandTouchListener private constructor(context: Context, private val absListView: AbsListView, private val contentContainer: View, private val gravity: Int, + /** + * This is used to determine top. status bar height is removed + */ + private val displayHeight: Int, + /** + * Default height for the holder + */ + private val defaultContentHeight: Int) : View.OnTouchListener { + private val gestureDetector: GestureDetector + + /** + * The last touch position in Y Axis + */ + private var y: Float = 0.toFloat() + + /** + * This is used to determine whether dialog reached to top or not. + */ + private var fullScreen: Boolean = false + + /** + * This is used to determine whether the user swipes from down to top. + * touchUp is calculated by touch events + */ + private var touchUp: Boolean = false + + /** + * This is used to determine whether the user swipes from down to top. + * scrollUp is calculated from gesture detector scroll event. + * This shouldn't be used for the touch events + */ + private var scrollUp: Boolean = false + + /** + * Content container params, not the holder itself. + */ + private val params: FrameLayout.LayoutParams + + init { + this.params = contentContainer.layoutParams as FrameLayout.LayoutParams + + gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapUp(e: MotionEvent): Boolean { + return true + } + + override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean { + scrollUp = distanceY > 0 + return false + } + }) + } + + override fun onTouch(v: View, event: MotionEvent): Boolean { + //If single tapped, don't consume the event + if (gestureDetector.onTouchEvent(event)) { + return false + } + + // when the dialog is fullscreen and user scrolls the content + // don't consume the event + if (!(!scrollUp && Utils.listIsAtTop(absListView)) && fullScreen) { + return false + } + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + y = event.rawY + return true + } + MotionEvent.ACTION_MOVE -> { + // This is a quick fix to not trigger click event + if (params.height == displayHeight) { + params.height-- + contentContainer.layoutParams = params + return false + } + onTouchMove(v, event) + } + MotionEvent.ACTION_UP -> onTouchUp(v, event) + else -> { + } + } + return true + } + + private fun onTouchMove(view: View, event: MotionEvent) { + // sometimes Action_DOWN is not called, we need to make sure that + // we calculate correct y value + if (y == -1f) { + y = event.rawY + } + var delta = y - event.rawY + // if delta > 0 , that means user swipes to top + touchUp = delta > 0 + if (gravity == Gravity.TOP) { + delta = -delta + } + //update the y value, otherwise delta will be incorrect + y = event.rawY + + var newHeight = params.height + delta.toInt() + + // This prevents dialog to move out of screen bounds + if (newHeight > displayHeight) { + newHeight = displayHeight + } + + // This prevents the dialog go below the default value while dragging + if (newHeight < defaultContentHeight) { + newHeight = defaultContentHeight + } + params.height = newHeight + contentContainer.layoutParams = params + + // we use fullscreen value to activate view content scroll + fullScreen = params.height == displayHeight + } + + private fun onTouchUp(view: View, event: MotionEvent) { + // reset y value + y = -1f + + // if the dragging direction is from top to down and dialog position still can't exceeds threshold + // move the dialog automatically to top + if (!touchUp && params.height < displayHeight && params.height > displayHeight * 4 / 5) { + Utils.animateContent(contentContainer, displayHeight, object : SimpleAnimationListener() { + override fun onAnimationEnd(animation: Animation) { + fullScreen = true + } + }) + return + } + + // if the dragging direction is down to top and dialog is dragged more than 50 to up + // move the dialog automatically to top + if (touchUp && params.height > defaultContentHeight + 50) { + Utils.animateContent(contentContainer, displayHeight, object : SimpleAnimationListener() { + override fun onAnimationEnd(animation: Animation) { + fullScreen = true + } + }) + return + } + + // if the dragging direction is from down to top and dialog position still can't exceeds threshold + // move the dialog automatically to down + if (touchUp && params.height <= defaultContentHeight + 50) { + Utils.animateContent(contentContainer, defaultContentHeight, SimpleAnimationListener()) + return + } + + // if the dragging direction is from top to down and the position exceeded the threshold + // move the dialog to down + if (!touchUp && params.height > defaultContentHeight) { + Utils.animateContent(contentContainer, defaultContentHeight, SimpleAnimationListener()) + } + } + + companion object { + + fun newListener(context: Context, listView: AbsListView, container: View, + gravity: Int, displayHeight: Int, defaultContentHeight: Int): ExpandTouchListener { + return ExpandTouchListener(context, listView, container, gravity, displayHeight, defaultContentHeight) + } + } +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnBackPressListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnBackPressListener.kt new file mode 100644 index 0000000..6dce35b --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnBackPressListener.kt @@ -0,0 +1,15 @@ +package com.mindorks.waprofileimage.customdialog.callback + +import com.mindorks.waprofileimage.customdialog.DialogCustomization + +/** + * dialogcustomization tries to listen back press actions. + */ +interface OnBackPressListener { + + /** + * Invoked when dialogcustomization receives any back press button event. + */ + fun onBackPressed(dialog: DialogCustomization) + +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnCancelListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnCancelListener.kt new file mode 100644 index 0000000..eb17fe2 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnCancelListener.kt @@ -0,0 +1,11 @@ +package com.mindorks.waprofileimage.customdialog.callback + +import com.mindorks.waprofileimage.customdialog.DialogCustomization + +/** + * dialogcustomization will use this listener to propagate cancel events when back button is pressed. + */ +interface OnCancelListener { + + fun onCancel(dialog: DialogCustomization) +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnClickListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnClickListener.kt new file mode 100644 index 0000000..f96a838 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnClickListener.kt @@ -0,0 +1,16 @@ +package com.mindorks.waprofileimage.customdialog.callback + +import android.view.View + +import com.mindorks.waprofileimage.customdialog.DialogCustomization + +interface OnClickListener { + + /** + * Invoked when any view within ViewHolder is clicked. + * + * @param view is the clicked view + */ + fun onClick(dialog: DialogCustomization, view: View) + +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnDismissListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnDismissListener.kt new file mode 100644 index 0000000..de834a0 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnDismissListener.kt @@ -0,0 +1,13 @@ +package com.mindorks.waprofileimage.customdialog.callback + +import com.mindorks.waprofileimage.customdialog.DialogCustomization + +/** + * Invoked when dialog is completely dismissed. This listener takes the animation into account and waits for it. + * + * + * It is invoked after animation is completed + */ +interface OnDismissListener { + fun onDismiss(dialog: DialogCustomization) +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnItemClickListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnItemClickListener.kt new file mode 100644 index 0000000..d8aa19f --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnItemClickListener.kt @@ -0,0 +1,11 @@ +package com.mindorks.waprofileimage.customdialog.callback + +import android.view.View + +import com.mindorks.waprofileimage.customdialog.DialogCustomization + +interface OnItemClickListener { + + fun onItemClick(dialog: DialogCustomization, item: Any, view: View, position: Int) + +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/SimpleAnimationListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/SimpleAnimationListener.kt new file mode 100644 index 0000000..9a1e6f8 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/SimpleAnimationListener.kt @@ -0,0 +1,17 @@ +package com.mindorks.waprofileimage.customdialog.callback + +import android.view.animation.Animation + +open class SimpleAnimationListener : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) { + + } + + override fun onAnimationEnd(animation: Animation) { + + } + + override fun onAnimationRepeat(animation: Animation) { + + } +} diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/dialogutil/Utils.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/dialogutil/Utils.kt new file mode 100644 index 0000000..7e6db86 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/dialogutil/Utils.kt @@ -0,0 +1,67 @@ +package com.mindorks.waprofileimage.customdialog.dialogutil + +import android.content.Context +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.animation.Animation +import android.widget.AbsListView + +import com.mindorks.waprofileimage.R +import com.mindorks.waprofileimage.customdialog.animation.HeightAnimation + + object Utils { + + private val INVALID = -1 + + fun getStatusBarHeight(context: Context): Int { + var result = 0 + val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android") + if (resourceId > 0) { + result = context.resources.getDimensionPixelSize(resourceId) + } + return result + } + + fun animateContent(view: View, to: Int, listener: Animation.AnimationListener) { + val animation = HeightAnimation(view, view.height, to) + animation.setAnimationListener(listener) + animation.duration = 200 + view.startAnimation(animation) + } + + fun listIsAtTop(listView: AbsListView): Boolean { + return listView.childCount == 0 || listView.getChildAt(0).top == listView.paddingTop + } + + /** + * This will be called in order to create view, if the given view is not null, + * it will be used directly, otherwise it will check the resourceId + * + * @return null if both resourceId and view is not set + */ + fun getView(context: Context, resourceId: Int, view: View?): View? { + var view = view + val inflater = LayoutInflater.from(context) + if (view != null) { + return view + } + if (resourceId != INVALID) { + view = inflater.inflate(resourceId, null) + } + return view + } + + /** + * Get default animation resource when not defined by the user + * + * @param gravity the gravity of the dialog + * @param isInAnimation determine if is in or out animation. true when is is + * @return the id of the animation resource + */ + fun getAnimationResource(gravity: Int, isInAnimation: Boolean): Int { + return if (gravity and Gravity.BOTTOM == Gravity.BOTTOM) { + if (isInAnimation) R.anim.slide_in_bottom else R.anim.slide_out_bottom + } else INVALID + } +}// no instance diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/util/PermUtil.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/util/PermUtil.kt new file mode 100644 index 0000000..2e79610 --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/util/PermUtil.kt @@ -0,0 +1,41 @@ +package com.mindorks.waprofileimage.util + +import android.Manifest +import android.app.Activity +import android.content.pm.PackageManager +import android.os.Build +import android.support.annotation.RequiresApi +import android.support.v4.app.FragmentActivity +import com.mindorks.waprofileimage.callback.TaskFinished +import java.util.ArrayList + +object PermUtil { + val REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 9921 + + @RequiresApi(api = Build.VERSION_CODES.M) + fun checkForCamara_WritePermissions(activity: FragmentActivity, taskFinished: TaskFinished) { + val permissionsNeeded = ArrayList() + val permissionsList = ArrayList() + if (!addPermission(permissionsList, Manifest.permission.CAMERA, activity)) + permissionsNeeded.add("CAMERA") + if (!addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE, activity)) + permissionsNeeded.add("WRITE_EXTERNAL_STORAGE") + if (permissionsList.size > 0) { + activity.requestPermissions(permissionsList.toTypedArray(), + REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS) + } else { + taskFinished.onTaskFinished(true) + } + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private fun addPermission(permissionsList: MutableList, permission: String, ac: Activity): Boolean { + if (ac.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { + permissionsList.add(permission) + // Check for Rationale Option + return ac.shouldShowRequestPermissionRationale(permission) + } + return true + } + +} \ No newline at end of file diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImage.kt similarity index 60% rename from whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt rename to whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImage.kt index a919141..6033935 100644 --- a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImage.kt @@ -1,23 +1,22 @@ -package com.mindorks.waprofileimage +package com.mindorks.waprofileimage.waprofileImage +import android.annotation.SuppressLint import android.os.Bundle import android.support.v4.app.FragmentActivity import android.support.v7.app.AppCompatActivity import android.view.MotionEvent import android.view.View +import com.mindorks.waprofileimage.R -class WAProfileImage : AppCompatActivity(), WAProfileImageView { - lateinit var presenter: WAProfileImagePresenter - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - presenter = WAProfileImagePresenter(this, this) - } - +object WAProfileImage : WAProfileImageView { + @SuppressLint("StaticFieldLeak") + private lateinit var presenter: WAProfileImagePresenter override fun onTouch(p0: View?, p1: MotionEvent?): Boolean { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } override fun launch(context: FragmentActivity, requestCode: Int) { + presenter = WAProfileImagePresenter(this, context) presenter.launchOptionDialog(context, requestCode) } diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImagePresenter.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImagePresenter.kt new file mode 100644 index 0000000..db6fdab --- /dev/null +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImagePresenter.kt @@ -0,0 +1,102 @@ +package com.mindorks.waprofileimage.waprofileImage + +import android.content.Context +import android.os.Build +import android.support.v4.app.FragmentActivity +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import com.mindorks.waprofileimage.R +import com.mindorks.waprofileimage.callback.TaskFinished +import com.mindorks.waprofileimage.customdialog.DialogCustomization +import com.mindorks.waprofileimage.customdialog.ListHolder +import com.mindorks.waprofileimage.customdialog.SimpleAdapter +import com.mindorks.waprofileimage.customdialog.callback.* +import com.mindorks.waprofileimage.util.PermUtil + +class WAProfileImagePresenter constructor(wAProfileImageView: WAProfileImageView, ctx: Context) + : WAProfileImagePresnterInterface,OnClickListener, + OnDismissListener, + OnItemClickListener, OnCancelListener , OnBackPressListener { + + + override fun launchOptionDialog(context: FragmentActivity, requestCode: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PermUtil.checkForCamara_WritePermissions(context, object : TaskFinished { + override fun onTaskFinished(check: Boolean?) { + //TODO open Custom Dialog + showCustomeDialog(context, requestCode) + } + }) + } else { + //TODO open Custom Dialog + showCustomeDialog(context, requestCode) + + } + + + } + + var view: WAProfileImageView? = null + lateinit var context: Context + + init { + view = wAProfileImageView + context = ctx + + } + override fun onClick(dialog: DialogCustomization, view: View) { + + } + override fun onDismiss(dialog: DialogCustomization) { + } + override fun onItemClick(dialog: DialogCustomization, item: Any, view: View, position: Int) { + } + override fun onCancel(dialog: DialogCustomization) { + + } + override fun onBackPressed(dialog: DialogCustomization) { + + } + + + private fun showCustomeDialog(context: FragmentActivity, requestCode: Int) { + val holder = ListHolder() + val adapter = SimpleAdapter(context, 3) + val builder = DialogCustomization.newDialog(context).apply { + setContentHolder(holder) + setHeader(R.layout.header, true) + setFooter(R.layout.footer, true) + setCancelable(true) + setGravity(Gravity.BOTTOM) + setAdapter(adapter) + setOnClickListener(this@WAProfileImagePresenter) + setOnItemClickListener(this@WAProfileImagePresenter) + setOnCancelListener(this@WAProfileImagePresenter) + setOnDismissListener(this@WAProfileImagePresenter) + setOnBackPressListener(this@WAProfileImagePresenter) + // setExpanded(expanded) + + + /*if (contentHeightInput.text.toString().toInt() != -1) { + setContentHeight(contentHeightInput.text.toString().toInt()) + } else { + setContentHeight(ViewGroup.LayoutParams.WRAP_CONTENT) + } + + if (contentWidthInput.text.toString().toInt() != -1) { + setContentWidth(800) + } +*/ + setOverlayBackgroundResource(android.R.color.transparent) + + + } + + builder.create().show() + + + } + +} + diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresnterInterface.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImagePresnterInterface.kt similarity index 76% rename from whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresnterInterface.kt rename to whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImagePresnterInterface.kt index 7ba38a2..c8c60a2 100644 --- a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImagePresnterInterface.kt +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImagePresnterInterface.kt @@ -1,8 +1,7 @@ -package com.mindorks.waprofileimage +package com.mindorks.waprofileimage.waprofileImage import android.support.v4.app.FragmentActivity interface WAProfileImagePresnterInterface { fun launchOptionDialog(context: FragmentActivity, requestCode: Int) - } \ No newline at end of file diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImageView.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImageView.kt similarity index 84% rename from whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImageView.kt rename to whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImageView.kt index 3cd5b35..a742cc9 100644 --- a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImageView.kt +++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImageView.kt @@ -1,4 +1,4 @@ -package com.mindorks.waprofileimage +package com.mindorks.waprofileimage.waprofileImage import android.support.v4.app.FragmentActivity import android.view.MotionEvent diff --git a/whatsappprofileimage/src/main/res/anim/slide_in_bottom.xml b/whatsappprofileimage/src/main/res/anim/slide_in_bottom.xml new file mode 100644 index 0000000..ba663a1 --- /dev/null +++ b/whatsappprofileimage/src/main/res/anim/slide_in_bottom.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/whatsappprofileimage/src/main/res/anim/slide_out_bottom.xml b/whatsappprofileimage/src/main/res/anim/slide_out_bottom.xml new file mode 100644 index 0000000..335c4c4 --- /dev/null +++ b/whatsappprofileimage/src/main/res/anim/slide_out_bottom.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/whatsappprofileimage/src/main/res/drawable/ic_google_maps_icon.png b/whatsappprofileimage/src/main/res/drawable/ic_google_maps_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3de6d3a279b2c16eac79c36b406e005726ed7495 GIT binary patch literal 5823 zcmV;w7C`BVP)JG?o*I+z>z^fMaGd!L100c0bOTy7-wJ|T@`j!MENY} zhzAHN=q@md2*M!9eJGcJ5FiO8A?fZUoler-RW+Zts@|%%s*mcB#8sF6C*9RmZ@u+> zf6wtg@7v*U>s7A=xO#2vR%5sYw4Oz93+OGN^-Q3io4cjZTR@{DaOv?Gt!;vDjOeW% zDh2|bAqtrg_#{CTEgWa5<}6$xXS1EP#KaweBX(Cc5)WjrSWy#Q>*3G(*(vnuZ)e;N zk7tHZQ$11eR%VL+fbQuIni+{_Cg#XE0z1n-TV1G71OXp8Y z_WHy*yvMVE_juAF2pSWHVGvM;oFyiPwpRs1$nVW(7vjBQ|-jueY$GcoC zFCQZY0`fDlN{kYyd<_R%Qqmb~qtxlGR=;jAfWmS*1d3zTrSI^qjr)0&OVT)p;K zG)n!+0=+Wp;RpHB()UEaKQ>JC33<3S3Z)HLlT)_XG9ABe_3}qmW>9Ml^ub+A5)*(l z-e-;a+G?|2FyBXW?vQKV*a`j_X&3b3kdb3A~8p_l! zp6OBMtC$@Sg+Tz)MR1&`JOD8?;(nyRhWF$MFCDeW-wLu16FE-)ORr(ClPp&@U)mn8 zJPG+cqQGmNyE``-f#qklwFsVUG&U;O!w5^ve3m4WJ(a@Vy7 zBu_~nM*?b8s`}Xol!6MSY8EK{UTZtEO2n$2*O5@|j|ynHpT{?@3gmffK>8@FUPM}~ z9laVSp=qq0@wH(Tu9%8FIeAIz4?q3)IsqNIdBMA0zjq1&Njsz7R%hmVh0=^rhRgsZ zCeNxj$W25B`pc_T`2C^c&=3qh)a46cXORol0qQW6F;LF4avb_MNkZEMNATbn^Hq^V z{J33hKiM(7PC$o#GVi2M@ZGMn3PsoSo&=Z#PytNdQ|OwXR7_q1Dg#!bORiO(rBHXG zwyh{ib~V6^Gv%0k#uXLN!_EqvEBESWO-g&TfkrkfMrEWTmYBaG57mJq)Bc#4tL-*_ z*}6_Z@BDant|*Faf*J{-PH-IJt+B9KWHVBsDi_!Aew6#<%!7QVf)5Fl0+wM@|NPW3 zCq`eY2x0E*9c8Qd>{=JLMsRf3ncSnch7D$h4j6}Ob_7I}1O7fxHUy1)Z{V|S|TnILKCD7>=8Vr;Sz+VoP zV)#{$DbO08$JT;URPj<;p)#PEiFQCl@6;r8N=^t5RK1)NI&AyOS~sAZ^gvnFSvd>t z&l-&Jtp{Oi-T~|@J|)2$kK202CC%jJLXrGg zQYRDP7acCas4JC`0}_A$FJQ~HQdAQE84jp&ykC=KWGBU&0u=#&_O`t-bpqPo091NZ z#b-`vGX%49#^8h8-FV~t-&LPTKs=g#H>R~8DcyTd(J4H0VlAoyK2@Ep|J2}`r%dxt zIPt*up-i4lpX3qvvdD#NRlbNoNnje4o{A6VSdJi9&;YXijn~ zEbliD7S4jl54?)+%JNj-NbYX|hsE8eU|4!L>GL^a_r^()qr7s7PZW=K`FvmRIsxrNfC>sw znzf3M%zS?5`#=KCto<+Jil<0b9~o)VhY=Y)v84NC>Hhm~oW;UJtHH6+oyk+F|76Ww z4qWE{ZZ7x&EZfgVSY|QbNxHC6CfJAr8G$lM!R#f&y2ldaBbOSlf5! zfhlzY+H2#iT#EvfHL4joqcN%72x-I@99)SLu1j)<6_^B2h9eb!8~6m0><*l8Uc#LH zD|8B#XQH9frQjJ7bqfhTK}zgfdX&Uomrp2<%KA(ZxaO_FrOGOGrj%f{qIE(XIwZz{ zn7q<)J*N-_YA?^v>-xq1*Xs;aRVeK*p!mp9(vjt7Kf$Ied)29>tTuoItu=$5M7zXv z?7aRB79D$A3N$q2$f;0xbC+Su;$bHWPaG-1xLhUDn*e1k+%E`{`P(hTA<EbZ2S?ivOgy?^95T7rwp$Eq~GbMco7IzG7`G zIEbm&g;1xP=QmzTY5CB^k3P~*y9{(q=kf%oNPVdGyl5PggxC7dL(BLy2%>;xr#E6t z-T_5{vfmq;-WAXGn1&tKPvE(4H=v4_%~UI*gfjQw`8zdt+IM?Ez>EJVLQf}M^$i0w zh}q2n6#(Ap7ar=r^XrX~)JeWG1=@Y%%v=aeVy{|?h}MbeSk(0)bZ(RdUKH?I!C`DK zI4T9I{>{2zbc^0tcWE~^UD=0#vfe{;%3xDx)v4v4!E z<43jhK}VckDgxy^zyAA_mQ&2s85PiOAI{9>l(b1d;PP0c#3bVG7QN6TwLQ{e8=+B5 z0-DA(lKkhZXE)>hOS^SbP}Xm%rmjAO>@L`Hol%_>ibWLh$Ne`j>Ke0z9-^S3a)}}< z2xam$0?MxO{xKtW93(-)^ku5hZXe9d1@sElz$))VAV8cwXT@V3@4>Y8BcvP8J+Kl- zOTSkYH&iE(b3*zM)HNwbYyZ6hgh~j0c&^tEF$OX8t)h78K zG7zG^uBq+uN}rjCx5;Z~B>#Nz)Fxagzov92Jj_U@F5N{GC1uWS>A@3O6|i$6JeLI& zt_;9ZV^m!@n6$s)R4und{I;`ohZp+)_uGGrf}?thDbU+C%*f?LBTy|mC)wlCEwvqT zl3SovLK;$HlhL(tYpIM!UwM@!Sn|#L_{Moz&O3?eGbxhMZ_-EB#pDn$Y5N76{HWd| zXc4v^qI-GU9C#;8UNmU<@aO9tD1v|jZIjY5JLewsZ`ujpx-MgP;YoZ~UV!3iH>&(K z=+Za~zwLZ4IwWOC{i*O*;mIR!;Yi8%O5UuQIaQ{tLT#SUK1h<4$MYS^FgA^>i|UE- zH6rrf@I$zu`E;Y3VD!pLSIFVHR5d1{ziCIQ<6HBO;OS%Ql+<4z(IdemXRhezQeYZP zR)Je_NkGx604#=;^GKV+f@egsk9Q6k1>eA7;5e@Sfp#W7lu;-tZY=Kp8{E^PH+B`C z#J?STTi)8jtXj&$E_;(Eu}N6ocNRJ|YJu}*1(>wwB`GIm%{&rJ3q&aZ{h_r|vI`2f znp&@K4#4|`Ko$<9mma2ZAQW@Zq%6JjHcms?)Y(8nV#5IKWKd|E#O8Qs-~u>ov3Tw5 zR=jiZOMOX9^<-LdKDGTYEa);8=gRUiY0t|FP_2+gvmIjsPhmcp<$;YeW~9M@d=&!n zUkd=&qW~IWLcntzN`Ey0eAk|`BpO1Y9oOp>O3e4QezPT=tUmuaR-fOdjIkEqXyBo< zqVZY%u(pJCZ^-vK?~uq#o&xP504*Qg$ln{QzP0 z@vZt{;o-NiulQ`kz!YO~&38Xsz8>J-UjxUf6XD^9P^Uj87S7+yfY>ribrlVw(Cqd4 zw21(k-r*N`ywhl@So~dCK3+Y$6$eW$Ku~uL%QO@04$RCRg?qF5;JI%%V*9ld^4c8} zjE&QeWSfBJ+q-eE6wOr!7g3o$p$YP8CZkHolFk#HrLeDh1uWp_(TPW%?Z>2FXbe z{^z#kcq~&{(bRcb9V+#MAjTrk_b@5~>OKtZyNYteyuJ)}w^M&B!$KUvTl&lK2=wY7 zlod3LLf1{#ugk05)3)YtoCQhNI9M%K1VkPcfhuVYM>>Pq{}d`tOU_>jK>wOm0h=1z zLe(O)V+1DgneIUNH8XC@M#Syg#mF{TomEZK}9gSB3PX@iOFz1 zIu~M6qmTpBAV6EMo32wR8+e1&Dt{Xeq8czn5hR+w(4ovEn(JE=tm&vu`W<*%wtOEF zot;j2JnVv*7Se^{Wc zA}BOcLJ;x?s*@u38$Ze2d&n1=q+xNH%8RxseofCDSf%tZ5MLoog}@|%HZtb!bKyWJ=+SAHo%wgv6{6tUa4O6~cYG(+i(d4P@i!9s|K z`cVL75ru~tKD^~ursFvVCM~8+?<)o=HIj=lrc~U?<8ZvUTJodbPPe0M!sOuG)ByDd z1!@-Y8>x9FtISAOi72XSiX}&%!daJ`XR4Jc4zOE*DZMnPkzi6Tx|Hf#9vmx{g5!Bq z3>Xa0@Ow-Rt-*i>ZN)L^N}^4~GO21rVVdC=L3}1r=GTRNgJ4QC90F$&Juj9*ZfIubYUn`{w0*Y6?3h#lv@J{;|h-uCKZ-8d5 zeIzP{5})~^G&c`dtOz#y{K^bGcYGnvFg{b+gd)Q;ET^!QJyIMT+FroifdU+ngS85d z_}#*yB9z>?5qAF0Qf?3*8V%6UFeCd-0+UURXzi3Pno2O4TB^FeUuu>tzQ~)+?!QC8 zBYh(0sVpQ3YGEO6+_>@MRHzJ-4M3*-1WApaJ+=^M-Ey9(gGtB11ST!bkb?BoJ_5$w zUOT7-1qITqD$=V#6q@nwBcXTSDM|a%Y_dJZ^dB9_{T6|u>NG4q@>DQP`eX`F$~BZS z)jF3HsT7}DOQ?ZB0C{Cg&bl_0(Ps2DZNFEyCZs~hJ&hZ>iwaBl7Eh|xLuiHK}{Lq zFs_rpl%jdQoEp)9Lv>+ye?3N0qYwdzFx~h;$rQe z8#E};+QF1%Sm4btg-JoG(ca)QS;d&a)cmcSIo#Jx#MFM#PgAKe=F#wG6!HRhSr^o`M+1|a#b=8#QcGQOL-04wpS{Yapy9n}2% zd?}@B5TG5`P03x>!QCeO3@DgpQB@iiAAKqaru6>^3}zmrOnOpSm}HJbH-It~DWw}~ znx%Y}s#JYNjO`jdR1*EKUcHLS%F6mroBn=i%%#yys@sMTVaP*dZcb)Edkgz9USC_z z36&bDsgp=iMW;;Z1|gKGs$O|R=Khm!0;<#LT$G)iUB6qxg-ats{AeL46e~V69nTzl zBCMI~V6y3zG0&uGGo)au+b{viT7jftrJbQU0%qM+tLVQdB_*YPf%?u3KVi3V@_!MC z@FLU6zaEbala}DZ1t3$O$wn}yFY>%zd$9w@u`Yx}|Bhl*P|3y!kH<4RGc%Kv`J;ZB z{;a0z^srTNah6#(1=AqpZ~=&JJEN!T1CR&;g~lke4y!Pgo6#}~Z9uA0<%pOwByvjS z_xmfHPUqm9oE+oM-H33_`iG)JgYRsdY};(Nb4^0Eq$<-XM|0yVq|a1M*4$^Z69Kc% zMuw^NjIh4Hhk&X5!%tJ)ZujanYu3zJvSf*I>wn#VlB-=u?|jq|Z+|&9#;V&@MOG)D zIrgOLGo?F8lWh5(b%JT|vC+WP=b?%t2s0J7lPc8!0b{!9p^{wG>-Fw)yWJ0V>C#2{ z`9steZ~pUc+El^u-Xjya#Q7GhIMBjb8kf{GCdFaybO>e253d-qYI;gb#?OibCHTz0 z{$S1CmZfoZ7VM?U)rr(H}a$5 zTEEN&zSa%?f4|@s(0W$DEugo6)-!>6Ztj*sZvm}m0`=V7KLK4am>5~B77PFY002ov JPDHLkV1gEKB`N>_ literal 0 HcmV?d00001 diff --git a/whatsappprofileimage/src/main/res/drawable/ic_google_messenger_icon.png b/whatsappprofileimage/src/main/res/drawable/ic_google_messenger_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0c65a95eeb88bd08143420a84badef858784bebf GIT binary patch literal 2414 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 YKU^k*pff!z9{>OV07*qoM6N<$f{w!K{r~^~ literal 0 HcmV?d00001 diff --git a/whatsappprofileimage/src/main/res/drawable/ic_launcher.png b/whatsappprofileimage/src/main/res/drawable/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..96a442e5b8e9394ccf50bab9988cb2316026245d GIT binary patch literal 9397 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 + + + + + + \ No newline at end of file diff --git a/whatsappprofileimage/src/main/res/layout/dialog_list.xml b/whatsappprofileimage/src/main/res/layout/dialog_list.xml new file mode 100644 index 0000000..1849142 --- /dev/null +++ b/whatsappprofileimage/src/main/res/layout/dialog_list.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/whatsappprofileimage/src/main/res/layout/dialog_view.xml b/whatsappprofileimage/src/main/res/layout/dialog_view.xml new file mode 100644 index 0000000..989f51f --- /dev/null +++ b/whatsappprofileimage/src/main/res/layout/dialog_view.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/whatsappprofileimage/src/main/res/layout/footer.xml b/whatsappprofileimage/src/main/res/layout/footer.xml new file mode 100644 index 0000000..e89608c --- /dev/null +++ b/whatsappprofileimage/src/main/res/layout/footer.xml @@ -0,0 +1,31 @@ + + + + + +