Bläddra i källkod

feat: H5链接测试

DoggyZhang 3 månader sedan
förälder
incheckning
30f020f2fe

+ 1 - 0
app/src/main/java/com/adealink/weparty/debug/DebugActivity.kt

@@ -69,6 +69,7 @@ class DebugActivity : BaseActivity(), OnReturnValue {
 
         //网络工具
         binding.webUrlEd.setText(DebugPrefs.webUrlEd)
+        binding.webUrlEd.setText("https://test-web.gami.vip/native/recharge?id=692c003e7e4a84752e314020&redirect=/native/callback")
         binding.webUrlBtn.setOnClickListener {
             saveWebUrlEd()
             goWebTest()

+ 17 - 1
app/src/main/java/com/adealink/weparty/module/wallet/Router.kt

@@ -20,8 +20,24 @@ interface Wallet {
     interface Detail {
         companion object {
             const val PATH = "${Common.PATH}/detail"
+        }
+    }
+
+    interface Coin {
+        companion object {
+            const val PATH = "${Common.PATH}/coin"
+        }
+    }
 
-            const val EXTRA_TAB = "extra_tab"
+    interface Diamond {
+        companion object {
+            const val PATH = "${Common.PATH}/diamond"
+        }
+    }
+
+    interface Bean {
+        companion object {
+            const val PATH = "${Common.PATH}/bean"
         }
     }
 

+ 10 - 1
app/src/main/java/com/adealink/weparty/module/wallet/data/WalletData.kt

@@ -3,5 +3,14 @@ package com.adealink.weparty.module.wallet.data
 enum class Currency(val type: Int) {
     COIN(0),
     DIAMOND(1),
-    BEAN(2),
+    BEAN(2);
+
+
+    companion object {
+        @JvmStatic
+        fun map(currency: Int?): Currency? {
+            currency ?: return null
+            return entries.find { it.type == currency }
+        }
+    }
 }

+ 51 - 1
app/src/main/java/com/adealink/weparty/webview/WebViewFragment.kt

@@ -8,22 +8,45 @@ import android.os.Bundle
 import android.view.KeyEvent
 import android.view.View
 import android.view.ViewOutlineProvider
+import android.webkit.JavascriptInterface
 import android.webkit.SslErrorHandler
 import androidx.core.net.toUri
 import androidx.fragment.app.FragmentManager
 import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.base.AppBase
+import com.adealink.frame.data.json.toJsonErrorNull
 import com.adealink.frame.ext.isViewBindingValid
+import com.adealink.frame.locale.language.languageManager
 import com.adealink.frame.log.Log
 import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.network.http.interceptor.ACCEPT_LANGUAGE
+import com.adealink.frame.network.http.interceptor.KEY_API
+import com.adealink.frame.network.http.interceptor.KEY_CHANNEL
+import com.adealink.frame.network.http.interceptor.KEY_CONTENT_TYPE
+import com.adealink.frame.network.http.interceptor.KEY_DEVICE
+import com.adealink.frame.network.http.interceptor.KEY_NETWORK
+import com.adealink.frame.network.http.interceptor.KEY_PACKAGE_NAME
+import com.adealink.frame.network.http.interceptor.KEY_PLATFORM
+import com.adealink.frame.network.http.interceptor.KEY_REQ_ID
+import com.adealink.frame.network.http.interceptor.KEY_TIME
+import com.adealink.frame.network.http.interceptor.KEY_TOKEN
+import com.adealink.frame.network.http.interceptor.KEY_UDID
+import com.adealink.frame.network.http.interceptor.KEY_VERSION_CODE
 import com.adealink.frame.router.Router
 import com.adealink.frame.router.annotation.BindExtra
 import com.adealink.frame.util.AppUtil
+import com.adealink.frame.util.PackageUtil
+import com.adealink.frame.util.getDeviceName
+import com.adealink.frame.util.md5
+import com.adealink.weparty.App
+import com.adealink.weparty.BuildConfig
 import com.adealink.weparty.R
 import com.adealink.weparty.commonui.BaseFragment
 import com.adealink.weparty.commonui.ext.dp
 import com.adealink.weparty.databinding.FragmentWebviewBinding
 import com.adealink.weparty.module.webview.Web
 import com.adealink.weparty.module.webview.Web.Common.Companion.EXTRA_TOP_RADIUS
+import com.adealink.weparty.storage.AppPref
 import com.adealink.weparty.webview.callback.IWebViewCallback
 import com.adealink.weparty.webview.callback.IWebViewFragmentCallback
 import com.adealink.weparty.webview.component.ErrorComp
@@ -38,6 +61,7 @@ import com.adealink.weparty.webview.jsnativemethod.addCommonMethod
 import com.adealink.weparty.webview.payermax.SpWebChromeClient
 import com.adealink.weparty.webview.payermax.SpWebViewClient
 import com.ushareit.easysdk.web.util.SPWebHelper
+import kotlin.random.Random
 
 class WebViewFragment : BaseFragment(R.layout.fragment_webview),
     IWebViewCallback {
@@ -160,7 +184,7 @@ class WebViewFragment : BaseFragment(R.layout.fragment_webview),
     }
 
     private fun notifyResumeMessage() {
-        notifyNativeMessage(NativeMessage<Any>(NativeMessageType.MSG_ON_RESUME.msgType))
+//        notifyNativeMessage(NativeMessage<Any>(NativeMessageType.MSG_ON_RESUME.msgType))
     }
 
     fun showUrl(url: String) {
@@ -229,6 +253,28 @@ class WebViewFragment : BaseFragment(R.layout.fragment_webview),
 
     override fun onPageStarted(url: String?, favicon: Bitmap?) {
         binding.progressIndicator.visibility = View.VISIBLE
+        loadJSUrl("javascript:window.localStorage.setItem('GAMI-web_auth_token', '${AppPref.token}')")
+        loadJSUrl("javascript:window.localStorage.setItem('GAMI-web_app_locale', '${languageManager?.getLanguageCode() ?: ""}')")
+        loadJSUrl("javascript:window.GAMI_BRIDGE={\"requestHeader\":${requestHeader()}}")
+    }
+
+    fun requestHeader(): String {
+        val currentTs = System.currentTimeMillis()
+        val deviceName = getDeviceName()
+        val headers = mutableMapOf<String, String>()
+        headers[KEY_TOKEN] = AppPref.token
+        headers[KEY_UDID] = App.instance.deviceIdService.getMemOldDeviceId()
+        headers[KEY_PACKAGE_NAME] = PackageUtil.getPackageName()
+        headers[KEY_DEVICE] = deviceName
+        headers[KEY_PLATFORM] = "1"
+        headers[KEY_CHANNEL] = "official"
+        headers[KEY_API] = "1"
+        headers[KEY_VERSION_CODE] = BuildConfig.VERSION_CODE.toString()
+        headers[KEY_NETWORK] = "5g"
+        headers[KEY_TIME] = currentTs.toString()
+        return (toJsonErrorNull(headers) ?: "").also {
+            Log.d("zhangfei", "requestHeader, header:$it")
+        }
     }
 
     override fun onPageFinished(url: String?) {
@@ -289,7 +335,11 @@ class WebViewFragment : BaseFragment(R.layout.fragment_webview),
     }
 
     override fun onPageLoadSuccess() {
+        //loadJSUrl("javascript:window.localStorage.setItem('GAMI-web_auth_token', 'xxxx')")
+    }
 
+    private fun loadJSUrl(url: String) {
+        loadUrl(url)
     }
 
     override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {

+ 1 - 2
app/src/main/java/com/adealink/weparty/webview/constant/Constant.kt

@@ -1,4 +1,3 @@
 package com.adealink.weparty.webview.constant
 
-const val NATIVE_JS_BRIDGE = "we_bridge"
-const val COCOS_GAME_JS_BRIDGE = "cocos_bridge"
+const val NATIVE_JS_BRIDGE = "GAMI_BRIDGE"

+ 73 - 15
app/src/main/java/com/adealink/weparty/webview/jsbridge/JSBridgeImpl.kt

@@ -6,8 +6,28 @@ import android.webkit.JavascriptInterface
 import androidx.annotation.UiThread
 import com.adealink.frame.data.json.froJsonErrorNull
 import com.adealink.frame.data.json.toJsonErrorNull
+import com.adealink.frame.locale.language.languageManager
 import com.adealink.frame.log.Log
+import com.adealink.frame.network.http.interceptor.ACCEPT_LANGUAGE
+import com.adealink.frame.network.http.interceptor.KEY_API
+import com.adealink.frame.network.http.interceptor.KEY_CHANNEL
+import com.adealink.frame.network.http.interceptor.KEY_CONTENT_TYPE
+import com.adealink.frame.network.http.interceptor.KEY_DEVICE
+import com.adealink.frame.network.http.interceptor.KEY_NETWORK
+import com.adealink.frame.network.http.interceptor.KEY_PACKAGE_NAME
+import com.adealink.frame.network.http.interceptor.KEY_PLATFORM
+import com.adealink.frame.network.http.interceptor.KEY_REQ_ID
+import com.adealink.frame.network.http.interceptor.KEY_TIME
+import com.adealink.frame.network.http.interceptor.KEY_TOKEN
+import com.adealink.frame.network.http.interceptor.KEY_UDID
+import com.adealink.frame.network.http.interceptor.KEY_VERSION_CODE
+import com.adealink.frame.util.PackageUtil
+import com.adealink.frame.util.getDeviceName
+import com.adealink.frame.util.md5
 import com.adealink.frame.util.runOnUiThread
+import com.adealink.weparty.App
+import com.adealink.weparty.BuildConfig
+import com.adealink.weparty.storage.AppPref
 import com.adealink.weparty.webview.IWebView
 import com.adealink.weparty.webview.constant.TAG_WEB_VIEW_JS_BRIDGE
 import com.adealink.weparty.webview.jsbridge.callback.JSBridgeCallback
@@ -16,6 +36,7 @@ import com.adealink.weparty.webview.jsbridge.data.JSResponse
 import com.adealink.weparty.webview.jsbridge.data.NativeMessage
 import com.adealink.weparty.webview.jsbridge.manager.JSBridgeManager
 import com.adealink.weparty.webview.jsbridge.method.JSNativeMethod
+import kotlin.random.Random
 
 
 class JSBridgeImpl(val webView: IWebView, private val interfaceName: String) : JSBridge {
@@ -41,11 +62,13 @@ class JSBridgeImpl(val webView: IWebView, private val interfaceName: String) : J
         methodMap.remove(methodName)
     }
 
-    override fun <T>notifyNativeMessage(message: NativeMessage<T>) {
+    override fun <T> notifyNativeMessage(message: NativeMessage<T>) {
         val messageStr = toJsonErrorNull(message)
         if (messageStr.isNullOrEmpty()) {
-            Log.e(TAG_WEB_VIEW_JS_BRIDGE,
-                "notifyNativeMessage, message is empty")
+            Log.e(
+                TAG_WEB_VIEW_JS_BRIDGE,
+                "notifyNativeMessage, message is empty"
+            )
             return
         }
         val script = "javascript:window.notifyMessage('$messageStr')"
@@ -63,6 +86,29 @@ class JSBridgeImpl(val webView: IWebView, private val interfaceName: String) : J
         runOnUiThread { handleJSMessage(json) }
     }
 
+    @JavascriptInterface
+    fun requestHeader(): String {
+        val currentTs = System.currentTimeMillis()
+        val deviceName = getDeviceName()
+        val headers = mutableMapOf<String, String>()
+        headers[KEY_TOKEN] = AppPref.token
+        headers[KEY_REQ_ID] = "$currentTs/${Random(100).nextInt()}/${deviceName}".md5()
+        headers[KEY_UDID] = App.instance.deviceIdService.getMemOldDeviceId()
+        headers[KEY_PACKAGE_NAME] = PackageUtil.getPackageName()
+        headers[KEY_DEVICE] = deviceName
+        headers[KEY_PLATFORM] = "1"
+        headers[KEY_CHANNEL] = "official"
+        headers[KEY_API] = "1"
+        headers[KEY_VERSION_CODE] = BuildConfig.VERSION_CODE.toString()
+        headers[KEY_NETWORK] = "5g"
+        headers[KEY_TIME] = currentTs.toString()
+        headers[KEY_CONTENT_TYPE] = "application/json"
+        headers[ACCEPT_LANGUAGE] = languageManager?.getLanguageCode() ?: ""
+        return (toJsonErrorNull(headers) ?: "").also {
+            Log.d("zhangfei", "requestHeader, header:$it")
+        }
+    }
+
     @UiThread
     private fun handleJSMessage(json: String?) {
         Log.d(TAG_WEB_VIEW_JS_BRIDGE, "handleJSMessage, json:${json}")
@@ -139,15 +185,19 @@ class JSBridgeImpl(val webView: IWebView, private val interfaceName: String) : J
 
         private fun sendResponseToJS(response: JSResponse<Any>) {
             if (TextUtils.isEmpty(callbackId)) {
-                Log.e(TAG_WEB_VIEW_JS_BRIDGE,
-                    "sendResponseToJS, methodName:${methodName}, response:${response}, callbackId is empty")
+                Log.e(
+                    TAG_WEB_VIEW_JS_BRIDGE,
+                    "sendResponseToJS, methodName:${methodName}, response:${response}, callbackId is empty"
+                )
                 return
             }
 
             val responseStr = toJsonErrorNull(response)
             if (responseStr.isNullOrEmpty()) {
-                Log.e(TAG_WEB_VIEW_JS_BRIDGE,
-                    "sendResponseToJS, methodName:${methodName}, response:${response}, to json error")
+                Log.e(
+                    TAG_WEB_VIEW_JS_BRIDGE,
+                    "sendResponseToJS, methodName:${methodName}, response:${response}, to json error"
+                )
                 return
             }
 
@@ -163,14 +213,18 @@ class JSBridgeImpl(val webView: IWebView, private val interfaceName: String) : J
         }
 
         override fun resolve(data: Any) {
-            Log.d(TAG_WEB_VIEW_JS_BRIDGE,
-                "resolve, methodName:${methodName}, callbackId:${callbackId}, data:${data}")
+            Log.d(
+                TAG_WEB_VIEW_JS_BRIDGE,
+                "resolve, methodName:${methodName}, callbackId:${callbackId}, data:${data}"
+            )
             sendResponseToJS(JSResponse(callbackId = this@CallbackBridge.callbackId, data = data))
         }
 
         override fun reject(error: JSResponse.JSError) {
-            Log.e(TAG_WEB_VIEW_JS_BRIDGE,
-                "reject, methodName:${methodName}, callbackId:${callbackId}, error:${error.message}")
+            Log.e(
+                TAG_WEB_VIEW_JS_BRIDGE,
+                "reject, methodName:${methodName}, callbackId:${callbackId}, error:${error.message}"
+            )
             sendResponseToJS(JSResponse(error.code, error.message, this@CallbackBridge.callbackId))
         }
 
@@ -184,17 +238,21 @@ class JSBridgeImpl(val webView: IWebView, private val interfaceName: String) : J
 
     }
 
-    inner class BridgeSupportJSNativeMethod: JSNativeMethod<Map<String,List<String>>, Map<String,Boolean>> {
+    inner class BridgeSupportJSNativeMethod :
+        JSNativeMethod<Map<String, List<String>>, Map<String, Boolean>> {
         override val methodName: String = "isBridgeSupport"
 
-        override fun handleMethodCall(data: Map<String,List<String>>, callback: JSBridgeCallback<Map<String,Boolean>>?) {
+        override fun handleMethodCall(
+            data: Map<String, List<String>>,
+            callback: JSBridgeCallback<Map<String, Boolean>>?
+        ) {
             val jsNativeMethods = data["data"]
 
-            if(jsNativeMethods == null) {
+            if (jsNativeMethods == null) {
                 callback?.reject(JSResponse.JSError.CLIENT_HANDLE_REQUEST_DATA_TYPE_ERROR)
                 return
             }
-            val result =  jsNativeMethods.associateWith { key -> methodMap.containsKey(key) }
+            val result = jsNativeMethods.associateWith { key -> methodMap.containsKey(key) }
             callback?.resolve(result)
         }
     }

+ 13 - 0
module/setting/src/main/java/com/adealink/weparty/setting/SettingActivity.kt

@@ -1,5 +1,6 @@
 package com.adealink.weparty.setting
 
+import android.view.View
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.updateLayoutParams
 import com.adealink.frame.aab.util.getCompatDimension
@@ -11,8 +12,11 @@ import com.adealink.frame.router.annotation.RouterUri
 import com.adealink.frame.util.onClick
 import com.adealink.frame.util.statusBarHeight
 import com.adealink.weparty.commonui.BaseActivity
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.toast.util.showToast
 import com.adealink.weparty.commonui.widget.CommonDialog
+import com.adealink.weparty.debug.Debug
 import com.adealink.weparty.module.account.AccountModule
 import com.adealink.weparty.module.setting.Setting
 import com.adealink.weparty.setting.databinding.ActivitySettingBinding
@@ -55,6 +59,15 @@ class SettingActivity : BaseActivity() {
         binding.vLogout.onClick {
             clickLogout()
         }
+
+        if (AppBase.isRelease) {
+            binding.vDebug.gone()
+        } else {
+            binding.vDebug.show()
+            binding.vDebug.onClick {
+                Router.build(this, Debug.Debug.PATH).start()
+            }
+        }
     }
 
     private fun clickLanguage() {

+ 47 - 0
module/setting/src/main/res/layout/activity_setting.xml

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
@@ -211,6 +212,52 @@
             </androidx.constraintlayout.widget.ConstraintLayout>
 
 
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/v_debug"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:background="@drawable/common_white_radius_16_bg"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="4dp"
+                android:visibility="gone"
+                tools:visibility="visible">
+
+                <!-- Debug -->
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingVertical="12dp"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent">
+
+                    <androidx.appcompat.widget.AppCompatTextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:includeFontPadding="false"
+                        android:text="Debug测试"
+                        android:textColor="@color/color_FF1D2129"
+                        android:textSize="12sp"
+                        app:layout_constraintBottom_toBottomOf="parent"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toTopOf="parent"
+                        tools:ignore="HardcodedText" />
+
+                    <androidx.appcompat.widget.AppCompatImageView
+                        android:layout_width="16dp"
+                        android:layout_height="16dp"
+                        app:layout_constraintBottom_toBottomOf="parent"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintTop_toTopOf="parent"
+                        app:srcCompat="@drawable/common_go_ic" />
+
+                </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+
         </androidx.appcompat.widget.LinearLayoutCompat>
     </androidx.core.widget.NestedScrollView>
 

+ 58 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/coin/CoinActivity.kt

@@ -0,0 +1,58 @@
+package com.adealink.weparty.wallet.coin
+
+import androidx.appcompat.widget.LinearLayoutCompat
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.updateLayoutParams
+import com.adealink.frame.aab.util.getCompatDimension
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.Router
+import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.frame.util.onClick
+import com.adealink.frame.util.statusBarHeight
+import com.adealink.weparty.commonui.BaseActivity
+import com.adealink.weparty.module.wallet.Wallet
+import com.adealink.weparty.wallet.databinding.ActivityCoinBinding
+import com.adealink.weparty.R as APP_R
+
+@RouterUri(
+        path = [Wallet.Coin.PATH],
+        desc = "金币首页"
+        )
+        class CoinActivity : BaseActivity() {
+
+            private val binding by viewBinding(ActivityCoinBinding::inflate)
+
+            override fun onBeforeCreate() {
+        super.onBeforeCreate()
+        Router.bind(this)
+    }
+
+    override fun initViews() {
+        super.initViews()
+        setContentView(binding.root)
+        val statusBarHeight = this@CoinActivity.statusBarHeight()
+        binding.topBar.root.updateLayoutParams<ConstraintLayout.LayoutParams> {
+            height = statusBarHeight
+        }
+        binding.vCoinCard.root.updateLayoutParams<LinearLayoutCompat.LayoutParams> {
+            height = statusBarHeight + getCompatDimension(APP_R.dimen.common_top_bar_height).toInt()
+        }
+        binding.topBar.btnBack.onClick {
+            finish()
+        }
+    }
+
+    override fun initComponents() {
+        super.initComponents()
+
+    }
+
+    override fun onResume() {
+        super.onResume()
+    }
+
+    private fun goDetail() {
+        Router.build(this, Wallet.Detail.PATH).start()
+    }
+
+}

+ 35 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/coin/TopUpFragment.kt

@@ -0,0 +1,35 @@
+package com.adealink.weparty.wallet.coin
+
+import android.os.Bundle
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.module.wallet.data.Currency
+import com.adealink.weparty.wallet.R
+
+class TopUpFragment : BaseFragment(R.layout.fragment_wallet_top_up) {
+
+    companion object {
+
+        private const val EXTRA_CURRENCY = "extra_currency"
+
+        @JvmStatic
+        fun newInstance(currency: Currency): TopUpFragment {
+            return TopUpFragment().apply {
+                arguments = Bundle().apply {
+                    putInt(EXTRA_CURRENCY, currency.type)
+                }
+            }
+        }
+    }
+
+    private var currency: Currency = Currency.COIN
+
+    override fun initViews() {
+        super.initViews()
+        currency =
+            Currency.map(arguments?.getInt(EXTRA_CURRENCY, Currency.COIN.type)) ?: Currency.COIN
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+    }
+}

+ 11 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/topup/TopUpActivity.kt

@@ -0,0 +1,11 @@
+package com.adealink.weparty.wallet.topup
+
+import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.weparty.module.wallet.Wallet
+
+@RouterUri(
+    path = [Wallet.Wallet.PATH],
+    desc = "钱包首页"
+)
+class TopUpActivity {
+}

BIN
module/wallet/src/main/res/drawable-xhdpi/wallet_coin_card_bg.png


+ 53 - 0
module/wallet/src/main/res/layout/activity_coin.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/color_FFF1F2F5">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/appBarLayout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@color/transparent"
+        app:elevation="0dp">
+
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            android:id="@+id/collapsing_toolbar_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed">
+
+            <androidx.appcompat.widget.LinearLayoutCompat
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <include
+                    android:id="@+id/v_coin_card"
+                    layout="@layout/layout_wallet_coin_header"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginHorizontal="16dp"
+                    app:layout_scrollFlags="scroll" />
+            </androidx.appcompat.widget.LinearLayoutCompat>
+
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/fl_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+    <include
+        android:id="@+id/top_bar"
+        layout="@layout/layout_wallet_top_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/common_top_bar_height"
+        android:paddingHorizontal="16dp" />
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 47 - 0
module/wallet/src/main/res/layout/fragment_wallet_top_up.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.core.widget.NestedScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/common_white_radius_16_bg"
+        android:paddingHorizontal="16dp"
+        android:paddingTop="20dp">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rv_list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <com.adealink.weparty.commonui.widget.CommonButton
+            android:id="@+id/btn_topup"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/common_button_height"
+            android:layout_marginTop="20dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/rv_list" />
+
+<!--        <androidx.appcompat.widget.AppCompatTextView-->
+<!--            android:id="@+id/tv_rule"-->
+<!--            android:layout_width="wrap_content"-->
+<!--            android:layout_height="wrap_content"-->
+<!--            android:ellipsize="end"-->
+<!--            android:fontFamily="@font/poppins_semibold"-->
+<!--            android:includeFontPadding="false"-->
+<!--            android:singleLine="true"-->
+<!--            android:textColor="@color/color_FF1D2129"-->
+<!--            app:layout_constraintTop_toBottomOf=""-->
+<!--            android:textSize="12sp"-->
+<!--            app:layout_constrainedWidth="true" />-->
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.core.widget.NestedScrollView>

+ 100 - 0
module/wallet/src/main/res/layout/layout_wallet_coin_header.xml

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <!--基础信息 -->
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_user_info"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:clipChildren="true"
+        app:layout_constraintDimensionRatio="345:113"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintDimensionRatio="345:144"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/wallet_coin_card_bg" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_balance_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="45dp"
+            android:ellipsize="end"
+            android:fontFamily="@font/poppins_semibold"
+            android:includeFontPadding="false"
+            android:singleLine="true"
+            android:text="@string/wallet_your_balance"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="14sp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toTopOf="@id/iv_icon"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.3"
+            app:layout_constraintVertical_chainStyle="packed" />
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_icon"
+            android:layout_width="19dp"
+            android:layout_height="19dp"
+            android:layout_marginTop="14dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="@id/tv_balance_title"
+            app:layout_constraintTop_toBottomOf="@id/tv_balance_title"
+            app:srcCompat="@drawable/common_wallet_coin_32_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_balance"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="2dp"
+            android:ellipsize="end"
+            android:fontFamily="@font/poppins_semibold"
+            android:includeFontPadding="false"
+            android:singleLine="true"
+            android:text="@string/wallet_your_balance"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="24sp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="@id/iv_icon"
+            app:layout_constraintStart_toEndOf="@id/iv_icon"
+            app:layout_constraintTop_toTopOf="@id/iv_icon"
+            app:layout_constraintVertical_chainStyle="packed" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/btn_convert"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="2dp"
+            android:ellipsize="end"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:singleLine="true"
+            android:text="@string/wallet_coin_convert_to_diamond"
+            android:textColor="@color/white"
+            android:textSize="14sp"
+            app:autoSizeMaxTextSize="14sp"
+            app:autoSizeMinTextSize="10sp"
+            app:autoSizeTextType="uniform"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="220:36"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHeight_percent="0.27"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 32 - 0
module/wallet/src/main/res/layout/layout_wallet_top_bar.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/common_top_bar_height">
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/btn_back"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:rotationY="@integer/locale_mirror_flip"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/commonui_back_black_48_ic" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/poppins_semibold"
+        android:includeFontPadding="false"
+        android:text="@string/wallet_title"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="16sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 3 - 0
module/wallet/src/main/res/values/strings.xml

@@ -10,4 +10,7 @@
     <string name="wallet_go_top_up"><![CDATA[Top-up >]]></string>
     <string name="wallet_go_detail"><![CDATA[Detail >]]></string>
     <string name="wallet_detail_title">Details</string>
+    <string name="wallet_your_balance">您的余额</string>
+    <string name="wallet_coin_convert_to_diamond"><![CDATA[Convert to Diamonds >]]></string>
+    <string name="wallet_topup_rule_title"></string>
 </resources>