<file_sep># NativeScript JavaScript Template

This template creates a "Hello, world" NativeScript app using JavaScript.

This is the default template, so you can create a new app that uses it with the `--template` option:

```
ns create my-hello-world-js --template @nativescript/template-hello-world
```

Or without it:

```
ns create my-hello-world-js
```

> Note: Both commands will create a new NativeScript app that uses the latest version of this template published to [npm](https://www.npmjs.com/package/@nativescript/template-hello-world).

If you want to create a new app that uses the source of the template from the `master` branch, you can execute the following:

```
# clone nativescript-app-templates monorepo locally
git clone <EMAIL>:NativeScript/nativescript-app-templates.git

# create app template from local source (all templates are in the 'packages' subfolder of the monorepo)
ns create my-hello-world-js --template nativescript-app-templates/packages/template-hello-world
```

## Get Help
The NativeScript framework has a vibrant community that can help when you run into problems.

Try [joining the NativeScript community Discord](https://nativescript.org/discord). The Discord channel is a great place to get help troubleshooting problems, as well as connect with other NativeScript developers.

If you have found an issue with this template, please report the problem in the [NativeScript repository](https://github.com/NativeScript/NativeScript/issues).

## Contributing

We love PRs, and accept them gladly. Feel free to propose changes and new ideas. We will review and discuss, so that they can be accepted and better integrated.
<file_sep>declare module '*.svelte' {
    export { SvelteComponent as default };
}<file_sep>import Vue from 'nativescript-vue'

import App from './components/App'

new Vue({
  render: (h) => h(App),
}).$start()
<file_sep>import { Observable } from '@nativescript/core'

export class HomeViewModel extends Observable {
  constructor() {
    super()
  }
}
<file_sep>import { Component, OnInit } from '@angular/core'
import { NavigationEnd, Router } from '@angular/router'
import { RouterExtensions } from '@nativescript/angular'
import {
  DrawerTransitionBase,
  RadSideDrawer,
  SlideInOnTopTransition,
} from 'nativescript-ui-sidedrawer'
import { filter } from 'rxjs/operators'
import { Application } from '@nativescript/core'

@Component({
  selector: 'ns-app',
  templateUrl: 'app.component.html',
})
export class AppComponent implements OnInit {
  private _activatedUrl: string
  private _sideDrawerTransition: DrawerTransitionBase

  constructor(private router: Router, private routerExtensions: RouterExtensions) {
    // Use the component constructor to inject services.
  }

  ngOnInit(): void {
    this._activatedUrl = '/home'
    this._sideDrawerTransition = new SlideInOnTopTransition()

    this.router.events
      .pipe(filter((event: any) => event instanceof NavigationEnd))
      .subscribe((event: NavigationEnd) => (this._activatedUrl = event.urlAfterRedirects))
  }

  get sideDrawerTransition(): DrawerTransitionBase {
    return this._sideDrawerTransition
  }

  isComponentSelected(url: string): boolean {
    return this._activatedUrl === url
  }

  onNavItemTap(navItemRoute: string): void {
    this.routerExtensions.navigate([navItemRoute], {
      transition: {
        name: 'fade',
      },
    })

    const sideDrawer = <RadSideDrawer>Application.getRootView()
    sideDrawer.closeDrawer()
  }
}
<file_sep>import { EventData, View, NavigatedData, Page } from '@nativescript/core'
import { Item } from '../shared/item'

export function onNavigatingTo(args: NavigatedData) {
  const page = args.object as Page
  const item = args.context as Item
  page.bindingContext = item
}

export function onBackButtonTap(args: EventData) {
  const view = args.object as View
  const page = view.page as Page

  page.frame.goBack()
}
<file_sep># React NativeScript Blank Template

App templates help you jump start your native cross-platform apps with built-in UI elements and best practices. Save time writing boilerplate code over and over again when you create new apps.

## Quick Start from NPM package
Execute the following command to create an app from this template:

```
ns create my-blank-react --react

cd my-blank-react
npm install

ns preview
# or
ns run android
# or
ns run ios
```

> Note: This command will create a new NativeScript app that uses the latest version of this template published to [npm](https://www.npmjs.com/package/@nativescript/template-blank-react).


**NB:** Please, keep in mind that the master branch may refer to dependencies that are not on NPM yet!

## Walkthrough

Having created an app from this template, here's an introduction to how it works:

### Architecture

- `src/app.ts`: The app entrypoint. It renders the MainStack component as the root component.
- `src/components/MainStack.tsx`: A React Navigation 5 [stack navigator](https://reactnavigation.org/docs/5.x/stack-navigator/) serving as the root component.
- `src/components/ScreenOne.tsx`, `src/components/ScreenTwo.tsx`: A couple of screens to navigate to.
- `src/NavigationParamList.tsx`: A record of the navigation params for each route in your app.

### The basics

Learn React NativeScript itself by reading [its docs](https://react-nativescript.netlify.app/docs/introduction/introduction.html).

Learn how to navigate by referring to the React Navigation 5 [docs])https://reactnavigation.org/docs/5.x/getting-started) and refer to the [react-nativescript-navigation](https://github.com/shirakaba/react-nativescript-navigation/tree/master/react-nativescript-navigation) repo to see which options are supported for each navigator.

React libraries without a dependency on `react-dom` (like Redux) should work just fine in React NativeScript as long as the `react` version is compatible.

## Get Help
The NativeScript framework has a vibrant community that can help when you run into problems.

Try [joining the NativeScript community Discord](https://nativescript.org/discord). The Discord `#react` channel is a great place to get help troubleshooting problems, as well as connect with other NativeScript developers.

If you have found an issue with this template, please report the problem in this repository's [Issues page](https://github.com/shirakaba/tns-template-blank-react/issues).

## Contributing

We love PRs, and accept them gladly. Feel free to propose changes and new ideas. We will review and discuss, so that they can be accepted and better integrated.
<file_sep>export const carClassList: Array<string> = ['Mini', 'Economy', 'Compact', 'Standard', 'Luxury']

export const carDoorList: Array<number> = [2, 3, 5]

export const carSeatList: Array<string> = ['2', '4', '4 + 1', '6 + 1']

export const carTransmissionList: Array<string> = ['Manual', 'Automatic']
<file_sep>import { Application } from '@nativescript/core'

import { HomeViewModel } from './home-view-model'

export function onNavigatingTo(args) {
  const page = args.object
  page.bindingContext = new HomeViewModel()
}

export function onDrawerButtonTap(args) {
  const sideDrawer = Application.getRootView()
  sideDrawer.showDrawer()
}

<file_sep>export const classList = ['Mini', 'Economy', 'Compact', 'Standard', 'Luxury']

export const doorsList = [2, 3, 5]

export const seatsList = ['2', '4', '4 + 1', '6 + 1']

export const transmissionList = ['Manual', 'Automatic']
<file_sep>import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'
import { NativeScriptCommonModule } from '@nativescript/angular'

import { FeaturedRoutingModule } from './featured-routing.module'
import { FeaturedComponent } from './featured.component'

@NgModule({
  imports: [NativeScriptCommonModule, FeaturedRoutingModule],
  declarations: [FeaturedComponent],
  schemas: [NO_ERRORS_SCHEMA],
})
export class FeaturedModule {}
<file_sep>export function Car(options) {
  const model = {
    id: options.id,
    name: options.name,
    hasAC: options.hasAC,
    description: options.description,
    seats: options.seats,
    luggage: Number(options.luggage),
    class: options.class,
    doors: Number(options.doors),
    price: Number(options.price),
    transmission: options.transmission,
    imageUrl: options.imageUrl,
    imageStoragePath: options.imageStoragePath,
  }

  return model
}
<file_sep>import db from "./car-rental-export-public.json"

class ApiService {
  constructor() {
    this.db = db;
  }

  update(path, updateModel) {
    const carId = path.split("/").slice(-1)[0];
    this.db.cars[carId] = {...this.db.cars[carId], ...updateModel }
  }

  uploadFile(params) {
    return {url: params.localFullPath}
  }

  addValueEventListener(onValueEvent, path) {
    onValueEvent(this.db.cars)
    return {
      path: path,
      listeners: this.db.cars
    }

  }
}

export default new ApiService;
<file_sep>export const visibilityValueConverter = {
  toView: function (value) {
    if (value) {
      return 'collapsed'
    } else {
      return 'visible'
    }
  },
  toModel: function (value) {
    return value
  },
}
<file_sep>import { Subscription } from 'rxjs'
import { finalize } from 'rxjs/operators'
import { Observable, ObservableArray } from '@nativescript/core'

import { ObservableProperty } from '../shared/observable-property-decorator'
import { Car } from './shared/car-model'
import { CarService } from './shared/car-service'

export class CarsListViewModel extends Observable {
  private static _subscriptionKey = 'car-list-view-model'

  @ObservableProperty() cars: ObservableArray<Car>
  @ObservableProperty() isLoading: boolean

  private _carService: CarService
  private _dataSubscription: Subscription

  constructor() {
    super()

    this.cars = new ObservableArray<Car>([])
    this.isLoading = false

    this._carService = CarService.getInstance()
  }

  load(): void {
    if (!this._dataSubscription) {
      this.isLoading = true

      // we need to be able to unsubscribe from data changes when old page got disposed / inaccessible but
      // we do not have equivalent of Angular ngOnInit / ngOnDestroy so it cannot be implicit solution i.e.
      // we should explicitly state when to unsubscribe e.g. in the following scenario master list #1 ->
      // car detail -> edit car detail -> master list #2 (forward nav with clearHistory)
      // we need to unsubscribe the master list #1 view model (as it can never be accessed again)
      // if cached subscription exists here, we know it is from a different (previous) instance of the same
      // view model and we need to unsubscribe from it
      const cachedSubscription = this._carService.getSubscription(
        CarsListViewModel._subscriptionKey
      )
      if (cachedSubscription) {
        cachedSubscription.unsubscribe()
        this._carService.setSubscription(CarsListViewModel._subscriptionKey, null)
      }

      this._dataSubscription = this._carService
        .load()
        .pipe(
          finalize(() => {
            this.isLoading = false
          })
        )
        .subscribe((cars: Array<Car>) => {
          this.cars = new ObservableArray(cars)
          this.isLoading = false
        })

      this._carService.setSubscription(CarsListViewModel._subscriptionKey, this._dataSubscription)
    }
  }
}
<file_sep>import { HomeItemsViewModel } from './home-items-view-model'

export function onNavigatingTo(args) {
  const component = args.object
  component.bindingContext = new HomeItemsViewModel()
}

export function onItemTap(args) {
  const view = args.view
  const page = view.page
  const tappedItem = view.bindingContext

  page.frame.navigate({
    moduleName: 'home/home-item-detail/home-item-detail-page',
    context: tappedItem,
    animated: true,
    transition: {
      name: 'slide',
      duration: 200,
      curve: 'ease',
    },
  })
}
<file_sep>import { Application } from '@nativescript/core'

import { SearchViewModel } from './search-view-model'

export function onNavigatingTo(args) {
  const page = args.object
  page.bindingContext = new SearchViewModel()
}

export function onDrawerButtonTap(args) {
  const sideDrawer = Application.getRootView()
  sideDrawer.showDrawer()
}
<file_sep>import Vue from 'nativescript-vue'
import RadListView from 'nativescript-ui-listview/vue'

Vue.use(RadListView)

import App from './components/App'

Vue.config.silent = !__DEV__

new Vue({
  render: h => h(App)
}).$start()
<file_sep>import { platformNativeScript, runNativeScriptAngularApp } from '@nativescript/angular';

import { AppModule } from './app/app.module';

runNativeScriptAngularApp({
  appModuleBootstrap: () => platformNativeScript().bootstrapModule(AppModule),
});

<file_sep>import { Component, EventEmitter, Input, OnInit, Output, ViewContainerRef } from '@angular/core'
import { ModalDialogOptions, ModalDialogService, PageRoute } from '@nativescript/angular'
import { switchMap } from 'rxjs/operators'

import { CarEditService } from '../../shared/car-edit.service'
import { Car } from '../../shared/car.model'
import { MyListSelectorModalViewComponent } from './my-list-selector-modal-view.component'

const capitalizeFirstLetter = (s) => s.charAt(0).toUpperCase() + s.slice(1)

@Component({
  providers: [ModalDialogService],
  selector: 'MyListSelector',
  templateUrl: './my-list-selector.component.html',
})
export class MyListSelectorComponent implements OnInit {
  @Input() tag: string
  @Input() items: Array<string>
  @Input() selectedValue: string
  @Output() selectedValueChange = new EventEmitter<string>()

  private _carEditModel: Car

  constructor(
    private _pageRoute: PageRoute,
    private _modalService: ModalDialogService,
    private _vcRef: ViewContainerRef,
    private _carEditService: CarEditService
  ) {}

  ngOnInit(): void {
    let carId = ''

    this._pageRoute.activatedRoute
      .pipe(switchMap((activatedRoute) => activatedRoute.params))
      .forEach((params) => {
        carId = params.id
      })

    this._carEditModel = this._carEditService.getEditableCarById(carId)
  }

  onSelectorTap(): void {
    const title = `Select Car ${capitalizeFirstLetter(this.tag)}`
    const selectedIndex = this.items.indexOf(this.selectedValue)
    const options: ModalDialogOptions = {
      viewContainerRef: this._vcRef,
      context: {
        items: this.items,
        title,
        selectedIndex,
      },
      fullscreen: false,
    }

    this._modalService
      .showModal(MyListSelectorModalViewComponent, options)
      .then((selectedValue: string) => {
        if (selectedValue) {
          this.selectedValue = selectedValue
          this.selectedValueChange.emit(this.selectedValue)
        }
      })
  }
}
<file_sep>import { Frame } from '@nativescript/core'

import { CarDetailViewModel } from './car-detail-view-model'

export function onNavigatingTo(args) {
  if (args.isBackNavigation) {
    return
  }

  const page = args.object

  page.bindingContext = new CarDetailViewModel(page.navigationContext)
}

export function onBackButtonTap() {
  Frame.topmost().goBack()
}

export function onEditButtonTap(args) {
  const bindingContext = args.object.bindingContext

  Frame.topmost().navigate({
    moduleName: 'cars/car-detail-edit-page/car-detail-edit-page',
    context: bindingContext.car,
    animated: true,
    transition: {
      name: 'slideTop',
      duration: 200,
      curve: 'ease',
    },
  })
}

<file_sep>import { Observable } from '@nativescript/core'

import { carClassList, carDoorList, carSeatList, carTransmissionList } from './constants'

export class ListSelectorViewModel extends Observable {
  private _items: Array<any>
  private _tag: string
  private _selectedIndex: number

  constructor(context: any, private _closeCallback: Function) {
    super()

    this._tag = context.tag

    const protoItems = this.resolveProtoItems()
    this._selectedIndex = protoItems.indexOf(context.selectedValue)
    this._items = []
    for (let i = 0; i < protoItems.length; i++) {
      this._items.push({
        value: protoItems[i],
        isSelected: i === this._selectedIndex ? true : false,
      })
    }
  }

  selectItem(newSelectedIndex: number): void {
    const oldSelectedItem = this._items[this._selectedIndex]
    oldSelectedItem.isSelected = false

    const newSelectedItem = this._items[newSelectedIndex]
    newSelectedItem.isSelected = true
    this._selectedIndex = newSelectedIndex

    this._closeCallback(newSelectedItem.value)
  }

  cancelSelection(): void {
    this._closeCallback(null)
  }

  get items(): Array<any> {
    return this._items
  }

  get title(): string {
    return `Select Car ${this.capitalizeFirstLetter(this._tag)}`
  }

  private resolveProtoItems(): Array<any> {
    switch (this._tag) {
      case 'class':
        return carClassList
      case 'doors':
        return carDoorList
      case 'seats':
        return carSeatList
      case 'transmission':
        return carTransmissionList
    }
  }

  private capitalizeFirstLetter(str: string): string {
    return str.charAt(0).toUpperCase() + str.slice(1)
  }
}
<file_sep>import { ShownModallyData, View } from '@nativescript/core'

import { ListSelectorViewModel } from './list-selector-view-model'

let viewModel: ListSelectorViewModel

export function onShownModally(args: ShownModallyData): void {
  viewModel = new ListSelectorViewModel(args.context, args.closeCallback)
  ;(<View>args.object).bindingContext = viewModel
}

export function onItemSelected(args): void {
  viewModel.selectItem(args.index)
}

export function onCancelButtonTap(): void {
  viewModel.cancelSelection()
}
<file_sep>import { catchError } from 'rxjs/operators'
import { Observable, throwError } from 'rxjs'
import { Car } from './car-model'
import ApiService  from "~/services/api.service";

const editableProperties = [
  'doors',
  'imageUrl',
  'luggage',
  'name',
  'price',
  'seats',
  'transmission',
  'class',
]

export function CarService() {
  if (CarService._instance) {
    throw new Error('Use CarService.getInstance() instead of new.')
  }

  this._cars = []
  this._subscriptionMap = new Map()
  CarService._instance = this

  this.getSubscription = function (key) {
    return this._subscriptionMap.get(key)
  }

  this.setSubscription = function (key, value) {
    this._subscriptionMap.set(key, value)
  }

  this.load = function () {
    return new Observable((observer) => {
      const path = 'cars'
      const onValueEvent = (snapshot) => {
        const results = this._handleSnapshot(snapshot)
        observer.next(results)
      }

      ApiService.addValueEventListener(onValueEvent, `/${path}`)
    }).pipe(catchError(this._handleErrors))
  }

  this.update = function (carModel) {
    const updateModel = cloneUpdateModel(carModel)

    return ApiService.update(`/cars/${carModel.id}`, updateModel)
  }

  this.uploadImage = function (remoteFullPath, localFullPath) {
    return ApiService.uploadFile({
      localFullPath,
      remoteFullPath,
      onProgress: null,
    })
  }

  this._handleSnapshot = function (data) {
    this._cars = []

    if (data) {
      for (const id in data) {
        if (data.hasOwnProperty(id)) {
          this._cars.push(new Car(data[id]))
        }
      }
    }

    return this._cars
  }

  this._handleErrors = function (error) {
    return throwError(error)
  }
}

CarService.getInstance = function () {
  return CarService._instance
}

CarService._instance = new CarService()

function cloneUpdateModel(car) {
  return editableProperties.reduce((a, e) => ((a[e] = car[e]), a), {}) // eslint-disable-line no-return-assign, no-sequences
}
<file_sep>import { Observable } from '@nativescript/core'

import { ObservableProperty } from '../../shared/observable-property-decorator'

export class Car extends Observable {
  @ObservableProperty() id: string
  @ObservableProperty() imageStoragePath: string
  @ObservableProperty() seats: string
  @ObservableProperty() luggage: number
  @ObservableProperty() class: string
  @ObservableProperty() hasAC: boolean
  @ObservableProperty() doors: number
  @ObservableProperty() price: number
  @ObservableProperty() transmission: string
  @ObservableProperty() isModelValid: boolean

  private _name: string
  private _imageUrl: string

  constructor(options: any) {
    super()

    this._name = options.name
    this._imageUrl = options.imageUrl

    this.id = options.id
    this.seats = options.seats
    this.luggage = Number(options.luggage)
    this.class = options.class
    this.hasAC = options.hasAC
    this.doors = Number(options.doors)
    this.price = Number(options.price)
    this.transmission = options.transmission
    this.imageStoragePath = options.imageStoragePath

    this.updateDependentProperties()
  }

  get name(): string {
    return this._name
  }

  set name(value: string) {
    if (this._name === value) {
      return
    }

    this._name = value
    this.notifyPropertyChange('name', value)

    this.updateDependentProperties()
  }

  get imageUrl(): string {
    return this._imageUrl
  }

  set imageUrl(value: string) {
    if (this._imageUrl === value) {
      return
    }

    this._imageUrl = value
    this.notifyPropertyChange('imageUrl', value)

    this.updateDependentProperties()
  }

  private updateDependentProperties(): void {
    this.isModelValid = !!this._name && !!this._imageUrl
  }
}
<file_sep>import Vue from 'nativescript-vue'

import Home from './components/Home'

new Vue({
  render: (h) => h('frame', [h(Home)]),
}).$start()
<file_sep>import { Observable } from '@nativescript/core'

import { Car } from '../shared/car-model'

export class CarDetailViewModel extends Observable {
  constructor(private _car: Car) {
    super()
  }

  get car(): Car {
    return this._car
  }
}
<file_sep>import { Frame, NavigatedData, Page } from '@nativescript/core'

import { CarDetailViewModel } from './car-detail-view-model'

export function onNavigatingTo(args: NavigatedData): void {
  if (args.isBackNavigation) {
    return
  }

  const page = <Page>args.object

  page.bindingContext = new CarDetailViewModel(page.navigationContext)
}

export function onBackButtonTap(): void {
  Frame.topmost().goBack()
}

export function onEditButtonTap(args): void {
  const bindingContext = <CarDetailViewModel>args.object.bindingContext

  Frame.topmost().navigate({
    moduleName: 'cars/car-detail-edit-page/car-detail-edit-page',
    context: bindingContext.car,
    animated: true,
    transition: {
      name: 'slideTop',
      duration: 200,
      curve: 'ease',
    },
  })
}
<file_sep>import { Component, OnDestroy, OnInit } from '@angular/core'
import { RouterExtensions } from '@nativescript/angular'
import { ListViewEventData } from 'nativescript-ui-listview'
import { Subscription } from 'rxjs'
import { finalize } from 'rxjs/operators'
import { ObservableArray } from '@nativescript/core'

import { Car } from './shared/car.model'
import { CarService } from './shared/car.service'

@Component({
  selector: 'CarsList',
  templateUrl: './car-list.component.html',
  styleUrls: ['./car-list.component.scss'],
})
export class CarListComponent implements OnInit, OnDestroy {
  private _isLoading: boolean = false
  private _cars: ObservableArray<Car> = new ObservableArray<Car>([])
  private _dataSubscription: Subscription

  constructor(private _carService: CarService, private _routerExtensions: RouterExtensions) {}

  ngOnInit(): void {
    if (!this._dataSubscription) {
      this._isLoading = true

      this._dataSubscription = this._carService
        .load()
        .pipe(finalize(() => (this._isLoading = false)))
        .subscribe((cars: Array<Car>) => {
          this._cars = new ObservableArray(cars)
          this._isLoading = false
        })
    }
  }

  ngOnDestroy(): void {
    if (this._dataSubscription) {
      this._dataSubscription.unsubscribe()
      this._dataSubscription = null
    }
  }

  get cars(): ObservableArray<Car> {
    return this._cars
  }

  get isLoading(): boolean {
    return this._isLoading
  }

  onCarItemTap(args: ListViewEventData): void {
    const tappedCarItem = args.view.bindingContext

    this._routerExtensions.navigate(['/cars/car-detail', tappedCarItem.id], {
      animated: true,
      transition: {
        name: 'slide',
        duration: 200,
        curve: 'ease',
      },
    })
  }
}
<file_sep>import { Observable } from '@nativescript/core'

export class HelloWorldModel extends Observable {
  private _counter: number
  private _message: string

  constructor() {
    super()

    // Initialize default values.
    this._counter = 42
    this.updateMessage()
  }

  get message(): string {
    return this._message
  }

  set message(value: string) {
    if (this._message !== value) {
      this._message = value
      this.notifyPropertyChange('message', value)
    }
  }

  onTap() {
    this._counter--
    this.updateMessage()
  }

  private updateMessage() {
    if (this._counter <= 0) {
      this.message = 'Hoorraaay! You unlocked the NativeScript clicker achievement!'
    } else {
      this.message = `${this._counter} taps left`
    }
  }
}
<file_sep>import { Frame, Application } from '@nativescript/core';

import { AppRootViewModel } from './app-root-view-model'

export function onLoaded(args) {
  const drawerComponent = args.object
  drawerComponent.bindingContext = new AppRootViewModel()
}

export function onNavigationItemTap(args) {
  const component = args.object
  const componentRoute = component.route
  const componentTitle = component.title
  const bindingContext = component.bindingContext

  bindingContext.set('selectedPage', componentTitle)

  Frame.topmost().navigate({
    moduleName: componentRoute,
    transition: {
      name: 'fade',
    },
  })

  const drawerComponent = Application.getRootView()
  drawerComponent.closeDrawer()
}

<file_sep>import { Application } from '@nativescript/core'

export const showDrawer = () => {
  let drawerNativeView = Application.getRootView()
  if (drawerNativeView && drawerNativeView.showDrawer) {
    drawerNativeView.showDrawer()
  }
}

export const closeDrawer = () => {
  let drawerNativeView = Application.getRootView()
  if (drawerNativeView && drawerNativeView.showDrawer) {
    drawerNativeView.closeDrawer()
  }
}
<file_sep>import * as imagePicker from '@nativescript/imagepicker'
import { Observable, Folder, knownFolders, path, ImageAsset, ImageSource } from '@nativescript/core'

import { ObservableProperty } from '../../shared/observable-property-decorator'
import { Car } from '../shared/car-model'
import { CarService } from '../shared/car-service'
import { RoundingValueConverter } from './roundingValueConverter'
import { VisibilityValueConverter } from './visibilityValueConverter'

const tempImageFolderName = 'nsimagepicker'

export class CarDetailEditViewModel extends Observable {
  static get imageTempFolder(): Folder {
    return knownFolders.temp().getFolder(tempImageFolderName)
  }

  private static clearImageTempFolder(): void {
    CarDetailEditViewModel.imageTempFolder.clear()
  }

  @ObservableProperty() car: Car
  @ObservableProperty() isUpdating: boolean

  private _roundingValueConverter: RoundingValueConverter
  private _visibilityValueConverter: VisibilityValueConverter
  private _isCarImageDirty: boolean
  private _carService: CarService

  constructor(car: Car) {
    super()

    // get a fresh editable copy of car model
    this.car = new Car(car)

    this.isUpdating = false

    this._carService = CarService.getInstance()
    this._isCarImageDirty = false

    // set up value converter to force iOS UISlider to work with discrete steps
    this._roundingValueConverter = new RoundingValueConverter()

    this._visibilityValueConverter = new VisibilityValueConverter()
  }

  get roundingValueConverter(): RoundingValueConverter {
    return this._roundingValueConverter
  }

  get visibilityValueConverter(): VisibilityValueConverter {
    return this._visibilityValueConverter
  }

  saveChanges(): Promise<any> {
    let queue = Promise.resolve()

    this.isUpdating = true

    // TODO: car image should be required field
    if (this._isCarImageDirty && this.car.imageUrl) {
      queue = queue
        .then(() => {
          // no need to explicitly delete old image as upload to an existing remote path overwrites it
          const localFullPath = this.car.imageUrl
          const remoteFullPath = this.car.imageStoragePath

          return this._carService.uploadImage(remoteFullPath, localFullPath)
        })
        .then((uploadedFile: any) => {
          this.car.imageUrl = uploadedFile.url

          this._isCarImageDirty = false
        })
    }

    return queue
      .then(() => {
        return this._carService.update(this.car)
      })
      .then(() => (this.isUpdating = false))
      .catch((errorMessage: any) => {
        this.isUpdating = false
        throw errorMessage
      })
  }

  onImageAddRemove(): void {
    if (this.car.imageUrl) {
      this.handleImageChange(null)

      return
    }

    CarDetailEditViewModel.clearImageTempFolder()

    this.pickImage()
  }

  private pickImage(): void {
    const context = imagePicker.create({
      mode: 'single',
    })

    context
      .authorize()
      .then(() => context.present())
      .then((selection) =>
        selection.forEach((selectedAsset: ImageAsset) => {
          selectedAsset.options.height = 768
          ImageSource.fromAsset(selectedAsset).then((imageSource: ImageSource) =>
            this.handleImageChange(imageSource)
          )
        })
      )
      .catch((errorMessage: any) => console.log(errorMessage))
  }

  private handleImageChange(source: ImageSource): void {
    let raisePropertyChange = true
    let tempImagePath = null

    if (source) {
      tempImagePath = path.join(CarDetailEditViewModel.imageTempFolder.path, `${Date.now()}.jpg`)
      raisePropertyChange = source.saveToFile(tempImagePath, 'jpeg')
    }

    if (raisePropertyChange) {
      this.car.imageUrl = tempImagePath
      this._isCarImageDirty = true
    }
  }
}
<file_sep>import { ListViewEventData } from 'nativescript-ui-listview'
import { Frame, NavigatedData, Page } from '@nativescript/core'

import { CarsListViewModel } from './cars-list-view-model'
import { Car } from './shared/car-model'

export function onNavigatingTo(args: NavigatedData): void {
  if (args.isBackNavigation) {
    return
  }

  const viewModel = new CarsListViewModel()
  viewModel.load()

  const page = <Page>args.object
  page.bindingContext = viewModel
}

export function onCarItemTap(args: ListViewEventData): void {
  const tappedCarItem = <Car>args.view.bindingContext

  Frame.topmost().navigate({
    moduleName: 'cars/car-detail-page/car-detail-page',
    context: tappedCarItem,
    animated: true,
    transition: {
      name: 'slide',
      duration: 200,
      curve: 'ease',
    },
  })
}
<file_sep>import { Application } from '@nativescript/core'

Application.run({ moduleName: 'app-root' })

/*
Do not place any code after the application has been started as it will not
be executed on iOS.
*/
<file_sep>import Vue from 'nativescript-vue'
import RadSideDrawer from 'nativescript-ui-sidedrawer/vue'

Vue.use(RadSideDrawer)

import App from './components/App'

Vue.config.silent = !__DEV__

new Vue({
  render: h => h(App)
}).$start()
<file_sep>import { Observable } from '@nativescript/core'
import { Item } from './shared/item'

export class HomeViewModel extends Observable {
  items: Array<Item>

  constructor() {
    super()

    this.items = new Array<Item>(
      {
        name: 'Item 1',
        description: 'Description for Item 1',
      },
      {
        name: 'Item 2',
        description: 'Description for Item 2',
      },
      {
        name: 'Item 3',
        description: 'Description for Item 3',
      },
      {
        name: 'Item 4',
        description: 'Description for Item 4',
      },
      {
        name: 'Item 5',
        description: 'Description for Item 5',
      },
      {
        name: 'Item 6',
        description: 'Description for Item 6',
      },
      {
        name: 'Item 7',
        description: 'Description for Item 7',
      },
      {
        name: 'Item 8',
        description: 'Description for Item 8',
      },
      {
        name: 'Item 9',
        description: 'Description for Item 9',
      },
      {
        name: 'Item 10',
        description: 'Description for Item 10',
      },
      {
        name: 'Item 11',
        description: 'Description for Item 11',
      },
      {
        name: 'Item 12',
        description: 'Description for Item 12',
      },
      {
        name: 'Item 13',
        description: 'Description for Item 13',
      },
      {
        name: 'Item 14',
        description: 'Description for Item 14',
      },
      {
        name: 'Item 15',
        description: 'Description for Item 15',
      },
      {
        name: 'Item 16',
        description: 'Description for Item 16',
      }
    )
  }
}
<file_sep>import { Component, OnInit } from '@angular/core'
import { PageRoute, RouterExtensions } from '@nativescript/angular'
import { switchMap } from 'rxjs/operators'

import { Car } from '../shared/car.model'
import { CarService } from '../shared/car.service'

@Component({
  selector: 'CarDetail',
  templateUrl: './car-detail.component.html',
})
export class CarDetailComponent implements OnInit {
  private _car: Car

  constructor(
    private _carService: CarService,
    private _pageRoute: PageRoute,
    private _routerExtensions: RouterExtensions
  ) {}

  ngOnInit(): void {
    this._pageRoute.activatedRoute
      .pipe(switchMap((activatedRoute) => activatedRoute.params))
      .forEach((params) => {
        const carId = params.id

        this._car = this._carService.getCarById(carId)
      })
  }

  get car(): Car {
    return this._car
  }

  onBackButtonTap(): void {
    this._routerExtensions.backToPreviousPage()
  }

  onEditButtonTap(): void {
    this._routerExtensions.navigate(['/cars/car-detail-edit', this._car.id], {
      animated: true,
      transition: {
        name: 'slideTop',
        duration: 200,
        curve: 'ease',
      },
    })
  }
}
<file_sep>const sveltePreprocess = require('svelte-preprocess')
const svelteNativePreprocessor = require('svelte-native-preprocessor')

module.exports = {
  compilerOptions: {
    namespace: "foreign"
  },
  preprocess: [ sveltePreprocess(), svelteNativePreprocessor() ]
}<file_sep>import { Injectable } from '@angular/core'

export interface DataItem {
  id: number
  name: string
  description: string
}

@Injectable({
  providedIn: 'root',
})
export class DataService {
  private items = new Array<DataItem>(
    {
      id: 1,
      name: 'Item 1',
      description: 'Description for Item 1',
    },
    {
      id: 2,
      name: 'Item 2',
      description: 'Description for Item 2',
    },
    {
      id: 3,
      name: 'Item 3',
      description: 'Description for Item 3',
    },
    {
      id: 4,
      name: 'Item 4',
      description: 'Description for Item 4',
    },
    {
      id: 5,
      name: 'Item 5',
      description: 'Description for Item 5',
    },
    {
      id: 6,
      name: 'Item 6',
      description: 'Description for Item 6',
    },
    {
      id: 7,
      name: 'Item 7',
      description: 'Description for Item 7',
    },
    {
      id: 8,
      name: 'Item 8',
      description: 'Description for Item 8',
    },
    {
      id: 9,
      name: 'Item 9',
      description: 'Description for Item 9',
    },
    {
      id: 10,
      name: 'Item 10',
      description: 'Description for Item 10',
    },
    {
      id: 11,
      name: 'Item 11',
      description: 'Description for Item 11',
    },
    {
      id: 12,
      name: 'Item 12',
      description: 'Description for Item 12',
    },
    {
      id: 13,
      name: 'Item 13',
      description: 'Description for Item 13',
    },
    {
      id: 14,
      name: 'Item 14',
      description: 'Description for Item 14',
    },
    {
      id: 15,
      name: 'Item 15',
      description: 'Description for Item 15',
    },
    {
      id: 16,
      name: 'Item 16',
      description: 'Description for Item 16',
    },
    {
      id: 17,
      name: 'Item 17',
      description: 'Description for Item 17',
    },
    {
      id: 18,
      name: 'Item 18',
      description: 'Description for Item 18',
    },
    {
      id: 19,
      name: 'Item 19',
      description: 'Description for Item 19',
    },
    {
      id: 20,
      name: 'Item 20',
      description: 'Description for Item 20',
    }
  )

  getItems(): Array<DataItem> {
    return this.items
  }

  getItem(id: number): DataItem {
    return this.items.filter((item) => item.id === id)[0]
  }
}
<file_sep>import { ListSelectorViewModel } from './list-selector-view-model'

let viewModel

export function onShownModally(args) {
  const page = args.object

  viewModel = new ListSelectorViewModel(args.context, args.closeCallback)
  page.bindingContext = viewModel
}

export function onItemSelected(args) {
  viewModel.selectItem(args.index)
}

export function onCancelButtonTap() {
  viewModel.cancelSelection()
}
<file_sep>import { NavigatedData, Page } from '@nativescript/core'
import { SearchViewModel } from './search-view-model'

export function onNavigatingTo(args: NavigatedData) {
  const page = <Page>args.object
  page.bindingContext = new SearchViewModel()
}
<file_sep>
NOTE: This guide assumes that you have a working version of the master / detail NativeScript app set up locally.

### Add Firebase to your app

 - Follow the [Firebase documentation](https://firebase.google.com/docs/android/setup#manually_add_firebase) to add Firebase to your app (you will need a Firebase project and a Firebase configuration file for your iOS / Android app).
 - When prompted for your app's package name make sure it matches the appid generated for your NativeScript app (open the root `package.json` for your app in your favorite editor and find the "nativescript" node there -- the "id" child node is your appid)
 - Download the generated Firebase configuration files and replace the existing `app/App_Resources/iOS/GoogleService-Info.plist` and `app/App_Resources/Android/google-services.json` in your app (for iOS and Android respectively)
 - In Firebase go to project settings (the settings button is to the right of the **Overview** side menu item), copy the **Project ID** and replace the one in `app/shared/config.ts` with it. For example if your Project ID is **my-awesome-project-80fbb**, the updated config.ts will look like:
```typescript
export class Config {
    static firebaseBucket = "gs://my-awesome-project-80fbb.appspot.com/";
}
```

### Set up your Firebase sample data
 - Follow the [Firebase documentation](https://support.google.com/firebase/answer/6386780?hl=en#import) to import the [sample JSON data](https://github.com/NativeScript/nativescript-app-templates/blob/master/packages/template-master-detail-ng/tools/firebase/car-rental-export-public.json) to your Firebase project
 - Default database security rules in Firebase require users to be authenticated but [for simplicity] the current version of the NativeScript master / detail app does not provide built-in support for authentication / authorization. Follow the [Firebase documentation](https://firebase.google.com/docs/database/security/quickstart) to replace the default rules (DO NOT use this ruleset for a production app):
```json
{
  "rules": {
    "cars": {
      ".read": true,
      ".write": true
    }
  }
}
```
 - Default storage security rules in Firebase require users to be authenticated in order to upload new content (the master / detail "edit" functionality supports uploading new images to Firebase). Follow the [Firebase documentation](https://firebase.google.com/docs/storage/security/start) to replace the default storage security rules as [for simplicity] the master / detail app does not implement authentication / authorization in the current version (DO NOT use this ruleset for a production app):
```
service firebase.storage {
  match /b/{bucket}/o {
    match /cars/{allPaths=**} {
      allow read,write;
    }
  }
}
```
- You need to comment out the readonly section in this car detail edit page and uncomment the actual code: https://github.com/NativeScript/nativescript-app-templates/blob/master/packages/template-master-detail-ts/app/cars/car-detail-edit-page/car-detail-edit-page.ts
 
<file_sep>import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'
import { NativeScriptCommonModule, NativeScriptFormsModule } from '@nativescript/angular'
import { NativeScriptUIListViewModule } from 'nativescript-ui-listview/angular'

import { CarDetailEditComponent } from './car-detail-edit/car-detail-edit.component'
import { MyImageAddRemoveComponent } from './car-detail-edit/my-image-add-remove/my-image-add-remove.component'
import { MyListSelectorModalViewComponent } from './car-detail-edit/my-list-selector/my-list-selector-modal-view.component' 
import { MyListSelectorComponent } from './car-detail-edit/my-list-selector/my-list-selector.component'
import { CarDetailComponent } from './car-detail/car-detail.component'
import { CarListComponent } from './car-list.component'
import { CarsRoutingModule } from './cars-routing.module'

@NgModule({
  imports: [
    CarsRoutingModule,
    NativeScriptCommonModule,
    NativeScriptFormsModule,
    NativeScriptUIListViewModule,
  ],
  declarations: [
    CarListComponent,
    CarDetailComponent,
    CarDetailEditComponent,
    MyListSelectorComponent,
    MyListSelectorModalViewComponent,
    MyImageAddRemoveComponent,
  ],
  entryComponents: [MyListSelectorModalViewComponent],
  providers: [],
  schemas: [NO_ERRORS_SCHEMA],
})
export class CarsModule {}
<file_sep># NativeScript with Angular Drawer Navigation Template

App templates help you jump start your native cross-platform apps with built-in UI elements and best practices. Save time writing boilerplate code over and over again when you create new apps.

App template featuring a RadSideDrawer component for navigation. The RadSideDrawer component is part of [Progress NativeScript UI](https://github.com/telerik/nativescript-ui-feedback).

<img src="/packages/template-drawer-navigation-ng/tools/assets/phone-drawer-ios.png" height="400" /> <img src="/packages/template-drawer-navigation-ng/tools/assets/phone-drawer-android.png" height="400" />

## Key Features

- Side drawer navigation
- Five blank pages hooked to the drawer navigation
- Customizable theme
- UX and development best practices
- Easy to understand code

## Quick Start

Execute the following command to create an app from this template:

``` shell
ns create my-drawer-ng --template @nativescript/template-drawer-navigation-ng
```

> Note: This command will create a new NativeScript app that uses the latest version of this template published to [npm](https://www.npmjs.com/package/@nativescript/template-drawer-navigation-ng).

If you want to create a new app that uses the source of the template from the `master` branch, you can execute the following:

``` shell
# clone nativescript-app-templates monorepo locally
git clone git@github.com:NativeScript/nativescript-app-templates.git

# create app template from local source (all templates are in the 'packages' subfolder of the monorepo)
ns create my-drawer-ng --template nativescript-app-templates/packages/template-drawer-navigation-ng
```

**NB:** Please, have in mind that the master branch may refer to dependencies that are not on NPM yet!

## Walkthrough

### Architecture

The RadSideDrawer component is set up as an application root view in:

- `/src/app/app-component` - sets up the side drawer content and defines a page router outlet for the pages.

RadSideDrawer has the following component structure:

- `RadSideDrawer` - The component to display a drawer on the page.
- `tkDrawerContent` directive - Marks the component that will hold the drawer content.
- `tkMainContent` directive - Marks the component that will hold the app main content.

There are five blank components located in these folders:

- `/src/app/browse/browse.component`
- `/src/app/featured/featured.component`
- `/src/app/home/home.component`
- `/src/app/search/search.component`
- `/src/app/settings/settings.component`

### Styling

This template is set up to use SASS for styling. All classes used are based on the {N} core theme – consult the [documentation](https://github.com/NativeScript/theme) to understand how to customize it.

It has 3 global style files that are located at the root of the app folder:

- `/src/_app-common.scss` - the global common style sheet. These style rules are applied to both Android and iOS.
- `/src/app.android.scss` - the global Android style sheet. These style rules are applied to Android only.
- `/src/app.ios.scss` - the global iOS style sheet. These style rules are applied to iOS only.

## Get Help

The NativeScript framework has a vibrant community that can help when you run into problems.

Try [joining the NativeScript community Discord](https://nativescript.org/discord). The Discord channel is a great place to get help troubleshooting problems, as well as connect with other NativeScript developers.

If you have found an issue with this template, please report the problem in the [NativeScript repository](https://github.com/NativeScript/NativeScript/issues).

## Contributing

We love PRs, and accept them gladly. Feel free to propose changes and new ideas. We will review and discuss, so that they can be accepted and better integrated.
<file_sep>import { BrowseViewModel } from './browse-view-model'

export function onNavigatingTo(args) {
  const component = args.object
  component.bindingContext = new BrowseViewModel()
}
<file_sep>import { fromObject } from '@nativescript/core'

export function HomeViewModel() {
  const viewModel = fromObject({
    /* Add your view model properties here */
  })

  return viewModel
}
<file_sep>// @ts-check
const fs = require('fs');
const path = require('path');
const cpy = require('cpy')

const SHARED_PATH = path.resolve(__dirname, './shared')
const PACKAGES_PATH = path.resolve(__dirname, './packages')

const entries = fs.readdirSync(PACKAGES_PATH, {
    withFileTypes: true
})

for(const entry of entries) {
    if(!entry.isDirectory()) {
        continue;
    }

    prepareTemplate(path.resolve(PACKAGES_PATH, entry.name))
}

function prepareTemplate(templatePath) {
    console.log('processing ', templatePath)

    // Copy shared resources into template
    cpy(`**/*`, templatePath, {
        cwd: SHARED_PATH,
        overwrite: false,
        dot: true,
        parents: true
    })
}<file_sep>import { Injectable, NgZone } from '@angular/core'
import { Observable, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'
import ApiService  from "~/services/api.service";

import { Car } from './car.model'

const editableProperties = [
  'doors',
  'imageUrl',
  'luggage',
  'name',
  'price',
  'seats',
  'transmission',
  'class',
]

/* ***********************************************************
 * This is the master detail data service. It handles all the data operations
 * of retrieving and updating the data. In this case, it is connected to Firebase and
 * is using the {N} Firebase plugin. Learn more about it here:
 * https://github.com/EddyVerbruggen/nativescript-plugin-firebase
 * The {N} Firebase plugin needs some initialization steps before the app starts.
 * Check out how it is imported in the main.ts file and the actual script in /shared/firebase.common.ts file.
 *************************************************************/
@Injectable({
  providedIn: 'root',
})
export class CarService {
  private static cloneUpdateModel(car: Car): object {
    return editableProperties.reduce((a, e) => ((a[e] = car[e]), a), {})
  }

  private _cars: Array<Car> = []

  constructor(private _ngZone: NgZone) {}

  getCarById(id: string): Car {
    if (!id) {
      return
    }

    return this._cars.filter((car) => {
      return car.id === id
    })[0]
  }

  load(): Observable<any> {
    return new Observable((observer: any) => {
      const path = 'cars'

      const onValueEvent = (snapshot: any) => {
        this._ngZone.run(() => {
          const results = this.handleSnapshot(snapshot)
          observer.next(results)
        })
      }
      ApiService.addValueEventListener(onValueEvent, `/${path}`)
    }).pipe(catchError(this.handleErrors))
  }

  update(carModel: Car): Promise<any> {
    const updateModel = CarService.cloneUpdateModel(carModel)

    return ApiService.update('/cars/' + carModel.id, updateModel)
  }

  uploadImage(remoteFullPath: string, localFullPath: string): Promise<any> {
    return ApiService.uploadFile({
      localFullPath,
      remoteFullPath,
      onProgress: null,
    })
  }

  private handleSnapshot(data: any): Array<Car> {
    this._cars = []

    if (data) {
      for (const id in data) {
        if (data.hasOwnProperty(id)) {
          this._cars.push(new Car(data[id]))
        }
      }
    }

    return this._cars
  }

  private handleErrors(error: Response): Observable<never> {
    return throwError(error)
  }
}
<file_sep>import { RadSideDrawer } from 'nativescript-ui-sidedrawer'
import { Application, EventData, NavigatedData, Page } from '@nativescript/core'

import { SearchViewModel } from './search-view-model'

export function onNavigatingTo(args: NavigatedData) {
  const page = <Page>args.object
  page.bindingContext = new SearchViewModel()
}

export function onDrawerButtonTap(args: EventData) {
  const sideDrawer = <RadSideDrawer>Application.getRootView()
  sideDrawer.showDrawer()
}
<file_sep>import { Component } from '@angular/core'
import { ModalDialogParams } from '@nativescript/angular'

@Component({
  selector: 'MyListSelectorModalView',
  templateUrl: './my-list-selector-modal-view.component.html',
  styleUrls: ['./my-list-selector-modal-view.component.scss'],
})
export class MyListSelectorModalViewComponent {
  private _items: Array<any>
  private _selectedIndex: number
  private _title: string

  constructor(private _params: ModalDialogParams) {
    this._title = _params.context.title
    this._selectedIndex = _params.context.selectedIndex

    this._items = []
    for (let i = 0; i < _params.context.items.length; i++) {
      this._items.push({
        value: _params.context.items[i],
        isSelected: i === this._selectedIndex ? true : false,
      })
    }
  }

  onItemSelected(args): void {
    const oldSelectedItem = this._items[this._selectedIndex]
    oldSelectedItem.isSelected = false

    const newSelectedItem = this._items[args.index]
    newSelectedItem.isSelected = true
    this._selectedIndex = args.index

    this._params.closeCallback(newSelectedItem.value)
  }

  onCancelButtonTap(): void {
    this._params.closeCallback(null)
  }

  get items(): Array<any> {
    return this._items
  }

  get title(): string {
    return this._title
  }
}
<file_sep># NativeScript App Templates
App templates help you jump start your native cross-platform apps with built-in UI elements and best practices. Save time writing boilerplate code over and over again when you create new apps.

This monorepo contains the following NativeScript app templates:
- Hello World ([JavaScript](/packages/template-hello-world), [TypeScript](/packages/template-hello-world-ts), and [Angular](/packages/template-hello-world-ng))
- Blank ([JavaScript](/packages/template-blank), [TypeScript](/packages/template-blank-ts), [Angular](/packages/template-blank-ng), [Vue](/packages/template-blank-vue), [React](/packages/template-blank-react), and [Svelte](/packages/template-blank-svelte))
- Drawer Navigation ([JavaScript](/packages/template-drawer-navigation), [TypeScript](/packages/template-drawer-navigation-ts), [Angular](/packages/template-drawer-navigation-ng), and [Vue](/packages/template-drawer-navigation-vue))
- Tab Navigation ([JavaScript](/packages/template-tab-navigation), [TypeScript](/packages/template-tab-navigation-ts), [Angular](/packages/template-tab-navigation-ng), and [Vue](/packages/template-tab-navigation-vue))
- Master Detail with Firebase ([JavaScript](/packages/template-master-detail), [TypeScript](/packages/template-master-detail-ts), [Angular](/packages/template-master-detail-ng), and [Vue](/packages/template-master-detail-vue))
- Master Detail with Kinvey ([JavaScript](/packages/template-master-detail-kinvey), [TypeScript](/packages/template-master-detail-kinvey-ts), and [Angular](/packages/template-master-detail-kinvey-ng))

## Get Help
The NativeScript framework has a vibrant community that can help when you run into problems.

Try [joining the NativeScript community Discord](https://nativescript.org/discord). The Discord channel is a great place to get help troubleshooting problems, as well as connect with other NativeScript developers.

If you have found an issue with this template, please report the problem in the [NativeScript repository](https://github.com/NativeScript/NativeScript/issues).

## Contributing

We love PRs, and accept them gladly. Feel free to propose changes and new ideas. We will review and discuss, so that they can be accepted and better integrated.

## Releasing

The templates are automatically released (CI) once they are merged to the main branch as long as the `package.json` version has been updated. Manual releases are not recommended, just bumping the version & pushing to the main branch is enough. 

It is recommended to only bump the patch version when making changes - minor/major versions should generally be kep in sync with the NativeScript version (CLI).

<!-- This monorepo uses Lerna to manage packages and their releases.

Install the dependencies in the root of the repo with

```bash
$ npm i # or yarn
```

To list which packages have been changed since the last release

```bash
$ npx lerna changed
```

This will print something like this

```bash
lerna notice cli v3.14.2
lerna info versioning independent
lerna info Looking for changed packages since tns-template-blank-ng@6.5.4
lerna info ignoring diff in paths matching [ 'ignored-file', '*.md' ]
tns-template-hello-world-ng
lerna success found 1 package ready to publish
```

To manually specify version for each package interactively run

```bash
$ npx lerna version
```

This should prompt the new version number for every changed package.

To release the packages, run

```bash
$ npx lerna publish
```

If you want to bump and publish at the same time,

```bash
$ npx lerna publish
```

will also prompt the new version interactively and then publish the package to npm.
 -->
<file_sep>/**
 * A record of the navigation params for each route in your app.
 */
export type MainStackParamList = {
  One?: {};
  Two: {
    message: string;
  };
};
<file_sep># NativeScript Core Blank Template
App templates help you jump start your native cross-platform apps with built-in UI elements and best practices. Save time writing boilerplate code over and over again when you create new apps.

## Quick Start
Execute the following command to create an app from this template:

```
ns create my-blank-js --template @nativescript/template-blank
```

> Note: This command will create a new NativeScript app that uses the latest version of this template published to [npm](https://www.npmjs.com/package/@nativescript/template-blank).

If you want to create a new app that uses the source of the template from the `master` branch, you can execute the following:

```
# clone nativescript-app-templates monorepo locally
git clone <EMAIL>:NativeScript/nativescript-app-templates.git

# create app template from local source (all templates are in the 'packages' subfolder of the monorepo)
ns create my-blank-js --template nativescript-app-templates/packages/template-blank
```

**NB:** Please, have in mind that the master branch may refer to dependencies that are not on NPM yet!

## Walkthrough

### Architecture
The application root module:
- `/app/app-root` - sets up a Frame that lets you navigate between pages.

There is a single blank page module that sets up an empty layout:
- `/app/home/home-page`

**Home** page has the following components:
- `ActionBar` - It holds the title of the page.
- `GridLayout` - The main page layout that should contains all the page content.

## Get Help
The NativeScript framework has a vibrant community that can help when you run into problems.

Try [joining the NativeScript community Discord](https://nativescript.org/discord). The Discord channel is a great place to get help troubleshooting problems, as well as connect with other NativeScript developers.

If you have found an issue with this template, please report the problem in the [NativeScript repository](https://github.com/NativeScript/NativeScript/issues).

## Contributing

We love PRs, and accept them gladly. Feel free to propose changes and new ideas. We will review and discuss, so that they can be accepted and better integrated.
<file_sep>/*
In NativeScript, a file with the same name as an XML file is known as
a code-behind file. The code-behind is a great place to place your view
logic, and to set up your page’s data binding.
*/

import { NavigatedData, Page } from '@nativescript/core'

import { HomeViewModel } from './home-view-model'

export function onNavigatingTo(args: NavigatedData) {
  const page = <Page>args.object

  page.bindingContext = new HomeViewModel()
}
<file_sep>import * as React from 'react';
import * as ReactNativeScript from 'react-nativescript';
import { MainStack } from './components/MainStack';

// In NativeScript, the app.ts file is the entry point to your application. You
// can use this file to perform app-level initialization, but the primary
// purpose of the file is to pass control to the app’s first module.

// Controls react-nativescript log verbosity.
// - true: all logs;
// - false: only error logs.
Object.defineProperty(global, '__DEV__', { value: false });

ReactNativeScript.start(React.createElement(MainStack, {}, null));

// Do not place any code after the application has been started as it will not
// be executed on iOS.
<file_sep>import { Observable, Subscription, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'

import { Car } from './car-model'
import ApiService  from "~/services/api.service";

const editableProperties = [
  'doors',
  'imageUrl',
  'luggage',
  'name',
  'price',
  'seats',
  'transmission',
  'class',
]

export class CarService {
  static getInstance(): CarService {
    return CarService._instance
  }

  private static _instance: CarService = new CarService()

  private static cloneUpdateModel(car: Car): object {
    return editableProperties.reduce((a, e) => ((a[e] = car[e]), a), {})
  }

  private _subscriptionMap = new Map<string, Subscription>()
  private _cars: Array<Car> = []

  constructor() {
    if (CarService._instance) {
      throw new Error('Use CarService.getInstance() instead of new.')
    }

    CarService._instance = this
  }

  getSubscription(key: string): Subscription {
    return this._subscriptionMap.get(key)
  }

  setSubscription(key: string, value: Subscription): void {
    this._subscriptionMap.set(key, value)
  }

  load(): Observable<any> {
    return new Observable((observer: any) => {
      const path = 'cars'

      const onValueEvent = (snapshot: any) => {
        const results = this.handleSnapshot(snapshot)
        observer.next(results)
      }
      ApiService.addValueEventListener(onValueEvent, `/${path}`)
    }).pipe(catchError(this.handleErrors))
  }

  update(carModel: Car): Promise<any> {
    const updateModel = CarService.cloneUpdateModel(carModel)

    return ApiService.update(`/cars/${carModel.id}`, updateModel)
  }

  uploadImage(remoteFullPath: string, localFullPath: string): Promise<any> {
    return ApiService.uploadFile({
      localFullPath,
      remoteFullPath,
      onProgress: null,
    })
  }

  private handleSnapshot(data: any): Array<Car> {
    this._cars = []

    if (data) {
      for (const id in data) {
        if (data.hasOwnProperty(id)) {
          this._cars.push(new Car(data[id]))
        }
      }
    }

    return this._cars
  }

  private handleErrors(error: Response): Observable<any> {
    return throwError(error)
  }
}
<file_sep>import { RadSideDrawer } from 'nativescript-ui-sidedrawer'
import { Application, EventData, Frame, GridLayout } from '@nativescript/core'

import { AppRootViewModel } from './app-root-view-model'

export function onLoaded(args: EventData): void {
  const drawerComponent = <RadSideDrawer>args.object
  drawerComponent.bindingContext = new AppRootViewModel()
}

export function onNavigationItemTap(args: EventData): void {
  const component = <GridLayout>args.object
  const componentRoute = component.get('route')
  const componentTitle = component.get('title')
  const bindingContext = <AppRootViewModel>component.bindingContext

  bindingContext.selectedPage = componentTitle

  Frame.topmost().navigate({
    moduleName: componentRoute,
    transition: {
      name: 'fade',
    },
  })

  const drawerComponent = <RadSideDrawer>Application.getRootView()
  drawerComponent.closeDrawer()
}
<file_sep>import { Observable } from '@nativescript/core'

function getMessage(counter) {
  if (counter <= 0) {
    return 'Hoorraaay! You unlocked the NativeScript clicker achievement!'
  } else {
    return `${counter} taps left`
  }
}

export function createViewModel() {
  const viewModel = new Observable()
  viewModel.counter = 42
  viewModel.message = getMessage(viewModel.counter)

  viewModel.onTap = () => {
    viewModel.counter--
    viewModel.set('message', getMessage(viewModel.counter))
  }

  return viewModel
}

<file_sep>import { EventData, Dialogs, Frame, GridLayout, NavigatedData, Page } from '@nativescript/core'

import { CarDetailEditViewModel } from './car-detail-edit-view-model'

export function onNavigatingTo(args: NavigatedData): void {
  if (args.isBackNavigation) {
    return
  }

  const page = <Page>args.object

  page.bindingContext = new CarDetailEditViewModel(page.navigationContext)
}

export function onCancelButtonTap(args: EventData): void {
  Frame.topmost().goBack()
}

export function onDoneButtonTap(args: EventData): void {
  /* ***********************************************************
   * By design this app is set up to work with read-only sample data.
   * Follow the steps in the "Firebase database setup" section in app/readme.md file
   * and uncomment the code block below to make it editable.
   *************************************************************/

  /* ***********************************************************
    const actionItem = <ActionItem>args.object;
    const bindingContext = <CarDetailEditViewModel>actionItem.bindingContext;

    bindingContext.saveChanges()
        .then(() => Frame.topmost().navigate({
            moduleName: "cars/cars-list-page",
            clearHistory: true,
            animated: true,
            transition: {
                name: "slideBottom",
                duration: 200,
                curve: "ease"
            }
        }))
        .catch((errorMessage: any) =>
            Dialogs.alert({ title: "Oops!", message: "Something went wrong. Please try again.", okButtonText: "Ok" }));
    *************************************************************/

  /* ***********************************************************
   * Comment out the code block below if you made the app editable.
   *************************************************************/
  const readOnlyMessage =
    'Check out the "Firebase database setup" section in the readme file to make it editable.'
  const queue = Promise.resolve()
  queue
    .then(() =>
      Dialogs.alert({
        title: 'Read-Only Template!',
        message: readOnlyMessage,
        okButtonText: 'Ok',
      })
    )
    .then(() =>
      Frame.topmost().navigate({
        moduleName: 'cars/cars-list-page',
        clearHistory: true,
        animated: true,
        transition: {
          name: 'slideBottom',
          duration: 200,
          curve: 'ease',
        },
      })
    )
}

export function onSelectorTap(args: EventData): void {
  const gridLayout = <GridLayout>args.object
  const tag = gridLayout.get('tag')
  const bindingContext = <CarDetailEditViewModel>gridLayout.bindingContext
  const selectedValue = bindingContext.car[tag]
  const context = { tag, selectedValue }
  const modalPagePath = 'cars/list-selector-modal-page/list-selector-modal-page'
  const page = <Page>gridLayout.page

  page.showModal(modalPagePath, {
    context,
    closeCallback: (value: string) => {
      if (value) {
        bindingContext.car[tag] = value
      }
    },
    fullscreen: false,
  })
}

export function onImageAddRemoveTap(args: EventData): void {
  const gridLayout = <GridLayout>args.object
  const bindingContext = <CarDetailEditViewModel>gridLayout.bindingContext

  bindingContext.onImageAddRemove()
}
<file_sep>import { Observable } from '@nativescript/core'

import { SelectedPageService } from '../shared/selected-page-service'

export class BrowseViewModel extends Observable {
  constructor() {
    super()

    SelectedPageService.getInstance().updateSelectedPage('Browse')
  }
}
<file_sep>export class Car {
  id: string
  name: string
  hasAC: boolean
  seats: string
  luggage: number
  class: string
  doors: number
  price: number
  transmission: string
  imageUrl: string
  imageStoragePath: string

  constructor(options: any) {
    this.id = options.id
    this.name = options.name
    this.hasAC = options.hasAC
    this.seats = options.seats
    this.luggage = Number(options.luggage)
    this.class = options.class
    this.doors = Number(options.doors)
    this.price = Number(options.price)
    this.transmission = options.transmission
    this.imageUrl = options.imageUrl
    this.imageStoragePath = options.imageStoragePath
  }
}
<file_sep>import { fromObject } from '@nativescript/core'

export function HomeItemsViewModel() {
  const viewModel = fromObject({
    items: [
      {
        name: 'Item 1',
        description: 'Description for Item 1',
      },
      {
        name: 'Item 2',
        description: 'Description for Item 2',
      },
      {
        name: 'Item 3',
        description: 'Description for Item 3',
      },
      {
        name: 'Item 4',
        description: 'Description for Item 4',
      },
      {
        name: 'Item 5',
        description: 'Description for Item 5',
      },
      {
        name: 'Item 6',
        description: 'Description for Item 6',
      },
      {
        name: 'Item 7',
        description: 'Description for Item 7',
      },
      {
        name: 'Item 8',
        description: 'Description for Item 8',
      },
      {
        name: 'Item 9',
        description: 'Description for Item 9',
      },
      {
        name: 'Item 10',
        description: 'Description for Item 10',
      },
      {
        name: 'Item 11',
        description: 'Description for Item 11',
      },
      {
        name: 'Item 12',
        description: 'Description for Item 12',
      },
      {
        name: 'Item 13',
        description: 'Description for Item 13',
      },
      {
        name: 'Item 14',
        description: 'Description for Item 14',
      },
      {
        name: 'Item 15',
        description: 'Description for Item 15',
      },
      {
        name: 'Item 16',
        description: 'Description for Item 16',
      },
    ],
  })

  return viewModel
}
<file_sep>/*
In NativeScript, the app.js file is the entry point to your application.
You can use this file to perform app-level initialization, but the primary
purpose of the file is to pass control to the app’s first module.
*/

import { Application } from '@nativescript/core';

Application.run({ moduleName: 'app-root' })

/*
Do not place any code after the application has been started as it will not
be executed on iOS.
*/
<file_sep>import { Component, OnInit } from '@angular/core'
import { PageRoute, RouterExtensions } from '@nativescript/angular'
import { switchMap } from 'rxjs/operators'
import { Dialogs } from '@nativescript/core'

import { CarEditService } from '../shared/car-edit.service'
import { Car } from '../shared/car.model'
import { CarService } from '../shared/car.service'
import { carClassList, carDoorList, carSeatList, carTransmissionList } from './constants'

@Component({
  selector: 'CarDetailEdit',
  templateUrl: './car-detail-edit.component.html',
  styleUrls: ['./car-detail-edit.component.scss'],
})
export class CarDetailEditComponent implements OnInit {
  private _car: Car
  private _carClassOptions: Array<string> = []
  private _carDoorOptions: Array<number> = []
  private _carSeatOptions: Array<string> = []
  private _carTransmissionOptions: Array<string> = []
  private _isCarImageDirty: boolean = false
  private _isUpdating: boolean = false

  constructor(
    private _carService: CarService,
    private _carEditService: CarEditService,
    private _pageRoute: PageRoute,
    private _routerExtensions: RouterExtensions
  ) {}

  ngOnInit(): void {
    this.initializeEditOptions()

    this._pageRoute.activatedRoute
      .pipe(switchMap((activatedRoute) => activatedRoute.params))
      .forEach((params) => {
        const carId = params.id

        this._car = this._carEditService.startEdit(carId)
      })
  }

  get isUpdating(): boolean {
    return this._isUpdating
  }

  get car(): Car {
    return this._car
  }

  get pricePerDay(): number {
    return this._car.price
  }

  set pricePerDay(value: number) {
    // force iOS UISlider to work with discrete steps
    this._car.price = Math.round(value)
  }

  get luggageValue(): number {
    return this._car.luggage
  }

  set luggageValue(value: number) {
    // force iOS UISlider to work with discrete steps
    this._car.luggage = Math.round(value)
  }

  get carClassOptions(): Array<string> {
    return this._carClassOptions
  }

  get carDoorOptions(): Array<number> {
    return this._carDoorOptions
  }

  get carSeatOptions(): Array<string> {
    return this._carSeatOptions
  }

  get carTransmissionOptions(): Array<string> {
    return this._carTransmissionOptions
  }

  get carImageUrl(): string {
    return this._car.imageUrl
  }

  set carImageUrl(value: string) {
    this._car.imageUrl = value
    this._isCarImageDirty = true
  }

  onCancelButtonTap(): void {
    this._routerExtensions.backToPreviousPage()
  }

  onDoneButtonTap(): void {
    /* ***********************************************************
     * By design this app is set up to work with read-only sample data.
     * Follow the steps in the "Firebase database setup" section in app/readme.md file
     * and uncomment the code block below to make it editable.
     *************************************************************/

    /* ***********************************************************
        let queue = Promise.resolve();

        this._isUpdating = true;

        if (this._isCarImageDirty && this._car.imageUrl) {
            queue = queue
                .then(() => this._carService.uploadImage(this._car.imageStoragePath, this._car.imageUrl))
                .then((uploadedFile: any) => {
                    this._car.imageUrl = uploadedFile.url;
                });
        }

        queue.then(() => this._carService.update(this._car))
            .then(() => {
                this._isUpdating = false;
                this._routerExtensions.navigate(["/cars"], {
                    clearHistory: true,
                    animated: true,
                    transition: {
                        name: "slideBottom",
                        duration: 200,
                        curve: "ease"
                    }
                });
            })
            .catch((errorMessage: any) => {
                this._isUpdating = false;
                Dialogs.alert({ title: "Oops!", message: "Something went wrong. Please try again.", okButtonText: "Ok" });
            });
        *************************************************************/

    /* ***********************************************************
     * Comment out the code block below if you made the app editable.
     *************************************************************/
    const readOnlyMessage =
      'Check out the "Firebase database setup" section in the readme file to make it editable.'
    const queue = Promise.resolve()
    queue
      .then(() =>
        Dialogs.alert({
          title: 'Read-Only Template!',
          message: readOnlyMessage,
          okButtonText: 'Ok',
        })
      )
      .then(() =>
        this._routerExtensions.navigate(['/cars'], {
          clearHistory: true,
          animated: true,
          transition: {
            name: 'slideBottom',
            duration: 200,
            curve: 'ease',
          },
        })
      )
  }

  private initializeEditOptions(): void {
    for (const classItem of carClassList) {
      this._carClassOptions.push(classItem)
    }

    for (const doorItem of carDoorList) {
      this._carDoorOptions.push(doorItem)
    }

    for (const seatItem of carSeatList) {
      this._carSeatOptions.push(seatItem)
    }

    for (const transmissionItem of carTransmissionList) {
      this._carTransmissionOptions.push(transmissionItem)
    }
  }
}
<file_sep>import { BehaviorSubject, Observable } from 'rxjs'

export class SelectedPageService {
  static getInstance(): SelectedPageService {
    return SelectedPageService._instance
  }

  private static _instance: SelectedPageService = new SelectedPageService()

  selectedPage$: Observable<string>

  private _selectedPageSource: BehaviorSubject<string>

  constructor() {
    if (SelectedPageService._instance) {
      throw new Error('Use SelectedPageService.getInstance() instead of new.')
    }

    SelectedPageService._instance = this

    // Observable selectedPage source
    this._selectedPageSource = new BehaviorSubject<string>('Home')

    // Observable selectedPage stream
    this.selectedPage$ = this._selectedPageSource.asObservable()
  }

  updateSelectedPage(selectedPage: string) {
    this._selectedPageSource.next(selectedPage)
  }
}
<file_sep>import { Observable } from '@nativescript/core'

export function ObservableProperty() {
  return (target: Observable, propertyKey: string) => {
    Object.defineProperty(target, propertyKey, {
      get() {
        return this['_' + propertyKey]
      },
      set(value) {
        if (this['_' + propertyKey] === value) {
          return
        }

        this['_' + propertyKey] = value
        this.notifyPropertyChange(propertyKey, value)
      },
      enumerable: true,
      configurable: true,
    })
  }
}
<file_sep>/*
In NativeScript, the app.ts file is the entry point to your application.
You can use this file to perform app-level initialization, but the primary
purpose of the file is to pass control to the app’s first page.
*/

import { svelteNativeNoFrame } from 'svelte-native'
import App from './App.svelte'

svelteNativeNoFrame(App, {})
<file_sep># NativeScript TypeScript Template

This template creates a NativeScript app with the NativeScript hello world example,
however, in this template the example is built with TypeScript.

You can create a new app that uses this template with either the `--template` option.

```
ns create my-hello-world-ts --template @nativescript/template-hello-world-ts
```

Or the `--tsc` shorthand.

```
ns create my-hello-world-ts --tsc
```

> Note: Both commands will create a new NativeScript app that uses the latest version of this template published to [npm](https://www.npmjs.com/package/@nativescript/template-hello-world-ts).

If you want to create a new app that uses the source of the template from the `master` branch, you can execute the following:

```
# clone nativescript-app-templates monorepo locally
git clone <EMAIL>:NativeScript/nativescript-app-templates.git

# create app template from local source (all templates are in the 'packages' subfolder of the monorepo)
ns create my-hello-world-ts --template nativescript-app-templates/packages/template-hello-world-ts
```

## Get Help
The NativeScript framework has a vibrant community that can help when you run into problems.

Try [joining the NativeScript community Discord](https://nativescript.org/discord). The Discord channel is a great place to get help troubleshooting problems, as well as connect with other NativeScript developers.

If you have found an issue with this template, please report the problem in the [NativeScript repository](https://github.com/NativeScript/NativeScript/issues).

## Contributing

We love PRs, and accept them gladly. Feel free to propose changes and new ideas. We will review and discuss, so that they can be accepted and better integrated.
<file_sep>// @ts-ignore
import db from "./car-rental-export-public.json"

class ApiService {

  private db: any;

  constructor() {
    this.db = db;
  }

  async update(path, updateModel) {
    const carId = path.split("/").slice(-1)[0];
    this.db.cars[carId] = {...this.db.cars[carId], ...updateModel }
  }

  async uploadFile(params) {
    return {url: params.localFullPath}
  }

  addValueEventListener(onValueEvent, path) {
    onValueEvent(this.db.cars)
    return {
      path: path,
      listeners: this.db.cars
    }

  }
}

export default new ApiService;
<file_sep>import { fromObject } from '@nativescript/core'

export function CarDetailViewModel(carModel) {
  const viewModel = fromObject({
    car: carModel,
  })

  return viewModel
}
<file_sep>export class RoundingValueConverter {
  toView(value: number): number {
    return value
  }

  toModel(value: number): number {
    return Math.round(value)
  }
}
<file_sep>import { Injectable } from '@angular/core'

import { Car } from './car.model'
import { CarService } from './car.service'

@Injectable({
  providedIn: 'root',
})
export class CarEditService {
  private _editModel: Car

  constructor(private _carService: CarService) {}

  startEdit(id: string): Car {
    this._editModel = null

    return this.getEditableCarById(id)
  }

  getEditableCarById(id: string): Car {
    if (!this._editModel || this._editModel.id !== id) {
      const car = this._carService.getCarById(id)

      // get fresh editable copy of car model
      this._editModel = new Car(car)
    }

    return this._editModel
  }
}
<file_sep>import { Observable } from '@nativescript/core'

import { ObservableProperty } from '../shared/observable-property-decorator'
import { SelectedPageService } from '../shared/selected-page-service'

export class AppRootViewModel extends Observable {
  @ObservableProperty() selectedPage: string

  constructor() {
    super()

    SelectedPageService.getInstance().selectedPage$.subscribe(
      (selectedPage: string) => (this.selectedPage = selectedPage)
    )
  }
}
<file_sep>import { Component, forwardRef, Input, Output } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import * as imagePicker from '@nativescript/imagepicker'
import { Folder, knownFolders, path, ImageAsset, ImageSource } from '@nativescript/core'

const tempImageFolderName = 'nsimagepicker'
const noop = () => {}

const MY_IMAGE_ADD_REMOVE_CONTROL_VALUE_ACCESSOR = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => MyImageAddRemoveComponent),
  multi: true,
}

/* ***********************************************************
 * The MyImageAddRemove custom component uses an imagepicker plugin to let the user select
 * an image and provides custom logic and design to the process.
 *************************************************************/
@Component({
  selector: 'MyImageAddRemove',
  templateUrl: './my-image-add-remove.component.html',
  styleUrls: ['./my-image-add-remove.component.scss'],
  providers: [MY_IMAGE_ADD_REMOVE_CONTROL_VALUE_ACCESSOR],
})
export class MyImageAddRemoveComponent implements ControlValueAccessor {
  static get imageTempFolder(): Folder {
    return knownFolders.temp().getFolder(tempImageFolderName)
  }

  private static clearImageTempFolder(): void {
    MyImageAddRemoveComponent.imageTempFolder.clear()
  }

  // placeholder for the callback later provided by the ControlValueAccessor
  private propagateChange: (_: any) => void = noop

  private innerImageUrl: string = ''

  get imageUrl(): string {
    return this.innerImageUrl
  }

  set imageUrl(value: string) {
    if (this.innerImageUrl !== value) {
      this.innerImageUrl = value
      this.propagateChange(value)
    }
  }

  // ControlValueAccessor implementation
  writeValue(value: string) {
    if (this.innerImageUrl !== value) {
      this.innerImageUrl = value
    }
  }

  // ControlValueAccessor implementation
  registerOnChange(fn: any): void {
    this.propagateChange = fn
  }

  // ControlValueAccessor implementation
  registerOnTouched(fn: any): void {}

  onImageAddRemoveTap(): void {
    if (this.imageUrl) {
      this.handleImageChange(null)

      return
    }

    MyImageAddRemoveComponent.clearImageTempFolder()

    this.pickImage()
  }

  pickImage(): void {
    const context = imagePicker.create({
      mode: 'single',
    })

    context
      .authorize()
      .then(() => context.present())
      .then((selection) =>
        selection.forEach((selectedAsset: ImageAsset) => {
          selectedAsset.options.height = 768
          ImageSource.fromAsset(selectedAsset).then((imageSource: ImageSource) =>
            this.handleImageChange(imageSource)
          )
        })
      )
      .catch((errorMessage: any) => console.log(errorMessage))
  }

  private handleImageChange(source: ImageSource): void {
    let raisePropertyChange = true
    let tempImagePath = null

    if (source) {
      tempImagePath = path.join(MyImageAddRemoveComponent.imageTempFolder.path, `${Date.now()}.jpg`)
      raisePropertyChange = source.saveToFile(tempImagePath, 'jpeg')
    }

    if (raisePropertyChange) {
      this.imageUrl = tempImagePath
    }
  }
}
<file_sep>import { View, ItemEventData, NavigatedData, Page } from '@nativescript/core'

import { HomeViewModel } from './home-view-model'
import { Item } from './shared/item'

export function onNavigatingTo(args: NavigatedData) {
  const page = <Page>args.object
  page.bindingContext = new HomeViewModel()
}

export function onItemTap(args: ItemEventData) {
  const view = <View>args.view
  const page = <Page>view.page
  const tappedItem = <Item>view.bindingContext

  page.frame.navigate({
    moduleName: 'home/home-item-detail/home-item-detail-page',
    context: tappedItem,
    animated: true,
    transition: {
      name: 'slide',
      duration: 200,
      curve: 'ease',
    },
  })
}
<file_sep>import { SearchViewModel } from './search-view-model'

export function onNavigatingTo(args) {
  const component = args.object
  component.bindingContext = new SearchViewModel()
}
<file_sep>import { fromObject } from '@nativescript/core'

import { SelectedPageService } from '../shared/selected-page-service'

export function AppRootViewModel() {
  const viewModel = fromObject({
    selectedPage: '',
  })

  SelectedPageService.getInstance().selectedPage$.subscribe((selectedPage) => {
    viewModel.selectedPage = selectedPage
  })

  return viewModel
}
<file_sep>export class VisibilityValueConverter {
  toView(value: string): string {
    if (value) {
      return 'collapsed'
    } else {
      return 'visible'
    }
  }

  toModel(value: string): string {
    return value
  }
}
<file_sep>import { fromObject, ObservableArray } from '@nativescript/core'
import { finalize } from 'rxjs/operators'

import { CarService } from './shared/car-service'

const subscriptionKey = 'car-list-view-model'

export function CarsListViewModel() {
  const viewModel = fromObject({
    cars: new ObservableArray([]),
    isLoading: false,

    _carService: CarService.getInstance(),
    _dataSubscription: null,

    load: function () {
      if (!this._dataSubscription) {
        this.set('isLoading', true)

        // we need to be able to unsubscribe from data changes when old page got disposed / inaccessible but
        // we do not have equivalent of Angular ngOnInit / ngOnDestroy so it cannot be implicit solution i.e.
        // we should explicitly state when to unsubscribe e.g. in the following scenario master list #1 ->
        // car detail -> edit car detail -> master list #2 (forward nav with clearHistory)
        // we need to unsubscribe the master list #1 view model (as it can never be accessed again)
        // if cached subscription exists here, we know it is from a different (previous) instance of the same
        // view model and we need to unsubscribe from it
        const cachedSubscription = this._carService.getSubscription(subscriptionKey)
        if (cachedSubscription) {
          cachedSubscription.unsubscribe()
          this._carService.setSubscription(subscriptionKey, null)
        }

        this._dataSubscription = this._carService
          .load()
          .pipe(finalize(() => this.set('isLoading', false)))
          .subscribe((cars) => {
            this.set('cars', new ObservableArray(cars))
            this.set('isLoading', false)
          })

        this._carService.setSubscription(subscriptionKey, this._dataSubscription)
      }
    },

    unload: function () {
      if (this._dataSubscription) {
        this._dataSubscription.unsubscribe()
        this._dataSubscription = null
      }
    },
  })

  return viewModel
}

