Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release-android-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
#创建签名需要的信息
- name: Create secrets.properties
run: |
echo "storeFile=sign_keystore.jks" > key.properties
echo "storeFile=sign_keystore.jks" >> key.properties
echo "storePassword=${{ secrets.APK_SIGN_KEYSTORE_PASSWORD }}" >> key.properties
echo "keyAlias=${{ secrets.APK_SIGN_ALIAS }}" >> key.properties
echo "keyPassword=${{ secrets.APK_SIGN_PASSWORD }}" >> key.properties
Expand Down
4 changes: 2 additions & 2 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ android {
applicationId = "com.smjcco.wxpusher"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 10751
versionName = "1.7.51"
versionCode = 10811
versionName = "1.8.11"
//指定产物名称
setProperty("archivesBaseName", "wxpusher-app-v$versionName")

Expand Down
1 change: 1 addition & 0 deletions androidApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<!-- 扫码相册选图改用系统 Photo Picker,无需任何存储/媒体读取权限 -->
<!-- 移除腾讯 Shiply 升级 SDK 带入的 WRITE 权限;移除后 merger 也不会再隐式添加 READ_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="remove" />

<!-- 声明需要相机特征 -->
<uses-feature
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ import com.smjcco.wxpusher.base.common.WxpLogUtils
import com.smjcco.wxpusher.base.common.WxpSaveService
import com.smjcco.wxpusher.base.common.WxpToastUtils
import com.smjcco.wxpusher.base.common.flush
import com.smjcco.wxpusher.biz.tab.WxpTabConfig
import com.smjcco.wxpusher.biz.tab.WxpTabConfigStore
import com.smjcco.wxpusher.biz.version.WxpVersionCheckManager
import com.smjcco.wxpusher.common.WxpSaveKey
import com.smjcco.wxpusher.page.main.fragment.ITabMenuProvider
import com.smjcco.wxpusher.page.main.fragment.MessageListFragment
import com.smjcco.wxpusher.page.main.fragment.ProfileFragment
import com.smjcco.wxpusher.page.main.fragment.WxpExtFuncFragment
import com.smjcco.wxpusher.page.main.fragment.WxpProviderListFragment
import com.smjcco.wxpusher.push.PushManager
import com.smjcco.wxpusher.push.ws.keepalive.KeepWsAliveServiceStarter
Expand All @@ -48,6 +51,16 @@ class WxpMainActivity : WxpBaseActivity(), CurrentTabProvider {
private var currentMenuProvider: ITabMenuProvider? = null
private var currentMenu: Menu? = null

//底部 tab 的枚举与当前有序列表(按配置动态构建)
private enum class MainTab { MESSAGE_LIST, MARKET, EXT_FUNC, PROFILE }

private var tabs: List<MainTab> = emptyList()
//已应用的 tab 配置快照,用于返回主界面时 diff,仅变化才重建
private var appliedTabConfig: WxpTabConfig? = null
private var tabMediator: TabLayoutMediator? = null
//KV 变更监听 id(保存即广播机制)
private var kvListenerId: Int = -1

private var permissionRequester: PermissionRequester? = null

companion object {
Expand All @@ -67,8 +80,11 @@ class WxpMainActivity : WxpBaseActivity(), CurrentTabProvider {
return
}
title = "消息列表"
// 设置ViewPager和TabLayout
setupViewPager()
// 设置ViewPager和TabLayout(按 tab 显隐配置动态构建)
setupTabListeners()
rebuildTabs(WxpTabConfigStore.read())
// 注册 KV 变更监听(保存即广播),tab 配置变化即重建
setupKvListener()

//检查是否有权限
setUpPermissionRequester()
Expand Down Expand Up @@ -165,38 +181,41 @@ class WxpMainActivity : WxpBaseActivity(), CurrentTabProvider {
}


private fun setupViewPager() {
pagerAdapter = MainPagerAdapter(this)
viewPager.adapter = pagerAdapter
viewPager.isUserInputEnabled = false

// 连接TabLayout和ViewPager2
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
when (position) {
0 -> {
tab.text = "消息列表"
tab.setIcon(R.drawable.ic_paperplane)
}
//根据配置构建有序 tab 列表:消息列表(必) → 消息市场(可配) → 扩展功能(可配) → 我的(必)
private fun buildTabs(config: WxpTabConfig): List<MainTab> {
val list = mutableListOf(MainTab.MESSAGE_LIST)
if (config.market) {
list.add(MainTab.MARKET)
}
if (config.extFunc) {
list.add(MainTab.EXT_FUNC)
}
list.add(MainTab.PROFILE)
return list
}

1 -> {
tab.text = "消息市场"
tab.setIcon(R.drawable.ic_cloud)
}
//tab 的标题与图标
private fun tabTitle(tab: MainTab): String = when (tab) {
MainTab.MESSAGE_LIST -> "消息列表"
MainTab.MARKET -> "消息市场"
MainTab.EXT_FUNC -> "扩展功能"
MainTab.PROFILE -> "我的"
}

2 -> {
tab.text = "我的"
tab.setIcon(R.drawable.ic_person)
}
}
}.attach()
private fun tabIcon(tab: MainTab): Int = when (tab) {
MainTab.MESSAGE_LIST -> R.drawable.ic_paperplane
MainTab.MARKET -> R.drawable.ic_cloud
MainTab.EXT_FUNC -> R.drawable.ic_ext_func
MainTab.PROFILE -> R.drawable.ic_person
}

// 监听Tab切换,更新Toolbar标题和菜单
tabLayout.addOnTabSelectedListener(object :
TabLayout.OnTabSelectedListener {
//监听只注册一次(tab 重建时不重复注册)
private fun setupTabListeners() {
viewPager.isUserInputEnabled = false
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
tab?.let {
title = it.text
// 更新菜单
updateMenuForCurrentTab(tab.position)
}
}
Expand All @@ -210,20 +229,34 @@ class WxpMainActivity : WxpBaseActivity(), CurrentTabProvider {
}
})

// 监听ViewPager页面切换(处理手势滑动切换)
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
// 延迟更新菜单,确保Fragment已经加载完成
viewPager.post {
updateMenuForCurrentTab(position)
}
}
})
}

// 延迟初始化第一个tab的菜单,确保Fragment已创建
//(重新)按配置构建 tab 列表与 ViewPager
private fun rebuildTabs(config: WxpTabConfig) {
appliedTabConfig = config
tabs = buildTabs(config)
pagerAdapter = MainPagerAdapter(this)
viewPager.adapter = pagerAdapter

tabMediator?.detach()
tabMediator = TabLayoutMediator(tabLayout, viewPager) { tab, position ->
val mainTab = tabs[position]
tab.text = tabTitle(mainTab)
tab.setIcon(tabIcon(mainTab))
}
tabMediator?.attach()

// 延迟初始化当前tab的菜单,确保Fragment已创建
viewPager.post {
updateMenuForCurrentTab(0)
updateMenuForCurrentTab(viewPager.currentItem)
}
}

Expand Down Expand Up @@ -277,11 +310,45 @@ class WxpMainActivity : WxpBaseActivity(), CurrentTabProvider {
WxpVersionCheckManager.onAppForeground()
}

//注册 KV 变更监听(保存即广播):tab_config 变化即重建,未变不动。
//回调已由共享层 runAtMainSuspend 切到主线程。
private fun setupKvListener() {
kvListenerId = WxpSaveService.addListener { key ->
if (key == WxpTabConfigStore.TAB_CONFIG_KEY) {
refreshTabsIfConfigChanged()
}
}
}

private fun refreshTabsIfConfigChanged() {
//未完成初始化(如未登录直接 finish)时跳过
val applied = appliedTabConfig ?: return
val latest = WxpTabConfigStore.read()
if (applied == latest) {
return
}
val currentTab = tabs.getOrNull(viewPager.currentItem)
rebuildTabs(latest)
//恢复到原来所在 tab;若该 tab 已被隐藏,回落到消息列表
val targetIndex = currentTab?.let { tabs.indexOf(it) }?.takeIf { it >= 0 } ?: 0
viewPager.setCurrentItem(targetIndex, false)
title = tabTitle(tabs[targetIndex])
viewPager.post { updateMenuForCurrentTab(targetIndex) }
}

override fun onPause() {
super.onPause()
WxpLogUtils.flush()
}

override fun onDestroy() {
if (kvListenerId >= 0) {
WxpSaveService.removeListener(kvListenerId)
kvListenerId = -1
}
super.onDestroy()
}

/**
* ViewPager2适配器
* 让FragmentStateAdapter来管理Fragment的创建和状态保存/恢复
Expand All @@ -291,14 +358,14 @@ class WxpMainActivity : WxpBaseActivity(), CurrentTabProvider {
) :
FragmentStateAdapter(activity) {

override fun getItemCount(): Int = 3
override fun getItemCount(): Int = tabs.size

override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> MessageListFragment()
1 -> WxpProviderListFragment()
2 -> ProfileFragment()
else -> throw IllegalArgumentException("Invalid position: $position")
return when (tabs[position]) {
MainTab.MESSAGE_LIST -> MessageListFragment()
MainTab.MARKET -> WxpProviderListFragment()
MainTab.EXT_FUNC -> WxpExtFuncFragment()
MainTab.PROFILE -> ProfileFragment()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ class ProfileFragment : WxpBaseFragment() {
ProfileSection(
title = "通用",
items = listOf(
ProfileItem(
title = "底部标签设置",
subtitle = "自定义底部导航标签的显示",
hasArrow = true
) {
WxpJumpPageUtils.jumpToWebUrl(
"${WxpConfig.appFeUrl}/app/#/tab-setting",
activity
)
},
ProfileItem(
title = "反馈建议",
subtitle = "欢迎你指导我们进步",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.smjcco.wxpusher.page.main.fragment

import android.os.Bundle
import android.view.View
import android.widget.ImageButton
import com.smjcco.wxpusher.R
import com.smjcco.wxpusher.WxpConfig
import com.smjcco.wxpusher.page.web.WxpWebViewFragment

/**
* 扩展功能 tab —— 原生 WebView 容器加载 app-fe 的九宫格入口页。
* 与消息市场 tab(WxpProviderListFragment)结构一致:隐藏返回按钮、关闭按钮改为回到本页。
*/
class WxpExtFuncFragment : WxpWebViewFragment() {

private val pageUrl: String
get() = "${WxpConfig.appFeUrl}/app/#/ext-func"

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loadWebContent(pageUrl)
}

override fun setupUI(view: View) {
super.setupUI(view)
//默认隐藏底部操作栏:布局默认 VISIBLE,避免打开瞬间闪一下再隐藏(UI 线程调用会同步生效)
setBottomBarVisibleOverride(false)
//隐藏左上角的返回按钮
getActivityHost()?.supportActionBar?.setDisplayHomeAsUpEnabled(false)
//覆盖webview默认的关闭按钮,改成home按钮
val closeButton: ImageButton = view.findViewById(R.id.closeButton)
closeButton.setImageResource(R.drawable.ic_home)
}

//覆盖关闭按钮为回到扩展功能首页
override fun onCloseButtonClicked() {
loadWebContent(pageUrl)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class WxpProviderListFragment : WxpWebViewFragment(), IWxpProviderListView {

override fun setupUI(view: View) {
super.setupUI(view)
//默认隐藏底部操作栏:布局默认 VISIBLE,避免打开瞬间闪一下再隐藏(UI 线程调用会同步生效)
setBottomBarVisibleOverride(false)
//隐藏左上角的返回按钮
getActivityHost()?.supportActionBar?.setDisplayHomeAsUpEnabled(false)
//覆盖webview默认的关闭按钮,改成home按钮
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package com.smjcco.wxpusher.page.web.bridge

import com.smjcco.wxpusher.base.common.WxpLogUtils
import com.smjcco.wxpusher.page.web.bridge.handlers.CloseWebPageBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.GetByKeyBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.GetLoginInfoBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.OpenSystemBrowserBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.OpenUrlBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.PayRequestBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.SetKeyValueBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.SetWebBottomBarBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.SetWebOptionMenuBridgeHandler
import com.smjcco.wxpusher.page.web.bridge.handlers.ShowToastBridgeHandler
Expand Down Expand Up @@ -40,6 +42,8 @@ class WxpWebBridgeManager(
registerHandler("setWebOptionMenu", requiresWhitelist = true, handler = SetWebOptionMenuBridgeHandler)
registerHandler("setWebBottomBar", requiresWhitelist = true, handler = SetWebBottomBarBridgeHandler)
registerHandler("closeWebPage", requiresWhitelist = true, handler = CloseWebPageBridgeHandler)
registerHandler("getByKey", requiresWhitelist = true, handler = GetByKeyBridgeHandler)
registerHandler("setKeyValue", requiresWhitelist = true, handler = SetKeyValueBridgeHandler)
}

fun onMessage(messageJson: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.smjcco.wxpusher.page.web.bridge.handlers

import com.smjcco.wxpusher.base.common.WxpSaveService
import com.smjcco.wxpusher.page.web.bridge.BridgeActionHandler
import com.smjcco.wxpusher.page.web.bridge.BridgeContext
import com.smjcco.wxpusher.page.web.bridge.BridgeRequest
import com.smjcco.wxpusher.page.web.bridge.WxpBridgeEmitter

/**
* 通用存储读取。直接读 WxpSaveService(原生与 H5 同一 key 空间,无前缀),空值视为不存在返回空 data。
*/
object GetByKeyBridgeHandler : BridgeActionHandler {
override fun handle(request: BridgeRequest, context: BridgeContext, emitter: WxpBridgeEmitter) {
val key = request.data["key"] as? String
if (key.isNullOrBlank()) {
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = false,
error = "key is empty"
)
return
}
val value = WxpSaveService.get(key, "")
val data: Map<String, Any?> = if (value.isEmpty()) emptyMap() else mapOf("value" to value)
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = true,
data = data
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.smjcco.wxpusher.page.web.bridge.handlers

import com.smjcco.wxpusher.base.common.WxpSaveService
import com.smjcco.wxpusher.page.web.bridge.BridgeActionHandler
import com.smjcco.wxpusher.page.web.bridge.BridgeContext
import com.smjcco.wxpusher.page.web.bridge.BridgeRequest
import com.smjcco.wxpusher.page.web.bridge.WxpBridgeEmitter

/**
* 通用存储写入。直接写 WxpSaveService(原生与 H5 同一 key 空间,无前缀)。
* 安全由桥的 requiresWhitelist 保证(仅白名单域可调)。
*/
object SetKeyValueBridgeHandler : BridgeActionHandler {
override fun handle(request: BridgeRequest, context: BridgeContext, emitter: WxpBridgeEmitter) {
val key = request.data["key"] as? String
if (key.isNullOrBlank()) {
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = false,
error = "key is empty"
)
return
}
val value = request.data["value"] as? String ?: ""
WxpSaveService.set(key, value)
emitter.sendBridgeCallback(
callbackId = request.callbackId,
success = true
)
}
}
Loading
Loading