Create view state files for `dashboard` and `search`

Release-Notes: skip
Google-Bug-Id: b/244279450
Change-Id: Iebb3b3cd6f0fa33d3e9ed46b1245890d376db466
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index 7c362f5..0b396f0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -253,12 +253,12 @@
     if (!value || value.view !== GerritView.SEARCH) return;
     const offset = isNaN(Number(value.offset)) ? 0 : Number(value.offset);
 
-    if (this.query !== value.query) {
+    if (this.query !== (value.query ?? '')) {
       this.selectedIndex = 0;
     }
 
     this.loading = true;
-    this.query = value.query;
+    this.query = value.query ?? '';
     this.offset = offset;
 
     // NOTE: This method may be called before attachment. Fire title-change
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index 33a118e..14867a7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -27,7 +27,6 @@
   PreferencesInput,
   RepoName,
 } from '../../../types/common';
-import {AppElementDashboardParams} from '../../gr-app-types';
 import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
 import {GrCreateCommandsDialog} from '../gr-create-commands-dialog/gr-create-commands-dialog';
 import {
@@ -52,6 +51,7 @@
 import {assertIsDefined} from '../../../utils/common-util';
 import {Shortcut} from '../../../services/shortcuts/shortcuts-config';
 import {ShortcutController} from '../../lit/shortcut-controller';
+import {DashboardViewState} from '../../../models/views/dashboard';
 
 const PROJECT_PLACEHOLDER_PATTERN = /\${project}/g;
 
@@ -84,7 +84,7 @@
   preferences?: PreferencesInput;
 
   @property({type: Object})
-  params?: AppElementDashboardParams;
+  params?: DashboardViewState;
 
   // private but used in test
   @state() results?: ChangeListSection[];
@@ -317,11 +317,12 @@
   // private but used in test
   getProjectDashboard(
     project: RepoName,
-    dashboard: DashboardId
+    dashboard?: DashboardId
   ): Promise<UserDashboard | undefined> {
     const errFn = (response?: Response | null) => {
       firePageError(response);
     };
+    assertIsDefined(dashboard, 'project dashboard must have id');
     return this.restApiService
       .getDashboard(project, dashboard, errFn)
       .then(response => {
@@ -352,7 +353,7 @@
     return 'Dashboard for ' + user;
   }
 
-  private isViewActive(params: AppElementDashboardParams) {
+  private isViewActive(params: DashboardViewState) {
     return params.view === GerritView.DASHBOARD;
   }
 
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index 986f616..ee5923e 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -42,7 +42,6 @@
 import {
   AppElementJustRegisteredParams,
   AppElementParams,
-  AppElementSearchParam,
   isAppElementJustRegisteredParams,
 } from './gr-app-types';
 import {GrMainHeader} from './core/gr-main-header/gr-main-header';
@@ -72,6 +71,7 @@
 import {subscribe} from './lit/subscription-controller';
 import {KnownExperimentId} from '../services/flags/flags';
 import {PluginViewState} from '../models/views/plugin';
+import {SearchViewState} from '../models/views/search';
 
 interface ErrorInfo {
   text: string;
@@ -369,7 +369,7 @@
       <gr-endpoint-decorator name="banner"></gr-endpoint-decorator>
       <gr-main-header
         id="mainHeader"
-        .searchQuery=${(this.params as AppElementSearchParam)?.query}
+        .searchQuery=${(this.params as SearchViewState)?.query}
         @mobile-search=${this.mobileSearchToggle}
         @show-keyboard-shortcuts=${this.showKeyboardShortcuts}
         .mobileSearchHidden=${!this.mobileSearch}
@@ -431,7 +431,7 @@
       <gr-smart-search
         id="search"
         label="Search for changes"
-        .searchQuery=${(this.params as AppElementSearchParam)?.query}
+        .searchQuery=${(this.params as SearchViewState)?.query}
       >
       </gr-smart-search>
     `;
diff --git a/polygerrit-ui/app/elements/gr-app-types.ts b/polygerrit-ui/app/elements/gr-app-types.ts
index 1d8a7cf..5a54aa7 100644
--- a/polygerrit-ui/app/elements/gr-app-types.ts
+++ b/polygerrit-ui/app/elements/gr-app-types.ts
@@ -5,14 +5,13 @@
  */
 import {
   BasePatchSetNum,
-  DashboardId,
   NumericChangeId,
   RepoName,
   RevisionPatchSetNum,
   UrlEncodedCommentId,
 } from '../types/common';
 import {GerritView} from '../services/router/router-model';
-import {GenerateUrlParameters, DashboardSection} from '../utils/router-util';
+import {GenerateUrlParameters} from '../utils/router-util';
 import {AttemptChoice} from '../models/checks/checks-util';
 import {SettingsViewState} from '../models/views/settings';
 import {AdminViewState} from '../models/views/admin';
@@ -21,29 +20,13 @@
 import {AgreementViewState} from '../models/views/agreement';
 import {DocumentationViewState} from '../models/views/documentation';
 import {PluginViewState} from '../models/views/plugin';
+import {SearchViewState} from '../models/views/search';
+import {DashboardViewState} from '../models/views/dashboard';
 
 export interface AppElement extends HTMLElement {
   params: AppElementParams | GenerateUrlParameters;
 }
 
-// TODO(TS): Remove unify AppElementParams with GenerateUrlParameters
-// Seems we can use GenerateUrlParameters instead of AppElementParams,
-// but it require some refactoring
-export interface AppElementDashboardParams {
-  view: GerritView.DASHBOARD;
-  project?: RepoName;
-  dashboard: DashboardId;
-  user?: string;
-  sections?: DashboardSection[];
-  title?: string;
-}
-
-export interface AppElementSearchParam {
-  view: GerritView.SEARCH;
-  query: string;
-  offset: string;
-}
-
 export interface AppElementDiffViewParam {
   view: GerritView.DIFF;
   changeNum: NumericChangeId;
@@ -94,14 +77,14 @@
 }
 
 export type AppElementParams =
-  | AppElementDashboardParams
+  | DashboardViewState
   | GroupViewState
   | AdminViewState
   | AppElementChangeViewParams
   | RepoViewState
   | DocumentationViewState
   | PluginViewState
-  | AppElementSearchParam
+  | SearchViewState
   | SettingsViewState
   | AgreementViewState
   | AppElementDiffViewParam
diff --git a/polygerrit-ui/app/models/views/dashboard.ts b/polygerrit-ui/app/models/views/dashboard.ts
new file mode 100644
index 0000000..bd4c20e
--- /dev/null
+++ b/polygerrit-ui/app/models/views/dashboard.ts
@@ -0,0 +1,30 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import {RepoName} from '../../api/rest-api';
+import {GerritView} from '../../services/router/router-model';
+import {DashboardId} from '../../types/common';
+import {DashboardSection} from '../../utils/router-util';
+import {Model} from '../model';
+import {ViewState} from './base';
+
+export interface DashboardViewState extends ViewState {
+  view: GerritView.DASHBOARD;
+  project?: RepoName;
+  dashboard?: DashboardId;
+  user?: string;
+  sections?: DashboardSection[];
+  title?: string;
+}
+
+const DEFAULT_STATE: DashboardViewState = {
+  view: GerritView.DASHBOARD,
+};
+
+export class DashboardViewModel extends Model<DashboardViewState> {
+  constructor() {
+    super(DEFAULT_STATE);
+  }
+}
diff --git a/polygerrit-ui/app/models/views/search.ts b/polygerrit-ui/app/models/views/search.ts
new file mode 100644
index 0000000..f18d2df
--- /dev/null
+++ b/polygerrit-ui/app/models/views/search.ts
@@ -0,0 +1,24 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import {GerritView} from '../../services/router/router-model';
+import {Model} from '../model';
+import {ViewState} from './base';
+
+export interface SearchViewState extends ViewState {
+  view: GerritView.SEARCH;
+  query?: string;
+  offset?: string;
+}
+
+const DEFAULT_STATE: SearchViewState = {
+  view: GerritView.SEARCH,
+};
+
+export class SearchViewModel extends Model<SearchViewState> {
+  constructor() {
+    super(DEFAULT_STATE);
+  }
+}
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index eb94fe6..c6404b1 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -91,10 +91,7 @@
 } from '../constants/constants';
 import {formatDate} from '../utils/date-util';
 import {GetDiffCommentsOutput} from '../services/gr-rest-api/gr-rest-api';
-import {
-  AppElementChangeViewParams,
-  AppElementSearchParam,
-} from '../elements/gr-app-types';
+import {AppElementChangeViewParams} from '../elements/gr-app-types';
 import {CommitInfoWithRequiredCommit} from '../elements/change/gr-change-metadata/gr-change-metadata';
 import {WebLinkInfo} from '../types/diff';
 import {
@@ -119,6 +116,7 @@
 import {Category, RunStatus} from '../api/checks';
 import {DiffInfo} from '../api/diff';
 import {GenerateUrlEditViewParameters} from '../utils/router-util';
+import {SearchViewState} from '../models/views/search';
 
 const TEST_DEFAULT_EXPRESSION = 'label:Verified=MAX -label:Verified=MIN';
 export const TEST_PROJECT_NAME: RepoName = 'test-project' as RepoName;
@@ -695,7 +693,7 @@
   };
 }
 
-export function createAppElementSearchViewParams(): AppElementSearchParam {
+export function createAppElementSearchViewParams(): SearchViewState {
   return {
     view: GerritView.SEARCH,
     query: TEST_NUMERIC_CHANGE_ID.toString(),