Merge branch 'master' of github.com:driftyco/ionic-native

This commit is contained in:
Ibby 2017-03-04 08:11:46 -05:00
commit 8f1e729f0e
7 changed files with 730 additions and 2 deletions

View File

@ -10,6 +10,7 @@ import { AndroidFingerprintAuth } from './plugins/android-fingerprint-auth';
import { AppAvailability } from './plugins/appavailability';
import { Appodeal } from './plugins/appodeal';
import { AppRate } from './plugins/apprate';
import { AppPreferences } from './plugins/apppreferences';
import { AppUpdate } from './plugins/app-update';
import { AppVersion } from './plugins/appversion';
import { Badge } from './plugins/badge';
@ -64,6 +65,7 @@ import { GoogleMap } from './plugins/googlemap';
import { GoogleAnalytics } from './plugins/googleanalytics';
import { Gyroscope } from './plugins/gyroscope';
import { HeaderColor } from './plugins/headercolor';
import { Health } from './plugins/health';
import { Hotspot } from './plugins/hotspot';
import { HTTP } from './plugins/http';
import { Httpd } from './plugins/httpd';
@ -96,6 +98,7 @@ import { OneSignal } from './plugins/onesignal';
import { PhotoViewer } from './plugins/photo-viewer';
import { ScreenOrientation } from './plugins/screen-orientation';
import { PayPal } from './plugins/pay-pal';
import { PhotoLibrary } from './plugins/photo-library';
import { PinDialog } from './plugins/pin-dialog';
import { Pinterest } from './plugins/pinterest';
import { PowerManagement } from './plugins/power-management';
@ -138,6 +141,7 @@ export * from './plugins/admob';
export * from './plugins/alipay';
export * from './plugins/android-fingerprint-auth';
export * from './plugins/appavailability';
export * from './plugins/apppreferences';
export * from './plugins/appodeal';
export * from './plugins/apprate';
export * from './plugins/app-update';
@ -194,6 +198,7 @@ export * from './plugins/googleanalytics';
export * from './plugins/googlemap';
export * from './plugins/gyroscope';
export * from './plugins/headercolor';
export * from './plugins/health';
export * from './plugins/hotspot';
export * from './plugins/http';
export * from './plugins/httpd';
@ -224,6 +229,7 @@ export * from './plugins/network';
export * from './plugins/nfc';
export * from './plugins/onesignal';
export * from './plugins/pay-pal';
export * from './plugins/photo-library';
export * from './plugins/photo-viewer';
export * from './plugins/pin-dialog';
export * from './plugins/pinterest';
@ -270,6 +276,7 @@ window['IonicNative'] = {
Alipay,
AndroidFingerprintAuth,
AppAvailability,
AppPreferences,
Appodeal,
AppRate,
AppUpdate,
@ -325,6 +332,7 @@ window['IonicNative'] = {
GoogleAnalytics,
Gyroscope,
HeaderColor,
Health,
Hotspot,
HTTP,
Httpd,
@ -353,6 +361,7 @@ window['IonicNative'] = {
NavigationBar,
Network,
PayPal,
PhotoLibrary,
NFC,
Printer,
Push,

View File

@ -0,0 +1,133 @@
import { Cordova, Plugin } from './plugin';
import { Observable } from 'rxjs/Observable';
/**
* @name AppPreferences
* @description
* This plugin allows you to read and write app preferences
*
* @usage
* ```
* import { AppPreferences } from 'ionic-native';
*
* AppPreferences.fetch('key').then((res) => { console.log(res); });
*
*
*/
@Plugin({
pluginName: 'AppPreferences',
plugin: 'cordova-plugin-app-preferences', // npm package name, example: cordova-plugin-camera
pluginRef: 'plugins.appPreferences', // the variable reference to call the plugin, example: navigator.geolocation
repo: 'https://github.com/apla/me.apla.cordova.app-preferences', // the github repository URL for the plugin
})
export class AppPreferences {
/**
* Get a preference value
*
* @param {string} dict Dictionary for key (OPTIONAL)
* @param {string} key Key
* @return {Promise<any>} Returns a promise
*/
@Cordova({
sync: true,
callbackOrder: 'reverse'
})
static fetch(dict: string, key?: string): Promise<any> { return; }
/**
* Set a preference value
*
* @param {string} dict Dictionary for key (OPTIONAL)
* @param {string} key Key
* @param {string} value Value
* @return {Promise<any>} Returns a promise
*/
@Cordova({
callbackOrder: 'reverse'
})
static store(dict: string, key: string, value?: string): Promise<any> {
return;
}
/**
* Remove value from preferences
*
* @param {string} dict Dictionary for key (OPTIONAL)
* @param {string} key Key
* @return {Promise<any>} Returns a promise
*/
@Cordova({
callbackOrder: 'reverse'
})
static remove(dict: string, key?: string): Promise<any> { return; }
/**
* Clear preferences
*
* @return {Promise<any>} Returns a promise
*/
@Cordova({
callbackOrder: 'reverse'
})
static clearAll(): Promise<any> { return; }
/**
* Show native preferences interface
*
* @return {Promise<any>} Returns a promise
*/
@Cordova({
callbackOrder: 'reverse'
})
static show(): Promise<any> { return; }
/**
* Show native preferences interface
*
* @param {boolean} subscribe true value to subscribe, false - unsubscribe
* @return {Observable<any>} Returns an observable
*/
@Cordova({
observable: true
})
static watch(subscribe: boolean): Observable<any> { return; }
/**
* Return named configuration context
* In iOS you'll get a suite configuration, on Android named file
* Supports: Android, iOS
* @param {string} suiteName suite name
* @returns {Object} Custom object, bound to that suite
*/
@Cordova({
platforms: ['Android']
})
static suite(suiteName: string): Object { return; }
@Cordova({
platforms: ['iOS']
})
static iosSuite(suiteName: string): Object { return; }
/**
* Return cloud synchronized configuration context
* Currently supports Windows and iOS/macOS
* @returns {Object} Custom object, bound to that suite
*/
@Cordova({
platforms: ['iOS', 'Windows', 'Windows Phone 8']
})
static cloudSync(): Object { return; }
/**
* Return default configuration context
* Currently supports Windows and iOS/macOS
* @returns {Object} Custom Object, bound to that suite
*/
@Cordova({
platforms: ['iOS', 'Windows', 'Windows Phone 8']
})
static defaults(): Object { return; }
}

298
src/plugins/health.ts Normal file
View File

@ -0,0 +1,298 @@
import {Plugin, Cordova} from './plugin';
export interface QueryOptions {
/**
* Start date from which to get data
*/
startDate: Date;
/**
* End date from which to get data
*/
endDate: Date;
/**
* Datatype to be queried (see https://github.com/dariosalvi78/cordova-plugin-health#supported-data-types)
*/
dataType: string;
/**
* Optional limit the number of values returned. Defaults to 1000
*/
limit?: number;
/**
* Optional indicator to sort values ascending or descending
*/
ascending?: boolean;
/**
* In Android, it is possible to query for "raw" steps or to select those as filtered by the Google Fit app.
* In the latter case the query object must contain the field filtered: true.
*/
filtered?: boolean;
}
export interface QueryOptionsAggregated {
/**
* Start date from which to get data
*/
startDate: Date;
/**
* End date from which to get data
*/
endDate: Date;
/**
* Datatype to be queried (see https://github.com/dariosalvi78/cordova-plugin-health#supported-data-types)
*/
dataType: string;
/**
* if specified, aggregation is grouped an array of "buckets" (windows of time),
* supported values are: 'hour', 'day', 'week', 'month', 'year'.
*/
bucket: string;
}
export interface StoreOptions {
/**
* Start date from which to get data
*/
startDate: Date;
/**
* End date from which to get data
*/
endDate: Date;
/**
* Datatype to be queried (see https://github.com/dariosalvi78/cordova-plugin-health#supported-data-types)
*/
dataType: string;
/**
* Value of corresponding Datatype (see "Overview of valid datatypes")
*/
value: string;
/*
* The source that produced this data. In iOS this is ignored and
* set automatically to the name of your app.
*/
sourceName: string;
/*
* The complete package of the source that produced this data.
* In Android, if not specified, it's assigned to the package of the App. In iOS this is ignored and
* set automatically to the bunde id of the app.
*/
sourceBundleId: string;
}
export interface HealthData {
/**
* Start date from which to get data
*/
startDate: Date;
/**
* End date from which to get data
*/
endDate: Date;
/**
* Value of corresponding Datatype (see https://github.com/dariosalvi78/cordova-plugin-health#supported-data-types)
*/
value: string;
/**
* Unit of corresponding value of Datatype (see https://github.com/dariosalvi78/cordova-plugin-health#supported-data-types)
*/
unit: string;
/*
* The source that produced this data. In iOS this is ignored and
* set automatically to the name of your app.
*/
sourceName: string;
/*
* The complete package of the source that produced this data.
* In Android, if not specified, it's assigned to the package of the App. In iOS this is ignored and
* set automatically to the bunde id of the app.
*/
sourceBundleId: string;
}
/**
* @name Health
* @description
* A plugin that abstracts fitness and health repositories like Apple HealthKit or Google Fit.
*
* @usage
* ```
* import { Health } from 'ionic-native';
*
* ```
* See description at https://github.com/dariosalvi78/cordova-plugin-health for a full list of Datatypes and see examples.
*/
@Plugin({
pluginName: 'Health',
plugin: 'cordova-plugin-health',
pluginRef: 'navigator.health',
repo: 'https://github.com/dariosalvi78/cordova-plugin-health'
})
export class Health {
/**
* Tells if either Google Fit or HealthKit are available.
*
* @return {Promise<boolean>}
*/
@Cordova({
callbackOrder: 'reverse'
})
static isAvailable(): Promise<boolean> {
return;
};
/**
* Checks if recent Google Play Services and Google Fit are installed. If the play services are not installed,
* or are obsolete, it will show a pop-up suggesting to download them. If Google Fit is not installed,
* it will open the Play Store at the location of the Google Fit app.
* The plugin does not wait until the missing packages are installed, it will return immediately.
* If both Play Services and Google Fit are available, this function just returns without any visible effect.
*
* This function is only available on Android.
*
* @return {Promise<any>}
*/
@Cordova({
callbackOrder: 'reverse'
})
static promptInstallFit(): Promise<any> {
return;
};
/**
* Requests read and write access to a set of data types. It is recommendable to always explain why the app
* needs access to the data before asking the user to authorize it.
* This function must be called before using the query and store functions, even if the authorization has already
* been given at some point in the past.
*
* Quirks of requestAuthorization()
* In Android, it will try to get authorization from the Google Fit APIs.
* It is necessary that the app's package name and the signing key are registered in the Google API console.
* In Android, be aware that if the activity is destroyed (e.g. after a rotation) or is put in background,
* the connection to Google Fit may be lost without any callback. Going through the authorization will ensure that
* the app is connected again.
* In Android 6 and over, this function will also ask for some dynamic permissions if needed
* (e.g. in the case of "distance", it will need access to ACCESS_FINE_LOCATION).
*
* @param {Array<String>} datatypes a list of data types you want to be granted access to
*
* @return {Promise<any>}
*/
@Cordova()
static requestAuthorization(datatypes: Array<string>): Promise<any> {
return;
};
/**
* Check if the app has authorization to read/write a set of datatypes.
* This function is similar to requestAuthorization() and has similar quirks.
*
* @param {Array<String>} datatypes a list of data types you want to be granted access to
* @return {type: function(authorized)}, if the argument is true, the app is authorized
*/
@Cordova()
static isAuthorized(datatypes: Array<string>): Promise<any> {
return;
};
/**
* Gets all the data points of a certain data type within a certain time window.
* Warning: if the time span is big, it can generate long arrays!
*
* Quirks of query()
*
* In iOS, the amount of datapoints is limited to 1000 by default.
* You can override this by adding a limit: xxx to your query object.
* In iOS, datapoints are ordered in an descending fashion (from newer to older).
* You can revert this behaviour by adding ascending: true to your query object.
* In Android, it is possible to query for "raw" steps or to select those as filtered by the Google Fit app.
* In the latter case the query object must contain the field filtered: true.
* In Google Fit, calories.basal is returned as an average per day, and usually is not available in all days.
* In Google Fit, calories.active is computed by subtracting the basal calories from the total.
* As basal energy expenditure, an average is computed from the week before endDate.
* While Google Fit calculates basal and active calories automatically,
* HealthKit needs an explicit input from some app.
* When querying for activities, Google Fit is able to determine some activities automatically
* (still, walking, running, biking, in vehicle), while HealthKit only relies on the input of
* the user or of some external app.
* When querying for activities, calories and distance are also provided in HealthKit (units are kcal and meters) and
* never in Google Fit.
* When querying for nutrition, Google Fit always returns all the nutrition elements it has,
* while HealthKit returns only those that are stored as correlation. To be sure to get all stored the quantities
* (regardless of they are stored as correlation or not), it's better to query single nutrients.
* nutrition.vitamin_a is given in micrograms in HealthKit and International Unit in Google Fit.
* Automatic conversion is not trivial and depends on the actual substance.
*
* @param queryOptions
*
*/
@Cordova()
static query(queryOptions: QueryOptions): Promise<any> {
return;
};
/**
* Gets aggregated data in a certain time window. Usually the sum is returned for the given quantity.
*
* Quirks of queryAggregated()
* In Android, to query for steps as filtered by the Google Fit app, the flag filtered:
* true must be added into the query object.
* When querying for activities, calories and distance are provided
* when available in HealthKit and never in Google Fit.
* In Android, the start and end dates returned are the date of the first and the last available samples.
* If no samples are found, start and end may not be set.
* When bucketing, buckets will include the whole hour / day / month / week / year where start and end times
* fall into. For example, if your start time is 2016-10-21 10:53:34,
* the first daily bucket will start at 2016-10-21 00:00:00.
* Weeks start on Monday.
* When querying for nutrition, HealthKit returns only those that are stored as correlation.
* To be sure to get all the stored quantities, it's better to query single nutrients.
* nutrition.vitamin_a is given in micrograms in HealthKit and International Unit in Google Fit.
*
* @param queryOptionsAggregated
* @return {Promise<any>}
*/
@Cordova()
static queryAggregated(queryOptionsAggregated: QueryOptionsAggregated): Promise<any> {
return;
};
/**
* Stores a data point.
*
* Quirks of store()
*
* Google Fit doesn't allow you to overwrite data points that overlap with others already stored of the same type (see here). At the moment there is no support for update nor delete.
* In iOS you cannot store the total calories, you need to specify either basal or active. If you use total calories, the active ones will be stored.
* In Android you can only store active calories, as the basal are estimated automatically. If you store total calories, these will be treated as active.
* In iOS distance is assumed to be of type WalkingRunning, if you want to explicitly set it to Cycling you need to add the field cycling: true.
* In iOS storing the sleep activities is not supported at the moment.
* Storing of nutrients is not supported at the moment.
* @param storeOptions
* @return {Promise<any>}
*/
@Cordova()
static store(storeOptions: StoreOptions): Promise<any> {
return;
};
}

View File

@ -566,7 +566,7 @@ export class IBeacon {
/**
* See the docuemntation of {@code requestWhenInUseAuthorization} for further details.
* See the documentation of {@code requestWhenInUseAuthorization} for further details.
*
* @returns {Promise<void>} Returns a promise which is resolved when the native layer
* shows the request dialog.

View File

@ -0,0 +1,239 @@
import { Plugin, Cordova, CordovaFiniteObservable } from './plugin';
import { Observable } from 'rxjs/Observable';
/**
* @name PhotoLibrary
* @description
* The PhotoLibrary plugin allows access to photos from device by url. So you can use plain img tag to display photos and their thumbnails, and different 3rd party libraries as well.
* Saving photos and videos to the library is also supported.
* cdvphotolibrary urls should be trusted by Angular. See plugin homepage to learn how.
*
* @usage
* ```
* import { PhotoLibrary } from 'ionic-native';
*
* PhotoLibrary.requestAuthorization().then(() => {
* PhotoLibrary.getLibrary().subscribe({
* next: library => {
* library.forEach(function(libraryItem) {
* console.log(libraryItem.id); // ID of the photo
* console.log(libraryItem.photoURL); // Cross-platform access to photo
* console.log(libraryItem.thumbnailURL);// Cross-platform access to thumbnail
* console.log(libraryItem.fileName);
* console.log(libraryItem.width);
* console.log(libraryItem.height);
* console.log(libraryItem.creationDate);
* console.log(libraryItem.latitude);
* console.log(libraryItem.longitude);
* console.log(libraryItem.albumIds); // array of ids of appropriate AlbumItem, only of includeAlbumsData was used
* });
* },
* error: err => {},
* complete: () => { console.log("could not get photos"); }
* });
* })
* .catch(err => console.log("permissions weren't granted"));
*
* ```
*/
@Plugin({
pluginName: 'PhotoLibrary',
plugin: 'cordova-plugin-photo-library',
pluginRef: 'cordova.plugins.photoLibrary',
repo: 'https://github.com/terikon/cordova-plugin-photo-library',
install: 'ionic plugin add cordova-plugin-photo-library --variable PHOTO_LIBRARY_USAGE_DESCRIPTION="To choose photos"'
})
export class PhotoLibrary {
/**
* Retrieves library items. Library item contains photo metadata like width and height, as well as photoURL and thumbnailURL.
* @param options {GetLibraryOptions} Optional, like thumbnail size and chunks settings.
* @return {Observable<LibraryItem[]>} Returns library items. If appropriate option was set, will be returned by chunks.
*/
@CordovaFiniteObservable({
callbackOrder: 'reverse',
resultFinalPredicate: (result: {isLastChunk: boolean}) => { return result.isLastChunk; },
resultTransform: (result: {library: LibraryItem[]}) => { return result.library; },
})
static getLibrary(options?: GetLibraryOptions): Observable<LibraryItem[]> { return; }
/**
* Asks user permission to access photo library.
* @param options {RequestAuthorizationOptions} Optional, like whether only read access needed or read/write.
* @return { Promise<void>} Returns a promise that resolves when permissions are granted, and fails when not.
*/
@Cordova({
callbackOrder: 'reverse',
})
static requestAuthorization(options?: RequestAuthorizationOptions): Promise<void> { return; }
/**
* Returns list of photo albums on device.
* @return {Promise<AlbumItem[]>} Resolves to list of albums.
*/
@Cordova({
callbackOrder: 'reverse',
})
static getAlbums(): Promise<AlbumItem[]> { return; }
/**
* @private
*/
static getThumbnailURL(photoId: string, options?: GetThumbnailOptions): Promise<string>;
/**
* @private
*/
static getThumbnailURL(libraryItem: LibraryItem, options?: GetThumbnailOptions): Promise<string>;
/**
* Provides means to request URL of thumbnail, with specified size or quality.
* @param photo {string | LibraryItem} Id of photo, or LibraryItem.
* @param options {GetThumbnailOptions} Options, like thumbnail size or quality.
* @return {Promise<string>} Resolves to URL of cdvphotolibrary schema.
*/
@Cordova({
successIndex: 1,
errorIndex: 2
})
static getThumbnailURL(photo: string | LibraryItem, options?: GetThumbnailOptions): Promise<string> { return; }
/**
* @private
*/
static getPhotoURL(photoId: string, options?: GetPhotoOptions): Promise<string>;
/**
* @private
*/
static getPhotoURL(libraryItem: LibraryItem, options?: GetPhotoOptions): Promise<string>;
/**
* Provides means to request photo URL by id.
* @param photo {string | LibraryItem} Id or LibraryItem.
* @param options {GetPhotoOptions} Optional options.
* @return {Promise<string>} Resolves to URL of cdvphotolibrary schema.
*/
@Cordova({
successIndex: 1,
errorIndex: 2
})
static getPhotoURL(photo: string | LibraryItem, options?: GetPhotoOptions): Promise<string> { return; }
/**
* @private
*/
static getThumbnail(photoId: string, options?: GetThumbnailOptions): Promise<Blob>;
/**
* @private
*/
static getThumbnail(libraryItem: LibraryItem, options?: GetThumbnailOptions): Promise<Blob>;
/**
* Returns thumbnail as Blob.
* @param photo {string | LibraryItem} Id or LibraryItem.
* @param options {GetThumbnailOptions} Options, like thumbnail size or quality.
* @return {Promise<Blob>} Resolves requested thumbnail as blob.
*/
@Cordova({
successIndex: 1,
errorIndex: 2
})
static getThumbnail(photo: string | LibraryItem, options?: GetThumbnailOptions): Promise<Blob> { return; }
/**
* @private
*/
static getPhoto(photoId: string, options?: GetPhotoOptions): Promise<Blob>;
/**
* @private
*/
static getPhoto(libraryItem: LibraryItem, options?: GetPhotoOptions): Promise<Blob>;
/**
* Returns photo as Blob.
* @param photo {string | LibraryItem} Id or LibraryItem.
* @param options {GetPhotoOptions} Optional options.
* @return {Promise<Blob>} Resolves requested photo as blob.
*/
@Cordova({
successIndex: 1,
errorIndex: 2
})
static getPhoto(photo: string | LibraryItem, options?: GetPhotoOptions): Promise<Blob> { return; }
/**
* Saves image to specified album. Album will be created if not exists.
* LibraryItem that represents saved image is returned.
* @param url {string} URL of a file, or DataURL.
* @param album {AlbumItem | string} Name of an album or AlbumItem object.
* @param options {GetThumbnailOptions} Options, like thumbnail size for resulting LibraryItem.
* @return {Promise<LibraryItem>} Resolves to LibraryItem that represents saved image.
*/
@Cordova({
successIndex: 2,
errorIndex: 3
})
static saveImage(url: string, album: AlbumItem | string, options?: GetThumbnailOptions): Promise<LibraryItem> { return; }
/**
* Saves video to specified album. Album will be created if not exists.
* @param url {string} URL of a file, or DataURL.
* @param album {AlbumItem | string} Name of an album or AlbumItem object.
* @return {Promise<void>} Resolves when save operation completes.
*/
@Cordova({
successIndex: 2,
errorIndex: 3
})
static saveVideo(url: string, album: AlbumItem | string): Promise<void> { return; }
}
export interface LibraryItem {
/**
* Local id of the photo
*/
id: string;
/**
* URL of cdvphotolibrary schema.
*/
photoURL: string;
/**
* URL of cdvphotolibrary schema.
*/
thumbnailURL: string;
fileName: string;
width: number;
height: number;
creationDate: Date;
latitude?: number;
longitude?: number;
albumIds?: string[];
}
export interface AlbumItem {
/**
* Local id of the album
*/
id: string;
title: string;
}
export interface GetLibraryOptions {
thumbnailWidth?: number;
thumbnailHeight?: number;
quality?: number;
itemsInChunk?: number;
chunkTimeSec?: number;
useOriginalFileNames?: boolean;
includeAlbumData?: boolean;
}
export interface RequestAuthorizationOptions {
read?: boolean;
write?: boolean;
}
export interface GetThumbnailOptions {
thumbnailWidth?: number;
thumbnailHeight?: number;
quality?: number;
}
export interface GetPhotoOptions {
}

View File

@ -552,3 +552,52 @@ export function CordovaFunctionOverride(opts: any = {}) {
};
};
}
/**
* @private
*/
export interface CordovaFiniteObservableOptions extends CordovaOptions {
/**
* Function that gets a result returned from plugin's success callback, and decides whether it is last value and observable should complete.
*/
resultFinalPredicate?: (result: any) => boolean;
/**
* Function that gets called after resultFinalPredicate, and removes service data that indicates end of stream from the result.
*/
resultTransform?: (result: any) => any;
}
/**
* @private
*
* Wraps method that returns an observable that can be completed. Provided opts.resultFinalPredicate dictates when the observable completes.
*
*/
export function CordovaFiniteObservable(opts: CordovaFiniteObservableOptions = {}) {
if (opts.observable === false) {
throw new Error('CordovaFiniteObservable decorator can only be used on methods that returns observable. Please provide correct option.');
}
opts.observable = true;
return (target: Object, methodName: string, descriptor: TypedPropertyDescriptor<any>) => {
return {
value: function(...args: any[]) {
let wrappedObservable: Observable<any> = wrap(this, methodName, opts).apply(this, args);
return new Observable<any>((observer) => {
let wrappedSubscription = wrappedObservable.subscribe({
next: (x) => {
observer.next(opts.resultTransform ? opts.resultTransform(x) : x);
if (opts.resultFinalPredicate && opts.resultFinalPredicate(x)) {
observer.complete();
}
},
error: (err) => { observer.error(err); },
complete: () => { observer.complete(); }
});
return () => {
wrappedSubscription.unsubscribe();
};
});
}
};
};
}

View File

@ -81,7 +81,7 @@ export class SafariViewController {
* Hides Safari View Controller
*/
@Cordova()
static hide(): void { }
static hide(): Promise<any> { return; }
/**
* Tries to connect to the Chrome's custom tabs service. you must call this method before calling any of the other methods listed below.