1/* 2 * Copyright (c) 2023 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 16import { Log } from '../utils/Log'; 17import { loggerMiddle } from './middlewares/loggerMiddle'; 18import type { CameraInitState } from './reducers/CameraInitReducer'; 19import { cameraInitReducer } from './reducers/CameraInitReducer'; 20import type { ContextState } from './reducers/ContextReducer'; 21import { contextReducer } from './reducers/ContextReducer'; 22import type { CameraState } from './reducers/CameraReducer'; 23import { cameraReducer } from './reducers/CameraReducer'; 24import type { PreviewState } from './reducers/PreviewReducer'; 25import { previewReducer } from './reducers/PreviewReducer'; 26import type { CaptureState } from './reducers/CaptureReducer'; 27import { captureReducer } from './reducers/CaptureReducer'; 28import type { ModeChangeState } from './reducers/ModeChangeReducer'; 29import { modeChangeReducer } from './reducers/ModeChangeReducer'; 30import type { ModeState } from './reducers/ModeReducer'; 31import { modeReducer } from './reducers/ModeReducer'; 32import type { SettingState } from './reducers/SettingReducer'; 33import { settingReducer } from './reducers/SettingReducer'; 34import type { RecordState } from './reducers/RecordReducer'; 35import { recordReducer } from './reducers/RecordReducer'; 36import type { ZoomState } from './reducers/ZoomReducer'; 37import { zoomReducer } from './reducers/ZoomReducer'; 38import type { ActionData } from './actions/Action'; 39import type { Enhancer, Reducer } from './core'; 40import { applyMiddleware, combineReducers } from './core'; 41import { eventBusMiddle } from './middlewares/EventBusMiddle'; 42 43const TAG = '[store]:'; 44const INIT_TAG = 'StoreInit'; 45 46export type OhCombinedState = { 47 cameraInitReducer: CameraInitState; 48 contextReducer: ContextState; 49 cameraReducer: CameraState; 50 previewReducer: PreviewState; 51 captureReducer: CaptureState; 52 recordReducer: RecordState; 53 modeChangeReducer: ModeChangeState; 54 modeReducer: ModeState 55 settingReducer: SettingState 56 zoomReducer: ZoomState 57}; 58 59function getReducers(): Reducer { 60 return combineReducers([ 61 cameraInitReducer, 62 contextReducer, 63 cameraReducer, 64 previewReducer, 65 captureReducer, 66 recordReducer, 67 modeChangeReducer, 68 modeReducer, 69 settingReducer, 70 zoomReducer, 71 ]); 72} 73 74export interface Unsubscribe { 75 destroy(): void 76} 77 78export interface Dispatch<A = ActionData> { 79 <T extends A>(action: T): T; 80} 81 82function getEnhancer(): Enhancer { 83 return applyMiddleware(loggerMiddle, eventBusMiddle); 84} 85 86export function getStore(): Store { 87 Log.info('getStore'); 88 return Store.getInstance(); 89} 90 91export class Store { 92 private static mInstance: Store | undefined = undefined; 93 private currentReducer: Reducer; 94 private currentState = undefined; 95 private currentListeners: (() => void)[] | null = []; 96 private nextListeners = this.currentListeners; 97 private isDispatching = false; 98 99 private constructor(reducer: Reducer, enhancer: Enhancer) { 100 this.currentReducer = reducer; 101 this.dispatch({ type: INIT_TAG, data: null }); 102 this.dispatch = enhancer(this.dispatch.bind(this)); 103 } 104 105 public static hasStore(): boolean { 106 return Store.mInstance !== undefined; 107 } 108 109 public static getInstance(): Store { 110 if (!Store.mInstance) { 111 Store.mInstance = new Store(getReducers(), getEnhancer()); 112 } 113 return Store.mInstance; 114 } 115 116 public static createStore(reducer: Reducer, enhancer: Enhancer): Store { 117 Store.mInstance = new Store(reducer, enhancer); 118 return Store.mInstance; 119 } 120 121 public getState(): OhCombinedState { 122 if (this.isDispatching) { 123 Log.error('isDispatching get error'); 124 return null; 125 } 126 return this.currentState; 127 } 128 129 public dispatch(action: ActionData): ActionData { 130 if (this.isDispatching) { 131 Log.error('isDispatching get error'); 132 return null; 133 } 134 try { 135 this.isDispatching = true; 136 this.currentState = this.currentReducer(this.currentState, action); 137 } finally { 138 this.isDispatching = false; 139 } 140 this.currentListeners = this.nextListeners; 141 const listeners = this.nextListeners; 142 for (let i = 0; i < listeners.length; i++) { 143 const listener = listeners[i]; 144 listener(); 145 } 146 return action; 147 } 148 149 public subscribe(mapToProps: MapStateProp | null, mapToDispatch: MapDispatchProp | null): Unsubscribe { 150 Log.info(`${TAG} getStore subscribe invoke.`); 151 if (mapToDispatch) { 152 mapToDispatch(this.dispatch as Dispatch); 153 } 154 let unsubscribe: () => void = () => { 155 }; 156 if (mapToProps) { 157 mapToProps(this.currentState); 158 unsubscribe = this.stateSubscribe(() => mapToProps(this.currentState)); 159 return { 160 destroy(): void { 161 unsubscribe(); 162 } 163 }; 164 } 165 return null; 166 } 167 168 private stateSubscribe(listener: () => void): () => void { 169 if (typeof listener !== 'function') { 170 Log.error('listener is not function'); 171 return () => { 172 }; 173 } 174 if (this.isDispatching) { 175 Log.error('isDispatching stateSubscribe error'); 176 return () => { 177 }; 178 } 179 let isSubScribed = true; 180 181 this.ensureCanMutateNextListeners(); 182 this.nextListeners.push(listener); 183 return (): void => { 184 if (!isSubScribed) { 185 return; 186 } 187 this.currentListeners = null; 188 if (this.isDispatching) { 189 Log.error('isDispatching unstateSubscribe error'); 190 return; 191 } 192 isSubScribed = false; 193 this.ensureCanMutateNextListeners(); 194 const index = this.nextListeners.indexOf(listener); 195 this.nextListeners.slice(index, 1); 196 this.currentListeners = null; 197 }; 198 } 199 200 private ensureCanMutateNextListeners(): void { 201 if (this.nextListeners === this.currentListeners) { 202 this.nextListeners = this.currentListeners.slice(); 203 } 204 } 205} 206 207interface MapStateProp { 208 (state: OhCombinedState): void; 209} 210 211interface MapDispatchProp { 212 (dispatch: Dispatch): void; 213}