package com.ydl.confide.home import android.annotation.SuppressLint import android.app.Dialog import android.content.Intent import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Build import android.os.Bundle import android.text.TextUtils import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView import androidx.fragment.app.FragmentActivity import com.alibaba.android.arouter.launcher.ARouter import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.interfaces.DraweeController import com.facebook.drawee.view.SimpleDraweeView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.tencent.smtt.export.external.interfaces.JsResult import com.tencent.smtt.sdk.* import com.ydl.confide.R import com.ydl.confide.event.ConfideDialogEvent import com.ydl.confide.event.ChangeAnotherExpertEvent import com.ydl.confide.home.http.ConfideHomeApi import com.ydl.confide.router.PhoneCallIn import com.ydl.utils.WebUrlParamsUtils import com.ydl.webview.IJavascriptHandler import com.ydl.webview.ProgressWebView import com.ydl.webview.TellData import com.ydl.webview.WebModularServiceUtils import com.ydl.ydlcommon.modular.findRouteService import com.ydl.ydlcommon.utils.ScreenUtil import com.ydl.ydlcommon.utils.TimeUtil import com.ydl.ydlcommon.utils.actionutil.ActionCountUtils import com.ydl.ydlcommon.view.dialog.CommonDialog import com.ydl.ydlnet.YDLHttpUtils import com.yidianling.common.tools.LogUtil import com.yidianling.common.tools.RxImageTool import com.yidianling.common.tools.ToastUtil import com.yidianling.im.api.service.IImService import com.yidianling.user.api.event.UserBindPhoneEvent import com.yidianling.user.api.event.UserLoginEvent import com.yidianling.user.api.service.IUserService import de.greenrobot.event.EventBus import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import java.io.UnsupportedEncodingException import java.net.URLDecoder import kotlin.math.roundToInt class ConfideBottomSheetDialogFragment : BottomSheetDialogFragment() { companion object { private const val KEY_JUMP_URL = "jumpUrl" private const val KEY_DOCTOR_ID = "doctor_id" private const val KEY_SHOULD_SHOW = "should_show" private const val KEY_LISTEN_FREE = "listen_free" private const val KEY_UID = "uid" private const val EXPERT_URL = "expert_url" } private var firstVisitWXH5PayUrl = true lateinit var wv_content: ProgressWebView lateinit var text_title: TextView lateinit var line: View lateinit var rl_title: View lateinit var close_webview_Icon: View lateinit var layoutCall: View lateinit var layoutChange: View lateinit var tvCall: TextView lateinit var ivCall: ImageView lateinit var tvTime: TextView lateinit var first_order: SimpleDraweeView lateinit var free: SimpleDraweeView lateinit var confideProgress: View lateinit var layout_change_text: View lateinit var layoutBottom: View lateinit var itemView: View var lineStatus: Int = 0 var bottomSheet: FrameLayout? = null var mJtoJHandle: IJavascriptHandler? = null private var behavior: BottomSheetBehavior<*>? = null var isLogin: Boolean = false lateinit var jumpUrl: String lateinit var doctorId: String lateinit var expertUrl: String var shouldShow: Boolean? = false var uid: String? = null var listen_free: Boolean = false private var hasOnResume = false fun showBottomSheetDialog( activity: FragmentActivity, jumpUrl: String, doctorId: String, shouldShow: Boolean = false, uid: String? = null, listenFree: Boolean = false, expertUrl: String = "" ) { arguments = Bundle().apply { putString(KEY_JUMP_URL, jumpUrl) putString(KEY_DOCTOR_ID, doctorId) putBoolean(KEY_SHOULD_SHOW, shouldShow) putBoolean(KEY_LISTEN_FREE, listenFree) uid?.let { putString(KEY_UID, it) } putString(EXPERT_URL, expertUrl) } show(activity.supportFragmentManager, "confide_bottom_showdialog") } @SuppressLint("WrongConstant") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NORMAL, R.style.AppBottomSheet) EventBus.getDefault().register(this) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.confide_webview, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ActionCountUtils.record( "ydl_experts_detail_popupwindows_page", "ydl_experts_detail_popupwindows_page_visit" ) jumpUrl = arguments?.getString(KEY_JUMP_URL) ?: "" doctorId = arguments?.getString(KEY_DOCTOR_ID) ?: "" expertUrl = arguments?.getString(EXPERT_URL) ?: "" shouldShow = arguments?.getBoolean(KEY_SHOULD_SHOW) ?: false uid = arguments?.getString(KEY_UID) listen_free = arguments?.getBoolean(KEY_LISTEN_FREE) ?: false onLoadDialStatus(doctorId) wv_content = view.findViewById<ProgressWebView>(com.ydl.webview.R.id.wv_content) wv_content.progressbar.visibility = View.GONE close_webview_Icon = view.findViewById<View>(R.id.close_webview_Icon) text_title = view.findViewById<TextView>(R.id.text_title) line = view.findViewById<View>(R.id.line) rl_title = view.findViewById<View>(R.id.rl_title) close_webview_Icon?.setOnClickListener { this.dismiss() } webViewInit(wv_content) } override fun setCancelable(cancelable: Boolean) { val dialog = dialog val touchOutsideView = dialog?.window?.decorView?.findViewById<View>(com.google.android.material.R.id.touch_outside) val bottomSheetView = dialog?.window?.decorView?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) if (cancelable) { touchOutsideView?.setOnClickListener(View.OnClickListener { if (dialog.isShowing) { dialog.cancel() } }) BottomSheetBehavior.from<View>(bottomSheetView!!).setHideable(true) } else { touchOutsideView?.setOnClickListener(null) // if false 按返回键也无法取消 // dialog.setCancelable(false) BottomSheetBehavior.from<View>(bottomSheetView!!).setHideable(false) } } private fun onShowReady() { itemView = LayoutInflater.from(context).inflate(R.layout.confide_bottom_two, null, false) layoutCall = itemView.findViewById<View>(R.id.layoutCall) layoutChange = itemView.findViewById<View>(R.id.layoutChange) confideProgress = itemView.findViewById<View>(R.id.confide_progress) layout_change_text = itemView.findViewById<View>(R.id.layout_change_text) layoutBottom = itemView.findViewById<View>(R.id.layoutBottom) tvCall = itemView.findViewById<TextView>(R.id.tvCall) ivCall = itemView.findViewById<ImageView>(R.id.ivCall) tvTime = itemView.findViewById<TextView>(R.id.tvTime) first_order = itemView.findViewById<SimpleDraweeView>(R.id.first_order) free = itemView.findViewById<SimpleDraweeView>(R.id.free) val userService = findRouteService(IUserService::class.java) isLogin = userService.isLogin() first_order.visibility = if (isLogin) View.GONE else View.VISIBLE val controller: DraweeController = Fresco.newDraweeControllerBuilder() .setUri(Uri.parse("res://drawable/" + R.drawable.confide_first_order)) .setOldController(first_order.controller) .setAutoPlayAnimations(true) // 是否自动播放Gif动画 .build() first_order.controller = controller layoutCall.setOnClickListener { ActionCountUtils.record( "ydl_experts_detail_popupwindows_page", "ydl_experts_detail_popupwindows_page_visit" ) if (!PhoneCallIn.loginByOneKeyLogin(requireContext(), true)) { return@setOnClickListener } //判断是否绑定手机号 if (!userService.isBindPhone()) { CommonDialog(requireContext()) .setMessage("为了您的账号安全,请绑定手机号") .setLeftOnclick("取消") { } .setRightClick("确定") { //跳转绑定手机号页面 userService.wxBindToInputhonePage(requireActivity()) } .setCancelAble(false) .show() return@setOnClickListener } when (lineStatus) { 1, 4 -> { callJsFun(wv_content, "listenCallClick()") } 3, 6 -> { if (!uid.isNullOrBlank()) { activity?.let { it1 -> ARouter.getInstance().navigation(IImService::class.java) ?.startChatBySessionId(it1, uid!!) } } } 5 -> { if (!TextUtils.isEmpty(expertUrl)) { val linkUri = Uri.parse(expertUrl) if (linkUri != null) { val host = linkUri.host if (!TextUtils.isEmpty(host) && host == "h5") { //如果是h5,跳转至NewH5Activity try { var params = URLDecoder.decode( linkUri.getQueryParameter("params"), "UTF-8" ) ARouter.getInstance().build("/new_h5/h5") .withSerializable("routerParam", params).navigation() } catch (e: NullPointerException) { LogUtil.e("params参数为空") } catch (e: UnsupportedEncodingException) { LogUtil.e("解码错误") } catch (e: UnsupportedOperationException) { LogUtil.e("这不是一个uri格式的地址") } } } } } } } layoutChange.setOnClickListener { layout_change_text.visibility = View.GONE confideProgress.visibility = View.VISIBLE callJsFun(wv_content, "handleNext()") } } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog onShowReady() dialog.setOnShowListener { if (shouldShow == true) { layoutChange.visibility = View.VISIBLE } else { layoutChange.visibility = View.GONE } val layoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT ).apply { gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL bottomMargin = ScreenUtil.getNavBarHeight(context) } dialog?.window?.addContentView(itemView, layoutParams) bottomSheet = (it as BottomSheetDialog).findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout? behavior = BottomSheetBehavior.from(bottomSheet!!) behavior?.peekHeight = (resources.displayMetrics.heightPixels * 0.7F).roundToInt() if (jumpUrl?.contains("payPage=1")) { //支付高度 setMaxHeight(RxImageTool.dp2px(450f)) behavior?.isHideable = false rl_title.visibility = View.GONE } else { setMaxHeight(resources.displayMetrics.heightPixels) } //true是跳过peekHeight,直接滑下去,false是可以滑动到顶部还可以保持peekHeight在滑下去 // behavior?.skipCollapsed=true behavior?.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { when (newState) { BottomSheetBehavior.STATE_EXPANDED -> { bottomSheet.background = ColorDrawable(Color.WHITE) if (!jumpUrl?.contains("payPage=1")) { rl_title.visibility = View.VISIBLE text_title.visibility = View.VISIBLE close_webview_Icon.visibility = View.VISIBLE line.visibility = View.GONE } } BottomSheetBehavior.STATE_DRAGGING -> { bottomSheet.setBackgroundResource(R.drawable.confide_bottom_webview) if (!jumpUrl?.contains("payPage=1")) { rl_title.visibility = View.VISIBLE } close_webview_Icon.visibility = View.GONE text_title.visibility = View.GONE line.visibility = View.VISIBLE } BottomSheetBehavior.STATE_COLLAPSED -> { if (!jumpUrl?.contains("payPage=1")) { rl_title.visibility = View.VISIBLE } line.visibility = View.VISIBLE } } } override fun onSlide(bottomSheet: View, slideOffset: Float) {} }) } return dialog } private fun setMaxHeight(height: Int) { bottomSheet?.layoutParams?.height = height bottomSheet?.requestLayout() } private fun callJsFun(wv_content: ProgressWebView, funcName: String) { val sb = StringBuffer("javascript:") sb.append(funcName) wv_content.post { try { wv_content.loadUrl(sb.toString()) } catch (e: Exception) { e.printStackTrace() } } } fun onEventMainThread(event: ConfideDialogEvent) { if(!hasOnResume) return // 第一位表示拨打按钮,第二位代表再换一位按钮 val show = event.show // if (show == 0) { // behavior?.state = BottomSheetBehavior.STATE_EXPANDED // } setMaxHeight(RxImageTool.dp2px(450f)) rl_title.visibility = View.GONE behavior?.isHideable = false layoutCall.visibility = if (show and 0x01 == 0x01) View.VISIBLE else View.GONE layoutChange.visibility = if (show and 0x02 == 0x01) View.VISIBLE else View.GONE } fun onEventMainThread(event: ChangeAnotherExpertEvent) { updateChange(event.doctorID, event.title, event.uid, event.linkUrl) } private fun updateChange(doctorId: String, title: String, uid: String, linkUrl: String) { if (doctorId == "0") {//没有下一位了 layoutChange.visibility = View.GONE return } this.uid = uid text_title.text = title confideProgress.visibility = View.GONE layout_change_text.visibility = View.VISIBLE this.doctorId = doctorId onLoadDialStatus(doctorId) callJsFun(wv_content, "setUnRead(${getUnReadByUid(uid = uid)})") } @SuppressLint("ClickableViewAccessibility") private fun webViewInit(wv_content: ProgressWebView) { val userService = findRouteService(IUserService::class.java) val isLogin = userService.isLogin() first_order.visibility = if (isLogin) View.GONE else View.VISIBLE val setting: WebSettings = wv_content?.settings!! //支持js setting.setJavaScriptEnabled(true) //设置字符编码 setting.setDefaultTextEncodingName("GBK") setting.setDomStorageEnabled(true) val appCachePath: String = context?.getCacheDir()?.getAbsolutePath().toString() setting.setAppCachePath(appCachePath) setting.setAllowFileAccessFromFileURLs(true) setting.setAppCacheEnabled(true) setting.setAllowFileAccess(true) setting.setBlockNetworkImage(false) wv_content?.scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY //滚动条风格,为0指滚动条不占用空间,直接覆盖在网页上 mJtoJHandle = WebModularServiceUtils.getWebService() .getJavascripHandler(requireActivity(), wv_content, tellData = TellData()) wv_content?.addJavascriptInterface(mJtoJHandle, "javascriptHandler") // var jumpurl = "http://192.168.210.152/jy/listenMask?listenerId=257&isFromApp=1" loadUrl() // wv_content.loadUrl(jumpUrl) wv_content.setOnTouchListener { p0, p1 -> //canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部 if (!wv_content.canScrollVertically(-1)) { wv_content.requestDisallowInterceptTouchEvent(false); } else { wv_content.requestDisallowInterceptTouchEvent(true); } false } wv_content.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(webview: WebView, url: String?): Boolean { if (url.isNullOrBlank()) return false return shouldOverridePayUrl(webview, url) } } wv_content.webChromeClient = object : WebChromeClient() { override fun onJsAlert( webView: WebView, s: String, s1: String, jsResult: JsResult ): Boolean { return super.onJsAlert(webView, s, s1, jsResult) } override fun onProgressChanged(view: WebView, newProgress: Int) { if (newProgress == 100) { wv_content.progressbar.visibility = View.GONE } else { if (wv_content.progressbar.visibility == View.GONE) { wv_content.progressbar.visibility = View.VISIBLE } wv_content.progressbar.progress = newProgress } if (newProgress == 100) { callJsFun(wv_content, "setUnRead(${uid?.let { getUnReadByUid(uid = it) }})") } super.onProgressChanged(view, newProgress) } override fun onReceivedTitle(view: WebView, title: String) { super.onReceivedTitle(view, title) text_title.text = title } // For Android < 3.0 fun openFileChooser(valueCallback: ValueCallback<Uri?>) { } // For Android >= 3.0 fun openFileChooser(valueCallback: ValueCallback<*>, acceptType: String?) { } //For Android >= 4.1 override fun openFileChooser( valueCallback: ValueCallback<Uri>, acceptType: String, capture: String ) { } // For Android >= 5.0 override fun onShowFileChooser( webView: WebView, filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams ): Boolean { return true } } } private fun loadUrl() { val url = WebUrlParamsUtils.getSuffix(jumpUrl, mJtoJHandle?.getUriAppendSuffix()) wv_content?.loadUrl(url) } private fun shouldOverridePayUrl(view: WebView, url: String): Boolean { if (url.startsWith("weixin://")) { return try { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) true } catch (e: java.lang.Exception) { // 防止手机没有安装处理某个 scheme 开头的 url 的 APP 导致 crash ToastUtil.toastShort("该手机没有安装微信") true } } else if (url.startsWith("alipays://") || url.startsWith("alipay")) { return try { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) true } catch (e: java.lang.Exception) { // 防止手机没有安装处理某个 scheme 开头的 url 的 APP 导致 crash // 启动支付宝 App 失败,会自行跳转支付宝网页支付 true } } // 处理普通 http 请求跳转 // if (!(url.startsWith("http") || url.startsWith("https"))) { // return true; // } // 处理微信 H5 支付跳转时验证请求头 referer 失效 // 验证不通过会出现“商家参数格式有误,请联系商家解决” if (url.contains("wx.tenpay.com")) { // 申请微信 H5 支付时填写的域名 // 比如经常用来测试网络连通性的 http://www.baidu.com val parse = Uri.parse(url) //从url 中获取 域名信息 val redirectUrlStr = parse.getQueryParameter("redirect_url") val redirectUrl = Uri.parse(redirectUrlStr) // 申请微信 H5 支付时填写的域名 val referer = redirectUrl.scheme + "://" + redirectUrl.host // 兼容 Android 4.4.3 和 4.4.4 两个系统版本设置 referer 无效的问题 return if ("4.4.3" == Build.VERSION.RELEASE || "4.4.4" == Build.VERSION.RELEASE ) { if (firstVisitWXH5PayUrl) { view.loadDataWithBaseURL( referer, "<script>window.location.href=\"$url\";</script>", "text/html", "utf-8", null ) // 修改标记位状态,避免循环调用 // 再次进入微信H5支付流程时记得重置状态 firstVisitWXH5PayUrl = true firstVisitWXH5PayUrl = false } // 返回 false 由系统 WebView 自己处理该 url false } else { // HashMap 指定容量初始化,避免不必要的内存消耗 val map = HashMap<String, String>(1) map["Referer"] = referer view.loadUrl(url, map) true } } else if (url.contains("alipay")) { return false } return false } override fun onPause() { super.onPause() hasOnResume = false } override fun onResume() { super.onResume() hasOnResume = true if (doctorId.isNotBlank() && layoutCall.visibility == View.VISIBLE) { onLoadDialStatus(doctorId) } } fun getUnReadByUid(uid: String): Int { if (uid.isBlank()) return 0 return ARouter.getInstance().navigation(IImService::class.java).getUnReadByUid(uid) } @SuppressLint("CheckResult") private fun onLoadDialStatus(doctorId: String, showTip: Boolean = false) { if (doctorId.isBlank()) return if (doctorId == "0") return val confideApi = YDLHttpUtils.obtainApi(ConfideHomeApi::class.java) confideApi.getDialStatus(doctorId) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ resp -> if (resp.code == "200") { layoutCall.visibility = View.VISIBLE lineStatus = resp.data?.confideLine ?: 2 expertUrl = resp.data?.linkUrl ?: "" if (isLogin && listen_free) { free.visibility = View.VISIBLE val controller: DraweeController = Fresco.newDraweeControllerBuilder() .setUri(Uri.parse("res://drawable/" + R.drawable.confide_free)) .setOldController(free.controller) .setAutoPlayAnimations(true) // 是否自动播放Gif动画 .build() free.controller = controller } else { free.visibility = View.GONE } /* * 1在线 2离线 3通话中 4 继续倾诉 * */ when (lineStatus) { 1 -> { tvCall.text = "立即拨打" tvTime.visibility = View.GONE ivCall.visibility = View.VISIBLE layoutCall.background = context?.getDrawable(R.drawable.confide_line_bg_1) } 2 -> { tvCall.text = "已离线" tvTime.visibility = View.VISIBLE ivCall.visibility = View.GONE tvTime.text = "(点击邀请上线)" layoutCall.background = context?.getDrawable(R.drawable.confide_line_bg_2) if (showTip) { if (!uid.isNullOrBlank()) { findRouteService(IImService::class.java).startChat( requireActivity(), uid!!, 0, 0 ) } } } 3 -> { tvCall.text = "通话中" tvTime.visibility = View.VISIBLE ivCall.visibility = View.GONE tvTime.text = "(点击留言)" layoutCall.background = context?.getDrawable(R.drawable.confide_line_bg_3) if (showTip) { ToastUtil.toastShort("对方正在通话中,请稍后拨打") if (!uid.isNullOrBlank()) { findRouteService(IImService::class.java).startChat( requireActivity(), uid!!, 0, 0 ) } } } 4 -> { tvCall.text = "继续倾诉" ivCall.visibility = View.VISIBLE layoutCall.background = context?.getDrawable(R.drawable.confide_line_bg_1) val t = resp?.data?.remainingTime?.remainingTime if (t != null) { tvTime.visibility = View.VISIBLE val remain = TimeUtil.getElapseTimeForShow(t * 1000) tvTime.text = "(剩余" + remain + ")" } } 5 -> { tvCall.text = "已离线,可预约" tvTime.visibility = View.GONE ivCall.visibility = View.GONE layoutCall.background = context?.getDrawable(R.drawable.confide_offline_book_bg) } 6 -> { tvCall.text = "已离线,可留言" tvTime.visibility = View.GONE ivCall.visibility = View.GONE layoutCall.background = context?.getDrawable(R.drawable.confide_offline_book_bg) } } } else { if (!resp.msg.isNullOrEmpty()) { ToastUtil.toastShort(resp.msg) } } }, { throwable -> throwable.printStackTrace() }) } override fun onDestroy() { super.onDestroy() wv_content.destroy() EventBus.getDefault().unregister(this) } //登录成功 fun onEvent(event: UserLoginEvent) { webViewInit(wv_content) } //绑定成功 fun onEvent(event: UserBindPhoneEvent) { webViewInit(wv_content) } }