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