Merge pull request #56 from tomfong/post-v3.0.0-dev

v3.0.1
This commit is contained in:
Tom Fong 2022-09-25 13:51:59 +08:00 committed by GitHub
commit 7f4c827772
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 2796 additions and 627 deletions

View file

@ -3,7 +3,11 @@
<component name="DesignSurface"> <component name="DesignSurface">
<option name="filePathToZoomLevelMap"> <option name="filePathToZoomLevelMap">
<map> <map>
<entry key="app/src/main/res/drawable/ic_baseline_qr_code_24.xml" value="0.2485" />
<entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.2485" /> <entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.2485" />
<entry key="app/src/main/res/drawable/splash_background.xml" value="0.2485" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.2485" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.2485" />
</map> </map>
</option> </option>
</component> </component>

View file

@ -6,8 +6,8 @@ android {
applicationId "com.tomfong.simpleqr" applicationId "com.tomfong.simpleqr"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 3000000 versionCode 3000100
versionName "3.0.0" versionName "3.0.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions { aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/colorPrimary"/>
<item android:gravity="center" android:drawable="@drawable/splash"/>
</layer-list>

View file

@ -16,6 +16,7 @@
</style> </style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen"> <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item> <item name="android:background">@drawable/splash_background</item>
<item name="android:windowBackground">@drawable/splash_background</item>
</style> </style>
</resources> </resources>

View file

@ -15,8 +15,8 @@
<item name="android:background">@null</item> <item name="android:background">@null</item>
</style> </style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen"> <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item> <item name="android:background">@drawable/splash_background</item>
<item name="android:windowBackground">@drawable/splash_background</item>
</style> </style>
</resources> </resources>

View file

@ -15,8 +15,8 @@
<item name="android:background">@null</item> <item name="android:background">@null</item>
</style> </style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen"> <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item> <item name="android:background">@drawable/splash_background</item>
<item name="android:windowBackground">@drawable/splash_background</item>
</style> </style>
</resources> </resources>

View file

@ -15,8 +15,8 @@
<item name="android:background">@null</item> <item name="android:background">@null</item>
</style> </style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen"> <item name="android:background">@drawable/splash_background</item>
<item name="android:background">@drawable/splash</item> <item name="android:windowBackground">@drawable/splash_background</item>
</style> </style>
</resources> </resources>

View file

@ -15,8 +15,8 @@
<item name="android:background">@null</item> <item name="android:background">@null</item>
</style> </style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen"> <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item> <item name="android:background">@drawable/splash_background</item>
<item name="android:windowBackground">@drawable/splash_background</item>
</style> </style>
</resources> </resources>

View file

@ -30,7 +30,10 @@
"output": "./svg" "output": "./svg"
} }
], ],
"styles": ["src/theme/variables.scss", "src/global.scss"], "styles": [
"src/theme/variables.scss",
"src/global.scss"
],
"scripts": [], "scripts": [],
"aot": false, "aot": false,
"vendorChunk": true, "vendorChunk": true,
@ -79,8 +82,7 @@
"production": { "production": {
"browserTarget": "app:build:production" "browserTarget": "app:build:production"
}, },
"ci": { "ci": {}
}
} }
}, },
"extract-i18n": { "extract-i18n": {
@ -132,30 +134,6 @@
"devServerTarget": "app:serve:ci" "devServerTarget": "app:serve:ci"
} }
} }
},
"ionic-cordova-build": {
"builder": "@ionic/angular-toolkit:cordova-build",
"options": {
"browserTarget": "app:build"
},
"configurations": {
"production": {
"browserTarget": "app:build:production"
}
}
},
"ionic-cordova-serve": {
"builder": "@ionic/angular-toolkit:cordova-serve",
"options": {
"cordovaBuildTarget": "app:ionic-cordova-build",
"devServerTarget": "app:serve"
},
"configurations": {
"production": {
"cordovaBuildTarget": "app:ionic-cordova-build:production",
"devServerTarget": "app:serve:production"
}
}
} }
} }
} }
@ -174,4 +152,4 @@
"styleext": "scss" "styleext": "scss"
} }
} }
} }

View file

@ -396,7 +396,7 @@
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 3.0.0; MARKETING_VERSION = 3.0.1;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.tomfong.simpleqr; PRODUCT_BUNDLE_IDENTIFIER = com.tomfong.simpleqr;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -419,7 +419,7 @@
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 3.0.0; MARKETING_VERSION = 3.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.tomfong.simpleqr; PRODUCT_BUNDLE_IDENTIFIER = com.tomfong.simpleqr;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Simple QR"; PROVISIONING_PROFILE_SPECIFIER = "Simple QR";

3258
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,13 @@
{ {
"name": "simple-qr", "name": "simple-qr",
"version": "3.0.0", "version": "3.0.1",
"author": "Tom Fong", "author": "Tom Fong",
"homepage": "https://tomfong.github.io", "homepage": "https://tomfong.github.io",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"build:an": "ionic cap build android --prod", "build:an": "ionic cap build android --prod --no-open",
"build:ios": "ionic cap build ios --prod", "build:ios": "ionic cap build ios --prod --no-open",
"build": "ionic cap build android --prod --no-open && ionic cap build ios --prod --no-open",
"sync": "ionic cap sync --prod --no-build", "sync": "ionic cap sync --prod --no-build",
"copy:an": "ionic cap copy android --prod", "copy:an": "ionic cap copy android --prod",
"copy:ios": "ionic cap copy ios --prod", "copy:ios": "ionic cap copy ios --prod",
@ -53,7 +54,7 @@
"@ionic/angular": "^6.2.8", "@ionic/angular": "^6.2.8",
"@ionic/storage": "^3.0.6", "@ionic/storage": "^3.0.6",
"@ionic/storage-angular": "^3.0.6", "@ionic/storage-angular": "^3.0.6",
"@ng-bootstrap/ng-bootstrap": "^11.0.1", "@ng-bootstrap/ng-bootstrap": "^13.0.0",
"@ngx-translate/core": "^13.0.0", "@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0", "@ngx-translate/http-loader": "^6.0.0",
"@techiediaries/ngx-qrcode": "^9.1.0", "@techiediaries/ngx-qrcode": "^9.1.0",
@ -64,11 +65,11 @@
"cordova-plugin-theme-detection": "^1.3.0", "cordova-plugin-theme-detection": "^1.3.0",
"cordova-plugin-x-socialsharing": "^6.0.4", "cordova-plugin-x-socialsharing": "^6.0.4",
"cordova-sms-plugin": "^1.0.2", "cordova-sms-plugin": "^1.0.2",
"date-fns": "2.29.3",
"es6-promise-plugin": "^4.2.2", "es6-promise-plugin": "^4.2.2",
"human-signals": "^2.1.0", "human-signals": "^2.1.0",
"jsqr": "^1.4.0", "jsqr": "^1.4.0",
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"moment": "^2.29.4",
"osenv": "^0.1.5", "osenv": "^0.1.5",
"properties-parser": "^0.3.1", "properties-parser": "^0.3.1",
"rxjs": "^6.6.7", "rxjs": "^6.6.7",
@ -85,11 +86,11 @@
"@angular/language-service": "^14.2.3", "@angular/language-service": "^14.2.3",
"@capacitor/cli": "^4.3.0", "@capacitor/cli": "^4.3.0",
"@ionic/angular-toolkit": "^6.1.0", "@ionic/angular-toolkit": "^6.1.0",
"@ionic/cli": "6.20.1",
"@types/jasmine": "^3.10.6", "@types/jasmine": "^3.10.6",
"@types/jasminewd2": "^2.0.10", "@types/jasminewd2": "^2.0.10",
"@types/node": "^12.20.55", "@types/node": "^12.20.55",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"codelyzer": "^6.0.2",
"jasmine-core": "~3.8.0", "jasmine-core": "~3.8.0",
"jasmine-spec-reporter": "~5.0.0", "jasmine-spec-reporter": "~5.0.0",
"karma": "^6.4.1", "karma": "^6.4.1",
@ -98,9 +99,7 @@
"karma-coverage-istanbul-reporter": "~3.0.2", "karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "^4.0.2", "karma-jasmine": "^4.0.2",
"karma-jasmine-html-reporter": "^1.7.0", "karma-jasmine-html-reporter": "^1.7.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0", "ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.8.3" "typescript": "~4.8.3"
} }
} }

View file

@ -4,7 +4,8 @@ import { Router } from '@angular/router';
import { Haptics, ImpactStyle, NotificationType } from '@capacitor/haptics'; import { Haptics, ImpactStyle, NotificationType } from '@capacitor/haptics';
import { AlertController, LoadingController, ToastController } from '@ionic/angular'; import { AlertController, LoadingController, ToastController } from '@ionic/angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import * as moment from 'moment'; // import * as moment from 'moment';
import { format } from 'date-fns';
import { EnvService } from 'src/app/services/env.service'; import { EnvService } from 'src/app/services/env.service';
import { Toast } from '@capacitor/toast'; import { Toast } from '@capacitor/toast';
import { fadeIn } from 'src/app/utils/animations'; import { fadeIn } from 'src/app/utils/animations';
@ -57,7 +58,7 @@ export class GeneratePage {
state: string = ""; state: string = "";
postalCode: string = ""; postalCode: string = "";
country: string = ""; country: string = "";
birthday: Date; birthday: string;
gender: "M" | "F" | "O" = "O"; gender: "M" | "F" | "O" = "O";
personalUrl: string = ""; personalUrl: string = "";
@ -320,9 +321,10 @@ export class GeneratePage {
vCard += `ORG:${this.organization.trim()}\n`; vCard += `ORG:${this.organization.trim()}\n`;
vCard += `TITLE:${this.jobTitle.trim()}\n`; vCard += `TITLE:${this.jobTitle.trim()}\n`;
vCard += `ADR:;;${this.street.trim()};${this.city.trim()};${this.state.trim()};${this.postalCode.trim()};${this.country.trim()}\n`; vCard += `ADR:;;${this.street.trim()};${this.city.trim()};${this.state.trim()};${this.postalCode.trim()};${this.country.trim()}\n`;
console.log("birthday => " + this.birthday) if (this.birthday != null) {
if (this.birthday && this.birthday !== null && this.birthday !== undefined) { const find = '-';
vCard += `BDAY:${moment(this.birthday).format('YYYYMMDD')}\n`; const re = new RegExp(find, 'g');
vCard += `BDAY:${this.birthday.replace(re, "")}\n`;
} }
vCard += `URL:${this.personalUrl.trim()}\n`; vCard += `URL:${this.personalUrl.trim()}\n`;
vCard += `GENDER:${this.gender}\n`; vCard += `GENDER:${this.gender}\n`;
@ -340,7 +342,7 @@ export class GeneratePage {
} }
get today() { get today() {
return moment().format("YYYY-MM-DD"); return format(new Date(), "yyyy-MM-dd");
} }
getIcon(type: "freeText" | "url" | "contact" | "phone" | "sms" | "emailW3C" | "emailDocomo" | "wifi"): string { getIcon(type: "freeText" | "url" | "contact" | "phone" | "sms" | "emailW3C" | "emailDocomo" | "wifi"): string {

View file

@ -2,7 +2,8 @@ import { ChangeDetectorRef, Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { AlertController, IonItemSliding, LoadingController, ModalController, PopoverController, ToastController } from '@ionic/angular'; import { AlertController, IonItemSliding, LoadingController, ModalController, PopoverController, ToastController } from '@ionic/angular';
import { EnvService } from 'src/app/services/env.service'; import { EnvService } from 'src/app/services/env.service';
import * as moment from 'moment'; import { format, Locale } from 'date-fns';
import { de, enUS, fr, it, zhCN, zhHK } from 'date-fns/locale';
import { ScanRecord } from 'src/app/models/scan-record'; import { ScanRecord } from 'src/app/models/scan-record';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Bookmark } from 'src/app/models/bookmark'; import { Bookmark } from 'src/app/models/bookmark';
@ -156,19 +157,36 @@ export class HistoryPage {
if (!date) { if (!date) {
return "-"; return "-";
} }
const momentObj = moment(date); let locale: Locale;
if (this.env.language != 'en') { switch (this.env.language) {
momentObj.locale(this.env.language.toLowerCase()); case "de":
locale = de;
break;
case "en":
locale = enUS;
break;
case "fr":
locale = fr;
break;
case "it":
locale = it;
break;
case "zh-CN":
locale = zhCN;
break;
case "zh-HK":
locale = zhHK;
break;
default:
locale = enUS;
} }
switch (source) { switch (source) {
case 'create': case 'create':
return `${this.translate.instant("CREATED")} ${this.translate.instant("AT")} ${momentObj.format("ll LTS")}`; return `${this.translate.instant("CREATED")} ${this.translate.instant("AT")} ${format(date, "PP pp", { locale: locale })}`;
case 'view': case 'view':
return `${this.translate.instant("VIEWED")} ${this.translate.instant("AT")} ${momentObj.format("ll LTS")}`; return `${this.translate.instant("VIEWED")} ${this.translate.instant("AT")} ${format(date, "PP pp", { locale: locale })}`;
case 'scan': case 'scan':
return `${this.translate.instant("SCANNED")} ${this.translate.instant("AT")} ${momentObj.format("ll LTS")}`; return `${this.translate.instant("SCANNED")} ${this.translate.instant("AT")} ${format(date, "PP pp", { locale: locale })}`;
default:
return momentObj.format("ll LTS");
} }
} }

View file

@ -113,7 +113,7 @@
</ion-row> </ion-row>
<ion-row *ngIf="contentType === 'url' && env.showOpenUrlButton === 'on' && !isHttp" class="d-flex justify-content-center"> <ion-row *ngIf="contentType === 'url' && env.showOpenUrlButton === 'on' && !isHttp" class="d-flex justify-content-center">
<ion-button *ngIf="" (click)="tapHaptic(); openLink()" [color]="'primary'" fill="clear"> <ion-button (click)="tapHaptic(); openLink()" [color]="'primary'" fill="clear">
<ion-icon name="open"></ion-icon> <ion-icon name="open"></ion-icon>
</ion-button> </ion-button>
</ion-row> </ion-row>

View file

@ -6,7 +6,7 @@ import { Clipboard } from '@capacitor/clipboard';
import { Toast } from '@capacitor/toast'; import { Toast } from '@capacitor/toast';
import { EncryptService } from 'src/app/services/encrypt.service'; import { EncryptService } from 'src/app/services/encrypt.service';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'; import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import * as moment from 'moment'; import { format } from 'date-fns';
import { Chooser, ChooserResult } from '@awesome-cordova-plugins/chooser/ngx'; import { Chooser, ChooserResult } from '@awesome-cordova-plugins/chooser/ngx';
import { ScanRecord } from 'src/app/models/scan-record'; import { ScanRecord } from 'src/app/models/scan-record';
import { Bookmark } from 'src/app/models/bookmark'; import { Bookmark } from 'src/app/models/bookmark';
@ -75,7 +75,7 @@ export class SettingRecordPage {
async (value) => { async (value) => {
loading1.dismiss(); loading1.dismiss();
const loading2 = await this.presentLoading(this.translate.instant("BACKING_UP")); const loading2 = await this.presentLoading(this.translate.instant("BACKING_UP"));
const now = moment().format("yyyyMMDDHHmmss"); const now = format(new Date(), "yyyyMMddHHmmss");
const filename = this.platform.is('ios') ? `i-simpleqr-backup-${now}.isqbk` : `simpleqr-backup-${now}.tfsqbk`; const filename = this.platform.is('ios') ? `i-simpleqr-backup-${now}.isqbk` : `simpleqr-backup-${now}.tfsqbk`;
await Filesystem.writeFile({ await Filesystem.writeFile({
path: `${filename}`, path: `${filename}`,

View file

@ -6,7 +6,7 @@ import { ScreenOrientation } from '@awesome-cordova-plugins/screen-orientation/n
import { Platform } from '@ionic/angular'; import { Platform } from '@ionic/angular';
import { Storage } from '@ionic/storage-angular'; import { Storage } from '@ionic/storage-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import * as moment from 'moment'; import { format } from 'date-fns';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { Bookmark } from '../models/bookmark'; import { Bookmark } from '../models/bookmark';
import { ScanRecord } from '../models/scan-record'; import { ScanRecord } from '../models/scan-record';
@ -20,7 +20,7 @@ export declare type LanguageType = 'de' | 'en' | 'fr' | 'it' | 'zh-CN' | 'zh-HK'
}) })
export class EnvService { export class EnvService {
public appVersionNumber: string = '3.0.0'; public appVersionNumber: string = '3.0.1';
public startPage: "/tabs/scan" | "/tabs/generate" | "/tabs/import-image" | "/tabs/history" | "/tabs/setting" = "/tabs/scan"; public startPage: "/tabs/scan" | "/tabs/generate" | "/tabs/import-image" | "/tabs/history" | "/tabs/setting" = "/tabs/scan";
public historyPageStartSegment: 'history' | 'bookmarks' = 'history'; public historyPageStartSegment: 'history' | 'bookmarks' = 'history';
@ -79,10 +79,10 @@ export class EnvService {
public readonly APP_STORE_URL: string = "https://apps.apple.com/us/app/simple-qr-by-tom-fong/id1621121553"; public readonly APP_STORE_URL: string = "https://apps.apple.com/us/app/simple-qr-by-tom-fong/id1621121553";
public readonly GITHUB_RELEASE_URL: string = "https://github.com/tomfong/simple-qr/releases"; public readonly GITHUB_RELEASE_URL: string = "https://github.com/tomfong/simple-qr/releases";
public readonly PRIVACY_POLICY: string = "https://www.privacypolicies.com/live/771b1123-99bb-4bfe-815e-1046c0437a0f"; public readonly PRIVACY_POLICY: string = "https://www.privacypolicies.com/live/771b1123-99bb-4bfe-815e-1046c0437a0f";
public readonly AN_PREV_PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v20800"; public readonly AN_PREV_PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v30000";
public readonly IOS_PREV_PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v20800"; public readonly IOS_PREV_PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v30000";
public readonly AN_PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v30000"; public readonly AN_PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v30001";
public readonly IOS_PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v30000"; public readonly IOS_PATCH_NOTE_STORAGE_KEY = "not-show-update-notes-v30001";
private _storage: Storage | null = null; private _storage: Storage | null = null;
private _scannedData: string = ''; private _scannedData: string = '';
@ -1068,9 +1068,9 @@ export class EnvService {
getBugReportMailContent(): string { getBugReportMailContent(): string {
const toEmail = "tomfong.dev@gmail.com"; const toEmail = "tomfong.dev@gmail.com";
const now = moment(); const now = new Date();
const datetimestr1 = now.format("YYYYMMDDHHmmss"); const datetimestr1 = format(now, "yyyyMMddHHmmss");
const datetimestr2 = now.format("YYYY-MM-DD HH:mm:ss ZZ"); const datetimestr2 = format(now, "yyyy-MM-dd HH:mm:ss zzzz");
const model = `${this._deviceInfo?.manufacturer} ${this._deviceInfo?.model}`; const model = `${this._deviceInfo?.manufacturer} ${this._deviceInfo?.model}`;
const os = this.platform.is("android") ? "Android" : (this.platform.is("ios") ? "iOS" : "Other"); const os = this.platform.is("android") ? "Android" : (this.platform.is("ios") ? "iOS" : "Other");
const osVersion = this._deviceInfo?.osVersion; const osVersion = this._deviceInfo?.osVersion;