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