WebViewFragment.kt 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. package com.adealink.weparty.webview
  2. import android.content.Intent
  3. import android.graphics.Bitmap
  4. import android.graphics.Outline
  5. import android.net.http.SslError
  6. import android.os.Bundle
  7. import android.view.KeyEvent
  8. import android.view.View
  9. import android.view.ViewOutlineProvider
  10. import android.webkit.CookieManager
  11. import android.webkit.SslErrorHandler
  12. import androidx.core.net.toUri
  13. import androidx.fragment.app.FragmentManager
  14. import androidx.lifecycle.LifecycleOwner
  15. import com.adealink.frame.data.json.toJsonErrorNull
  16. import com.adealink.frame.ext.isViewBindingValid
  17. import com.adealink.frame.locale.language.languageManager
  18. import com.adealink.frame.log.Log
  19. import com.adealink.frame.mvvm.view.viewBinding
  20. import com.adealink.frame.network.http.interceptor.KEY_API
  21. import com.adealink.frame.network.http.interceptor.KEY_CHANNEL
  22. import com.adealink.frame.network.http.interceptor.KEY_DEVICE
  23. import com.adealink.frame.network.http.interceptor.KEY_NETWORK
  24. import com.adealink.frame.network.http.interceptor.KEY_PACKAGE_NAME
  25. import com.adealink.frame.network.http.interceptor.KEY_PLATFORM
  26. import com.adealink.frame.network.http.interceptor.KEY_TIME
  27. import com.adealink.frame.network.http.interceptor.KEY_TOKEN
  28. import com.adealink.frame.network.http.interceptor.KEY_UDID
  29. import com.adealink.frame.network.http.interceptor.KEY_VERSION_CODE
  30. import com.adealink.frame.router.Router
  31. import com.adealink.frame.router.annotation.BindExtra
  32. import com.adealink.frame.util.AppUtil
  33. import com.adealink.frame.util.PackageUtil
  34. import com.adealink.frame.util.getDeviceName
  35. import com.adealink.frame.util.naviBarHeightDp
  36. import com.adealink.frame.util.statusBarHeightDp
  37. import com.adealink.weparty.App
  38. import com.adealink.weparty.BuildConfig
  39. import com.adealink.weparty.R
  40. import com.adealink.weparty.commonui.BaseFragment
  41. import com.adealink.weparty.commonui.ext.dp
  42. import com.adealink.weparty.databinding.FragmentWebviewBinding
  43. import com.adealink.weparty.module.webview.Web
  44. import com.adealink.weparty.module.webview.Web.Common.Companion.EXTRA_TOP_RADIUS
  45. import com.adealink.weparty.storage.AppPref
  46. import com.adealink.weparty.webview.callback.IWebViewCallback
  47. import com.adealink.weparty.webview.callback.IWebViewFragmentCallback
  48. import com.adealink.weparty.webview.component.ErrorComp
  49. import com.adealink.weparty.webview.constant.NATIVE_JS_BRIDGE
  50. import com.adealink.weparty.webview.constant.NavigationBarMode
  51. import com.adealink.weparty.webview.constant.TAG_WEB_FRAGMENT
  52. import com.adealink.weparty.webview.datasource.local.WebLocalService
  53. import com.adealink.weparty.webview.jsbridge.JSBridge
  54. import com.adealink.weparty.webview.jsbridge.data.NativeMessage
  55. import com.adealink.weparty.webview.jsnativemethod.addCommonMethod
  56. import com.adealink.weparty.webview.payermax.SpWebChromeClient
  57. import com.adealink.weparty.webview.payermax.SpWebViewClient
  58. import com.ushareit.easysdk.web.util.SPWebHelper
  59. class WebViewFragment : BaseFragment(R.layout.fragment_webview),
  60. IWebViewCallback {
  61. @BindExtra(Web.Common.EXTRA_LOADING_URL, desc = "加载图标链接")
  62. var loadingUrl: String? = null
  63. @BindExtra(Web.Common.EXTRA_DIALOG_TAG, desc = "dialog的tag")
  64. var fgTag: String? = ""
  65. private val binding by viewBinding(FragmentWebviewBinding::bind)
  66. private var url: String = ""
  67. private var topRadius: Int = 0
  68. private var errorComp: ErrorComp? = null
  69. private var transparentBgWhenPageFinish = false
  70. private var transparentBg = false
  71. private var webFragmentCallback: IWebViewFragmentCallback? = null
  72. private val webHelper by lazy { SPWebHelper() }
  73. private var isPayerMaxPage = false
  74. override fun onCreate(savedInstanceState: Bundle?) {
  75. super.onCreate(savedInstanceState)
  76. Router.bind(this)
  77. }
  78. override fun initViews() {
  79. super.initViews()
  80. val topRadius = arguments?.getInt(EXTRA_TOP_RADIUS) ?: 16.dp()
  81. binding.webView.outlineProvider = object : ViewOutlineProvider() {
  82. override fun getOutline(view: View, outline: Outline) {
  83. val left = 0
  84. val top = 0
  85. val right = view.width
  86. val bottom = view.height
  87. //top corners
  88. outline.setRoundRect(
  89. left,
  90. top,
  91. right,
  92. bottom + topRadius,
  93. topRadius.toFloat()
  94. )
  95. }
  96. }
  97. binding.webView.clipToOutline = true
  98. binding.webView.webViewCallback = this
  99. if (transparentBg) {
  100. binding.root.background = null
  101. binding.webView.setBackgroundColor(0)
  102. binding.webView.background?.alpha = 0
  103. }
  104. initJsBridge()
  105. initPayerMaxPageIfNeed()
  106. }
  107. private fun initJsBridge() {
  108. binding.webView.getJSBridge(NATIVE_JS_BRIDGE)?.let { bridge ->
  109. bridge.addCommonMethod(binding.webView)
  110. }
  111. }
  112. private fun initPayerMaxPageIfNeed() {
  113. val act = activity ?: return
  114. var host: String? = null
  115. if (url.isEmpty().not()) {
  116. host = url.toUri().host
  117. }
  118. if (host?.contains("payermax") == true) {
  119. isPayerMaxPage = true
  120. }
  121. val commonWebChromeClient = binding.webView.getCommonWebChromeClient()
  122. val commonWebViewClient = binding.webView.getCommonWebViewClient()
  123. val spWebChromeClient = SpWebChromeClient(webHelper, commonWebChromeClient)
  124. val spWebViewClient = SpWebViewClient(act, binding.webView, commonWebViewClient)
  125. webHelper.initWebView(
  126. AppUtil.application,
  127. act,
  128. binding.webView,
  129. spWebChromeClient,
  130. spWebViewClient
  131. )
  132. }
  133. override fun onRequestPermissionsResult(
  134. requestCode: Int,
  135. permissions: Array<out String>,
  136. grantResults: IntArray
  137. ) {
  138. if (isPayerMaxPage) {
  139. webHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)
  140. }
  141. }
  142. override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  143. webHelper.onActivityResult(requestCode, resultCode, data)
  144. }
  145. override fun initComponents() {
  146. super.initComponents()
  147. errorComp =
  148. ErrorComp(
  149. this,
  150. binding.webView,
  151. binding.errorView,
  152. reload = { loadUrl(url) }
  153. ).apply { attach() }
  154. }
  155. override fun onResume() {
  156. super.onResume()
  157. notifyResumeMessage()
  158. }
  159. private fun notifyResumeMessage() {
  160. // notifyNativeMessage(NativeMessage<Any>(NativeMessageType.MSG_ON_RESUME.msgType))
  161. }
  162. fun showUrl(url: String) {
  163. this.url = url
  164. if (isAdded) {
  165. loadUrl(url)
  166. }
  167. }
  168. fun replaceAndShow(fragmentManager: FragmentManager, contentId: Int, url: String) {
  169. Log.d(TAG_WEB_FRAGMENT, "replaceAndShow, url:$url")
  170. this.url = url
  171. if (isAdded) {
  172. loadUrl(url)
  173. return
  174. }
  175. fragmentManager.beginTransaction()
  176. .replace(contentId, this, "WebFragment")
  177. .commit()
  178. showUrl(url)
  179. }
  180. fun getWebView(): BaseWebView? {
  181. if (!isAdded) {
  182. return null
  183. }
  184. return binding.webView
  185. }
  186. fun setTopRadius(topRadius: Int) {
  187. this.topRadius = topRadius
  188. }
  189. fun setTransparentBgWhenPageFinish() {
  190. this.transparentBgWhenPageFinish = true
  191. }
  192. fun setTransparentBg() {
  193. this.transparentBg = true
  194. }
  195. fun setWebFragmentCallback(callback: IWebViewFragmentCallback?) {
  196. this.webFragmentCallback = callback
  197. }
  198. override fun loadData() {
  199. super.loadData()
  200. loadUrl(url)
  201. }
  202. private fun loadUrl(url: String) {
  203. errorComp?.hideError()
  204. binding.webView.loadUrl(url)
  205. }
  206. override val lifeCycleOwner: LifecycleOwner
  207. get() = viewLifecycleOwner
  208. override fun closeWebView() {
  209. webFragmentCallback?.closeWebView()
  210. }
  211. override fun setTitle(title: String?) {
  212. webFragmentCallback?.setTitle(title)
  213. }
  214. override fun beforeLoadUrl(url: String) {
  215. val cookieManager: CookieManager = CookieManager.getInstance()
  216. cookieManager.setAcceptCookie(true)
  217. // 设置多个 Cookie
  218. val cookies = mutableMapOf<String, String>()
  219. cookies.put(
  220. "GAMI-web_auth_token",
  221. AppPref.token
  222. )
  223. cookies.put(
  224. "GAMI-web_app_locale",
  225. languageManager?.getLanguageCode() ?: ""
  226. )
  227. cookies.put(
  228. "GAMI-cookie_safe_area",
  229. "${(activity?.statusBarHeightDp() ?: 0)}|${(activity?.naviBarHeightDp() ?: 0)}"
  230. )
  231. for (entry in cookies) {
  232. val cookieString: String = entry.key + "=" + entry.value + "; path=/"
  233. cookieManager.setCookie(url, cookieString)
  234. }
  235. // 同步 Cookie
  236. cookieManager.flush()
  237. }
  238. /**
  239. * 详见: https://mimolive.sg.larksuite.com/wiki/ZYliwjteIilW5XkmdhtlysIWgGg
  240. */
  241. override fun onPageStarted(url: String?, favicon: Bitmap?) {
  242. binding.progressIndicator.visibility = View.VISIBLE
  243. loadJSUrl("javascript:window.localStorage.setItem('GAMI-web_auth_token', '${AppPref.token}')")
  244. loadJSUrl("javascript:window.localStorage.setItem('GAMI-web_app_locale', '${languageManager?.getLanguageCode() ?: ""}')")
  245. loadJSUrl(
  246. "javascript:window.GAMI_BRIDGE={" +
  247. "\"requestHeader\":${requestHeader()}," +
  248. "\"safeArea\":\"${(activity?.statusBarHeightDp() ?: 0)}|${(activity?.naviBarHeightDp() ?: 0)}\"" +
  249. "}"
  250. )
  251. }
  252. fun requestHeader(): String {
  253. val currentTs = System.currentTimeMillis()
  254. val deviceName = getDeviceName()
  255. val headers = mutableMapOf<String, String>()
  256. headers[KEY_TOKEN] = AppPref.token
  257. headers[KEY_UDID] = App.instance.deviceIdService.getMemOldDeviceId()
  258. headers[KEY_PACKAGE_NAME] = PackageUtil.getPackageName()
  259. headers[KEY_DEVICE] = deviceName
  260. headers[KEY_PLATFORM] = "1"
  261. headers[KEY_CHANNEL] = "official"
  262. headers[KEY_API] = "1"
  263. headers[KEY_VERSION_CODE] = BuildConfig.VERSION_CODE.toString()
  264. headers[KEY_NETWORK] = "5g"
  265. headers[KEY_TIME] = currentTs.toString()
  266. return (toJsonErrorNull(headers) ?: "").also {
  267. Log.d(TAG_WEB_FRAGMENT, "requestHeader, header:$it")
  268. }
  269. }
  270. override fun onPageFinished(url: String?) {
  271. binding.progressIndicator.visibility = View.GONE
  272. WebLocalService.initWebVersion()
  273. if (transparentBgWhenPageFinish) {
  274. binding.root.background = null
  275. binding.webView.setBackgroundColor(0)
  276. binding.webView.background?.alpha = 0
  277. }
  278. }
  279. override fun onProgressChanged(progress: Int) {
  280. binding.progressIndicator.setProgressCompat(progress, true)
  281. }
  282. override fun onReceivedError(url: String?, errorCode: Int, description: String?) {
  283. errorComp?.showError("$errorCode:$description")
  284. }
  285. override fun onReceivedSslError(handler: SslErrorHandler?, error: SslError?) {
  286. errorComp?.showError()
  287. }
  288. override fun isHostActive(): Boolean {
  289. val hostActivity = activity ?: return false
  290. return !hostActivity.isDestroyed && isAdded
  291. }
  292. override fun setImmersionMode() {
  293. webFragmentCallback?.setImmersionMode()
  294. }
  295. override fun setNavigationBarMode(mode: NavigationBarMode) {
  296. webFragmentCallback?.setNavigationBarMode(mode)
  297. }
  298. override fun hideNavigationBar() {
  299. webFragmentCallback?.hideNavigationBar()
  300. }
  301. override fun <T> notifyNativeMessage(message: NativeMessage<T>) {
  302. if (!isViewBindingValid()) {
  303. return
  304. }
  305. binding.webView.notifyNativeMessage(message)
  306. }
  307. override fun getJsBridge(name: String): JSBridge? {
  308. if (!isViewBindingValid()) {
  309. return null
  310. }
  311. return binding.webView.getJSBridge(name)
  312. }
  313. override fun url(): String? {
  314. return url
  315. }
  316. override fun onPageLoadSuccess() {
  317. //loadJSUrl("javascript:window.localStorage.setItem('GAMI-web_auth_token', 'xxxx')")
  318. }
  319. private fun loadJSUrl(url: String) {
  320. loadUrl(url)
  321. }
  322. override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
  323. if (!isViewBindingValid()) {
  324. return false
  325. }
  326. if (keyCode == KeyEvent.KEYCODE_BACK) {
  327. if (onBackPressed()) {
  328. return true
  329. }
  330. }
  331. return false
  332. }
  333. override fun onBackPressed(): Boolean {
  334. if (binding.webView.canGoBack()) {
  335. binding.webView.goBack()
  336. return true
  337. }
  338. return false
  339. }
  340. override fun onDestroyView() {
  341. super.onDestroyView()
  342. binding.webView.onDestroy()
  343. }
  344. }