Skip to content

Commit af3cc3b

Browse files
committed
feat: implement mini program menu and capsule button for enhanced navigation; adjust layout calculations for better positioning (#207)
1 parent 951c1be commit af3cc3b

12 files changed

Lines changed: 1178 additions & 130 deletions

File tree

android/dimina/src/main/kotlin/com/didi/dimina/common/Utils.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,8 @@ object Utils {
282282
val systemInfo = getMiniProgramSystemInfo(currentActivity)
283283
val width = 87
284284
val height = 32
285-
val top = systemInfo.getInt("statusBarHeight")
285+
val navigationBarContentHeight = 64
286+
val top = systemInfo.getInt("statusBarHeight") + (navigationBarContentHeight - height) / 2
286287
val right = systemInfo.getInt("windowWidth") - 10
287288
val left = right - width
288289
val bottom = top + height

android/dimina/src/main/kotlin/com/didi/dimina/ui/container/DiminaActivity.kt

Lines changed: 389 additions & 79 deletions
Large diffs are not rendered by default.

harmony/dimina/src/main/ets/Bridges/DMPContainerBridgesModule+Menu.ets

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export class DMPContainerBridgesModuleMenu extends DMPContainerBridgesModule {
1717

1818
const width = 87;
1919
const height = 32;
20-
const top = whData.get('statusBarHeight') as number;
20+
const navigationBarContentHeight = 55;
21+
const top = (whData.get('statusBarHeight') as number) + Math.round((navigationBarContentHeight - height) / 2);
2122
const right = whData.get('windowWidth') - 10;
2223
const left = right - width;
2324
const bottom = top + height;
@@ -29,10 +30,10 @@ export class DMPContainerBridgesModuleMenu extends DMPContainerBridgesModule {
2930
height: height,
3031
bottom: bottom,
3132
right: right,
33+
x: left,
34+
y: top,
3235
})
3336
return menuButtonBoundingClientRect
3437
}
3538
}
3639

37-
38-

harmony/dimina/src/main/ets/Bundle/Loader/DMPReleaseBundleLoader.ets

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ export class DMPReleaseBundleLoader implements DMPBundleLoader {
3434
this.app = app;
3535
}
3636

37+
private isDebugMode(): boolean {
38+
return this.app.appConfig.isDebugMode || DMPContextUtils.debugMode;
39+
}
40+
3741
async install(installConfig: DMPBundleInstallConfig, launchBundleReady: (config: DMPBundleLoadInfo) => void,
3842
loadComplete?: (config: DMPBundleLoadInfo) => void,
3943
loadError?: (code: number, errMsg: string) => void): Promise<DMPBundleLoadInfo> {
@@ -99,8 +103,8 @@ export class DMPReleaseBundleLoader implements DMPBundleLoader {
99103
//加载底包
100104
async loadLocalPackage(bundleLoadInfo: DMPBundleLoadInfo) {
101105
try {
102-
if (bundleLoadInfo.isAppUpdate() || DMPContextUtils.debugMode) {
103-
DMPLogger.i(Tags.LOAD_LOCAL_BUNDLE, '应用有更新');
106+
if (bundleLoadInfo.isAppUpdate() || this.isDebugMode()) {
107+
DMPLogger.i(Tags.LOAD_LOCAL_BUNDLE, '应用有更新或处于调试模式');
104108
//加载本地配置
105109
this.loadLocalBundleConfig(bundleLoadInfo);
106110
//安装jsApp本地包
@@ -166,7 +170,8 @@ export class DMPReleaseBundleLoader implements DMPBundleLoader {
166170
if (bundleLoadInfo.localJSSdkBundleConfig != null) {
167171
let cacheJSSdkConfig = this.fileManager.loadJSSdkConfig();
168172
bundleLoadInfo.cacheJSSdkBundleConfig = DMPJSSdkBundleConfig.fromJson(cacheJSSdkConfig);
169-
if (bundleLoadInfo.cacheJSSdkBundleConfig == null
173+
if (this.isDebugMode()
174+
|| bundleLoadInfo.cacheJSSdkBundleConfig == null
170175
|| bundleLoadInfo.localJSSdkBundleConfig.versionCode > bundleLoadInfo.cacheJSSdkBundleConfig.versionCode) {
171176
await this.fileManager.copyJSSDKAndUnZip(`${bundleLoadInfo.localJSSdkBundleConfig.versionCode}`);
172177
DMPLogger.i(Tags.BUNDLE, '复制jsSdk完成');
@@ -182,7 +187,8 @@ export class DMPReleaseBundleLoader implements DMPBundleLoader {
182187
if (bundleLoadInfo.localJSAppBundleConfig != null) {
183188
let cacheJSAppConfig = this.fileManager.loadJSAppConfig(bundleLoadInfo.appId);
184189
bundleLoadInfo.cacheJSAppBundleConfig = DMJSAppBundleConfig.fromJson(cacheJSAppConfig);
185-
if (bundleLoadInfo.cacheJSAppBundleConfig == null
190+
if (this.isDebugMode()
191+
|| bundleLoadInfo.cacheJSAppBundleConfig == null
186192
|| bundleLoadInfo.localJSAppBundleConfig.versionCode > bundleLoadInfo.cacheJSAppBundleConfig.versionCode) {
187193
await this.fileManager.copyJSAppAndUnZip(this.installConfig!.appId,
188194
`${bundleLoadInfo.localJSAppBundleConfig.versionCode}`);
@@ -333,4 +339,3 @@ export class DMPReleaseBundleLoader implements DMPBundleLoader {
333339
}
334340
}
335341

336-
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { DMPAppManager } from '../DApp/DMPAppManager'
2+
import { DMPLogger } from '../EventTrack/DMPLogger'
3+
import { Tags } from '../EventTrack/Tags'
4+
5+
@Component
6+
export struct DMPCapsuleButton {
7+
@Prop appIndex: number
8+
@Prop webViewId: number
9+
private onMoreClick?: () => void
10+
private onCloseClick?: () => void
11+
12+
build() {
13+
Row() {
14+
Stack() {
15+
Row({ space: 4 }) {
16+
Blank()
17+
.width(4)
18+
.height(4)
19+
.borderRadius(2)
20+
.backgroundColor(this.getForegroundColor())
21+
Blank()
22+
.width(7)
23+
.height(7)
24+
.borderRadius(4)
25+
.backgroundColor(this.getForegroundColor())
26+
Blank()
27+
.width(4)
28+
.height(4)
29+
.borderRadius(2)
30+
.backgroundColor(this.getForegroundColor())
31+
}
32+
.alignItems(VerticalAlign.Center)
33+
.justifyContent(FlexAlign.Center)
34+
}
35+
.width(43)
36+
.height('100%')
37+
.alignContent(Alignment.Center)
38+
.onClick(() => {
39+
DMPLogger.d(Tags.DMP_PAGE, `Mini program capsule more tapped, webViewId=${this.webViewId}`)
40+
if (this.onMoreClick) {
41+
this.onMoreClick()
42+
}
43+
})
44+
45+
Divider()
46+
.vertical(true)
47+
.height(16)
48+
.strokeWidth(0.5)
49+
.color(this.getBorderColor())
50+
51+
Stack() {
52+
Text('')
53+
.width(18)
54+
.height(18)
55+
.borderRadius(9)
56+
.border({ width: 2.4, color: this.getForegroundColor() })
57+
Text('')
58+
.width(6)
59+
.height(6)
60+
.borderRadius(3)
61+
.backgroundColor(this.getForegroundColor())
62+
}
63+
.width(43)
64+
.height('100%')
65+
.alignContent(Alignment.Center)
66+
.onClick(() => {
67+
if (this.onCloseClick) {
68+
this.onCloseClick()
69+
} else {
70+
DMPAppManager.sharedInstance().getApp(this.appIndex)?.navigatorManager.getCurNavigator()?.pop(999)
71+
}
72+
})
73+
}
74+
.alignItems(VerticalAlign.Center)
75+
.width(87)
76+
.height(32)
77+
.borderRadius(16)
78+
.border({ width: 0.5, color: this.getBorderColor() })
79+
.backgroundColor(this.getBackgroundColor())
80+
}
81+
82+
private getForegroundColor(): string {
83+
return '#1f1f1f'
84+
}
85+
86+
private getBackgroundColor(): string {
87+
return '#ffffff'
88+
}
89+
90+
private getBorderColor(): string {
91+
return '#e5e5e5'
92+
}
93+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
@CustomDialog
2+
export struct DMPMiniProgramMenuDialog {
3+
controller: CustomDialogController
4+
private appName: string = ''
5+
private buildController?: () => CustomDialogController
6+
private onReenter?: () => void
7+
private onClose?: () => void
8+
9+
build() {
10+
Column() {
11+
Row() {
12+
Stack() {
13+
Text(this.getAppInitial())
14+
.fontSize(18)
15+
.fontColor('#8a8a8a')
16+
.fontWeight(FontWeight.Medium)
17+
}
18+
.width(44)
19+
.height(44)
20+
.borderRadius(22)
21+
.backgroundColor('#f4f4f4')
22+
.alignContent(Alignment.Center)
23+
24+
Column() {
25+
Text(this.appName || '小程序')
26+
.fontSize(18)
27+
.fontColor('#202020')
28+
.fontWeight(FontWeight.Bold)
29+
.maxLines(1)
30+
.textOverflow({ overflow: TextOverflow.Ellipsis })
31+
Text('小程序')
32+
.fontSize(14)
33+
.fontColor('#9a9a9a')
34+
.margin({ top: 3 })
35+
}
36+
.alignItems(HorizontalAlign.Start)
37+
.margin({ left: 14 })
38+
}
39+
.width('100%')
40+
.height(84)
41+
.padding({ left: 24, right: 24 })
42+
.alignItems(VerticalAlign.Center)
43+
44+
Divider()
45+
.height(1)
46+
.backgroundColor('#f2f2f2')
47+
48+
Row({ space: 16 }) {
49+
this.MenuItem('↻', 24, '重新进入\n小程序', () => {
50+
this.closeDialog()
51+
this.onReenter?.()
52+
})
53+
this.MenuItem('×', 26, '关闭小程序', () => {
54+
this.closeDialog()
55+
this.onClose?.()
56+
})
57+
}
58+
.width('100%')
59+
.height(134)
60+
.padding({ left: 24, right: 24, top: 20, bottom: 20 })
61+
.alignItems(VerticalAlign.Top)
62+
.justifyContent(FlexAlign.Start)
63+
64+
Divider()
65+
.height(1)
66+
.backgroundColor('#ededed')
67+
68+
Text('取消')
69+
.width('100%')
70+
.height(58)
71+
.fontSize(18)
72+
.fontColor('#576b95')
73+
.textAlign(TextAlign.Center)
74+
.onClick(() => {
75+
this.closeDialog()
76+
})
77+
}
78+
.width('100%')
79+
.backgroundColor(Color.White)
80+
.borderRadius(18)
81+
}
82+
83+
@Builder
84+
private MenuItem(icon: string, iconSize: number, title: string, onClick: () => void) {
85+
Column() {
86+
Stack() {
87+
Text(icon)
88+
.fontSize(iconSize)
89+
.fontColor('#333333')
90+
.fontWeight(FontWeight.Medium)
91+
}
92+
.width(52)
93+
.height(52)
94+
.borderRadius(10)
95+
.backgroundColor('#f8f8f8')
96+
.alignContent(Alignment.Center)
97+
98+
Text(title)
99+
.width(78)
100+
.height(34)
101+
.fontSize(13)
102+
.fontColor('#686868')
103+
.textAlign(TextAlign.Center)
104+
.lineHeight(17)
105+
.maxLines(2)
106+
.margin({ top: 8 })
107+
}
108+
.width(78)
109+
.height(94)
110+
.alignItems(HorizontalAlign.Center)
111+
.justifyContent(FlexAlign.Start)
112+
.onClick(() => {
113+
onClick()
114+
})
115+
}
116+
117+
private getAppInitial(): string {
118+
return this.appName.length > 0 ? this.appName.substring(0, 1) : '小'
119+
}
120+
121+
private closeDialog() {
122+
if (this.buildController) {
123+
this.buildController().close()
124+
}
125+
}
126+
}

harmony/dimina/src/main/ets/DPages/DMPPageContainer.ets

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import { DMPTabBarConfig } from '../Bundle/Model/DMPBundleAppConfigNext'
1111
import { DMPLogger } from '../EventTrack/DMPLogger'
1212
import { Tags } from '../EventTrack/Tags'
1313
import { DMPMap } from '../Utils/DMPMap'
14+
import { DMPDeviceUtil } from '../Utils/DMPDeviceUtils'
15+
import { DMPCapsuleButton } from '../Components/DMPCapsuleButton'
16+
import { DMPMiniProgramMenuDialog } from '../Components/DMPMiniProgramMenuDialog'
17+
import { DMPLaunchConfig } from '../DApp/config/DMPLaunchConfig'
1418

1519
@Component
1620
export struct DMPPageContainer {
@@ -150,6 +154,21 @@ export struct DMPPageContainer {
150154
.width('100%')
151155
.height('100%')
152156
}
157+
158+
if (this.pageRecord) {
159+
DMPCapsuleButton({
160+
appIndex: this.appIndex,
161+
webViewId: this.webViewId,
162+
onMoreClick: () => {
163+
this.showMiniProgramMenu()
164+
},
165+
onCloseClick: () => {
166+
this.closeMiniProgram()
167+
}
168+
})
169+
.position({ x: this.getMenuButtonLeft(), y: this.getMenuButtonTop() })
170+
.zIndex(10)
171+
}
153172
}
154173
}
155174

@@ -167,6 +186,52 @@ export struct DMPPageContainer {
167186
return undefined
168187
}
169188

189+
private getMenuButtonTop(): number {
190+
const navigationBarContentHeight = 55
191+
const capsuleHeight = 32
192+
return (DMPDeviceUtil.getSafeAreaAndDisplayWHSync().get('statusBarHeight') as number) +
193+
Math.round((navigationBarContentHeight - capsuleHeight) / 2)
194+
}
195+
196+
private getMenuButtonLeft(): number {
197+
const capsuleWidth = 87
198+
const capsuleRightMargin = 10
199+
const windowWidth = DMPDeviceUtil.getSafeAreaAndDisplayWHSync().get('windowWidth') as number
200+
return windowWidth - capsuleRightMargin - capsuleWidth
201+
}
202+
203+
private showMiniProgramMenu() {
204+
let dialogController: CustomDialogController | undefined = undefined
205+
dialogController = new CustomDialogController({
206+
builder: DMPMiniProgramMenuDialog({
207+
appName: this.app.appConfig.appName,
208+
buildController: () => {
209+
return dialogController!
210+
},
211+
onReenter: () => {
212+
this.reenterMiniProgram()
213+
},
214+
onClose: () => {
215+
this.closeMiniProgram()
216+
}
217+
}),
218+
alignment: DialogAlignment.Bottom,
219+
autoCancel: true
220+
})
221+
dialogController.open()
222+
}
223+
224+
private reenterMiniProgram() {
225+
const launchConfig = new DMPLaunchConfig()
226+
launchConfig.appEntryPath = this.app.bundleManager.getJsAppModuleConfig().entryPagePath
227+
launchConfig.query = new DMPMap()
228+
this.app.reLaunchRootStack(launchConfig)
229+
}
230+
231+
private closeMiniProgram() {
232+
this.app.navigatorManager.getCurNavigator()?.pop(999)
233+
}
234+
170235
private shouldShowTabBar(): boolean {
171236
// Don't show TabBar if explicitly hidden (e.g., inside TabBar container)
172237
if (this.hideTabBar) {
@@ -248,5 +313,3 @@ export struct DMPPageContainer {
248313
})
249314
}
250315
}
251-
252-

0 commit comments

Comments
 (0)