• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16
17import { int32 } from "@koalaui/common"
18import { __memo_context_type , __memo_id_type  } from "arkui.stateManagement.runtime";
19import {
20    MutableState,
21    contextLocal,
22    contextLocalScope,
23    mutableState,
24    remember,
25    RepeatByArray,
26    arrayState,
27    RunEffect,
28    __context,
29    __id,
30    memoEntry,
31    GlobalStateManager,
32    StateContext,
33    ComputableState,
34} from "@koalaui/runtime"
35import { ArkUINativeModule } from "#components"
36import { KPointer, runtimeType, RuntimeType } from "@koalaui/interop"
37import router from "@ohos/router"
38import { EntryPoint, UserView, UserViewBuilder } from "../UserView"
39import { InteropNativeModule, nullptr } from "@koalaui/interop"
40import { PeerNode } from "../PeerNode"
41import { ArkUIGeneratedNativeModule, TypeChecker } from "#components"
42import { Visibility } from "../component"
43import { Serializer } from "../component/peers/Serializer"
44import { RouterExtender } from "./ArkRouterExtenderMaterialized"
45
46// ----------------------------------------------------------
47// TODO: Remove these constants when enums are fixed in Panda
48export const VisibilityHidden = 0
49export const VisibilityVisible = 1
50export const VisibilityShowing = 2
51export const VisibilityHiding = 3
52
53const RouteType_NONE = 0
54const RouteType_None = 0
55const RouteType_PUSH = 1
56const RouteType_Push = 1
57const RouteType_POP = 2
58const RouteType_Pop = 2
59// ----------------------------------------------------------
60
61export enum RouterTransitionVisibility {
62    Hidden = VisibilityHidden,
63    Visible = VisibilityVisible,
64    Showing = VisibilityShowing,
65    Hiding = VisibilityHiding
66}
67
68export interface RouterTransitionState {
69    pageId: int32
70    visibility: int32 // TODO: Use RouterTransitionVisibility enum when enums are fixed in Panda
71    route?: int32 // TODO: Use RouteType enum when enums are fixed in Panda
72}
73
74class VisiblePage {
75    /** @memo */
76    page: UserViewBuilder
77    url: string
78    path: string
79    params: Object | undefined
80    peerNode: PeerNode | undefined
81
82    constructor(
83        page: UserViewBuilder,
84        url: string,
85        path: string,
86        params?: Object
87    ) {
88        this.page = page
89        this.url = url
90        this.path = path
91        this.params = params
92        this.peerNode = undefined
93    }
94
95    updatePeerNode(node: PeerNode): void {
96        this.peerNode = node
97    }
98}
99
100export interface Router {
101    push(options: router.RouterOptions): void
102
103    replace(options: router.RouterOptions): void
104
105    pushUrl(options: router.RouterOptions): Promise<void>
106
107    back(options?: router.RouterOptions): void
108
109    clear(): void
110
111    getLength(): string
112
113    getParams(): Object
114
115    getState(): router.RouterState
116
117    getStateByIndex(index: number): router.RouterState | undefined
118
119    getStateByUrl(url: string): Array<router.RouterState>
120
121    UpdateVisiblePagePeerNode(node: PeerNode, index?: number): void
122
123    getEntryRootValue(): Array<ComputableState<PeerNode>>
124
125    runPage(options: router.RouterOptions, builder: UserViewBuilder): void
126}
127
128class ArkRouter implements Router {
129    private readonly moduleName: string
130    private peerNodeList = new Array<KPointer>
131    public readonly visiblePages = arrayState<VisiblePage>()
132    private showingPageIndex : number = 0
133    private rootState: Array<ComputableState<PeerNode>> = new Array<ComputableState<PeerNode>>()
134
135    constructor(moduleName: string) {
136        this.moduleName = moduleName
137    }
138
139    private getClassName(url: string): string {
140        let className: string = this.moduleName + "/src/main/ets/" + url + "/__EntryWrapper";
141        return className;
142    }
143
144    private getPathInfo(url: string): string {
145        let pathInfo = this.moduleName + "/src/main/ets/" + url + ".js"
146        return pathInfo
147    }
148
149    private RunPage(url: string): EntryPoint | undefined {
150        try {
151            //@ts-ignore
152            let runtimeLinker = getNearestNonBootRuntimeLinker();
153            let entryClass = runtimeLinker?.loadClass(url, false);
154            if (!entryClass) {
155                InteropNativeModule._NativeLog("AceRouter: load entryClass failed")
156            } else {
157                let entryInstance = entryClass.createInstance();
158                let entryPoint = entryInstance as EntryPoint;
159                return entryPoint
160            }
161        }
162        //@ts-ignore
163        catch (e: Error) {
164            InteropNativeModule._NativeLog("AceRouter: catch RunPage error: " + e)
165        }
166        return undefined
167    }
168
169    UpdateVisiblePagePeerNode(node: PeerNode, index: number = -1): void {
170        InteropNativeModule._NativeLog("AceRouter: router UpdateVisiblePagePeerNode, index: " + index)
171        if (index == -1) {
172            index = this.visiblePages.length - 1
173        }
174        if (index < 0 || index > this.showingPageIndex) {
175            InteropNativeModule._NativeLog("AceRouter: router page size is incorrect")
176            return;
177        }
178        if (this.visiblePages.length > index && this.peerNodeList.length > index) {
179            this.visiblePages.value[index].updatePeerNode(node)
180            RouterExtender.moveCommonUnderPageNode(node.peer.ptr, this.peerNodeList[index])
181        }
182    }
183
184    push(options: router.RouterOptions): void {
185        let className = this.getClassName(options.url)
186        let entryObject = this.RunPage(className)
187        if (entryObject) {
188            let manager = GlobalStateManager.instance
189            let peerNode = PeerNode.generateRootPeer()
190            let stateNode = manager.updatableNode<PeerNode>(peerNode, (context: StateContext) => {
191                const frozen = manager.frozen
192                manager.frozen = true
193                memoEntry<void>(context, 0, () => {
194                    entryObject!.entry()
195                })
196                manager.frozen = frozen
197            })
198            this.rootState.push(stateNode)
199            let pageNode = RouterExtender.routerPush(options)
200            if (pageNode === nullptr) {
201                InteropNativeModule._NativeLog("AceRouter:push page failed")
202                this.rootState.pop()
203                return
204            }
205            this.peerNodeList.splice(this.peerNodeList.length, 0, pageNode)
206
207            let newPage = new VisiblePage(entryObject.entry, options.url, this.getPathInfo(options.url), options.params)
208            this.visiblePages.splice(this.showingPageIndex + 1, 0, newPage)
209            this.showingPageIndex += 1
210        }
211    }
212
213    replace(options: router.RouterOptions): void {
214        let className = this.getClassName(options.url)
215        let entryObject = this.RunPage(className)
216        if (entryObject) {
217            let manager = GlobalStateManager.instance
218            let peerNode = PeerNode.generateRootPeer()
219            let stateNode = manager.updatableNode<PeerNode>(peerNode, (context: StateContext) => {
220                const frozen = manager.frozen
221                manager.frozen = true
222                memoEntry<void>(context, 0, () => {
223                    entryObject!.entry()
224                })
225                manager.frozen = frozen
226            })
227
228            this.rootState.push(stateNode)
229            let pageTransiTionFinishCallback = () => {
230                this.peerNodeList.splice(this.showingPageIndex - 1, 1)
231                this.visiblePages.splice(this.showingPageIndex - 1, 1)
232                let preNodeList = this.rootState.splice(this.showingPageIndex - 1, 1)
233                if (preNodeList.length > 0 &&  preNodeList[0]) {
234                    preNodeList[0].dispose();
235                }
236                this.showingPageIndex -= 1
237            }
238            let pageNode = RouterExtender.routerReplace(options, pageTransiTionFinishCallback)
239            if (pageNode === nullptr) {
240                InteropNativeModule._NativeLog("AceRouter:replace page failed")
241                this.rootState.pop()
242                return
243            }
244            this.peerNodeList.push(pageNode)
245
246            let newPage = new VisiblePage(entryObject.entry, options.url, this.getPathInfo(options.url), options.params)
247            this.visiblePages.push(newPage)
248            this.showingPageIndex += 1
249        }
250    }
251
252    pushUrl(options: router.RouterOptions): Promise<void> {
253        return new Promise<void>(() => {})
254    }
255
256    back(options?: router.RouterOptions): void {
257        if (this.peerNodeList.length <= 1) {
258            return;
259        }
260        this.showingPageIndex = this.showingPageIndex - 1
261        this.peerNodeList.pop()
262        RouterExtender.routerBack(options)
263        this.visiblePages.pop()
264        let preNode = this.rootState.pop()
265        if (preNode) {
266            preNode?.dispose();
267        }
268    }
269
270    clear(): void {
271        InteropNativeModule._NativeLog("AceRouter: router clear")
272        if (this.peerNodeList.length <= 1) {
273            return;
274        }
275        this.peerNodeList.splice(0, this.showingPageIndex)
276        this.visiblePages.splice(0, this.showingPageIndex)
277        this.rootState.splice(0, this.showingPageIndex)
278        RouterExtender.routerClear();
279        this.showingPageIndex = 0
280    }
281
282    getParams(): Object {
283        let curPage = this.visiblePages.at(this.showingPageIndex)
284        return curPage.params !== undefined ? curPage.params! : new Object()
285    }
286
287    getLength(): string {
288        return String(this.visiblePages.length)
289    }
290
291    getState(): router.RouterState {
292        let curPage = this.visiblePages.at(this.showingPageIndex)
293        let state: router.RouterState = {
294            index: this.showingPageIndex,
295            name: curPage.url,
296            path: curPage.path,
297            params: curPage.params !== undefined ? curPage.params! : new Object()
298        } as router.RouterState
299        return state
300    }
301
302    getStateByIndex(index: number): router.RouterState | undefined {
303        if (index > this.showingPageIndex) {
304            return undefined
305        }
306        let page = this.visiblePages.at(index)
307        let state: router.RouterState = {
308            index: index,
309            name: page.url,
310            path: page.path,
311            params: page.params !== undefined ? page.params! : new Object()
312        } as router.RouterState
313        return state
314    }
315
316    getStateByUrl(url: string): Array<router.RouterState> {
317        let retVal: Array<router.RouterState> = new Array<router.RouterState>()
318        this.visiblePages.value.forEach((element, index) => {
319            if (element.url === url) {
320                let state: router.RouterState = {
321                    index: index,
322                    name: element.url,
323                    path: element.path,
324                    params: element.params !== undefined ? element.params! : new Object()
325                } as router.RouterState
326                retVal.push(state)
327            }
328        })
329        return retVal
330    }
331
332    getEntryRootValue(): Array<ComputableState<PeerNode>> {
333        return this.rootState
334    }
335
336    runPage(options: router.RouterOptions, builder: UserViewBuilder): void {
337        let manager = GlobalStateManager.instance
338        let peerNode = PeerNode.generateRootPeer()
339        let stateNode = manager.updatableNode<PeerNode>(peerNode, (context: StateContext) => {
340            const frozen = manager.frozen
341            manager.frozen = true
342            memoEntry<void>(context, 0, builder)
343            manager.frozen = frozen
344        })
345
346        let pageNode = RouterExtender.routerRunPage(options)
347        let node: PeerNode = stateNode.value
348        this.rootState.push(stateNode)
349        this.peerNodeList.splice(this.peerNodeList.length, 0, pageNode)
350
351        let newPage = new VisiblePage(builder, options.url, this.getPathInfo(options.url), options.params)
352        this.visiblePages.splice(0, 0, newPage)
353        this.visiblePages.value[this.showingPageIndex].updatePeerNode(node)
354        RouterExtender.moveCommonUnderPageNode(node.peer.ptr, this.peerNodeList[this.showingPageIndex])
355    }
356}
357
358export function Routed(
359    /** @memo */
360    initial: () => void,
361    moduleName: string,
362    rootPeer: PeerNode,
363    initialUrl?: string,
364): void {
365    let routerImp = new ArkRouter(moduleName)
366    // Install default global router.
367    router.setRouter(routerImp)
368}
369