mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2025-12-18 04:06:19 +00:00
feat: app release notes
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {Platform} from '@ionic/angular';
|
||||
import {ModalController, Platform} from '@ionic/angular';
|
||||
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {ThingTranslateService} from './translation/thing-translate.service';
|
||||
@@ -45,6 +45,7 @@ describe('AppComponent', () => {
|
||||
let platformIsSpy;
|
||||
let storageProvider: jasmine.SpyObj<StorageProvider>;
|
||||
let simpleBrowser: jasmine.SpyObj<SimpleBrowser>;
|
||||
let modalController: jasmine.SpyObj<ModalController>;
|
||||
|
||||
beforeEach(() => {
|
||||
platformReadySpy = Promise.resolve();
|
||||
@@ -71,6 +72,7 @@ describe('AppComponent', () => {
|
||||
ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']);
|
||||
storageProvider = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']);
|
||||
simpleBrowser = jasmine.createSpyObj('SimpleBrowser', ['open']);
|
||||
modalController = jasmine.createSpyObj('ModalController', ['create', 'dismiss', 'getTop']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule, AuthModule],
|
||||
@@ -85,6 +87,7 @@ describe('AppComponent', () => {
|
||||
{provide: NGXLogger, useValue: ngxLogger},
|
||||
{provide: StorageProvider, useValue: storageProvider},
|
||||
{provide: SimpleBrowser, useValue: simpleBrowser},
|
||||
{provide: ModalController, useValue: modalController},
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
|
||||
@@ -22,6 +22,7 @@ import {environment} from '../environments/environment';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
|
||||
import {Keyboard, KeyboardResize} from '@capacitor/keyboard';
|
||||
import {AppVersionService} from './modules/about/app-version.service';
|
||||
import {SplashScreen} from '@capacitor/splash-screen';
|
||||
|
||||
/**
|
||||
@@ -59,13 +60,19 @@ export class AppComponent implements AfterContentInit {
|
||||
private readonly authHelper: AuthHelperService,
|
||||
private readonly toastController: ToastController,
|
||||
private readonly scheduleSyncService: ScheduleSyncService,
|
||||
private readonly versionService: AppVersionService,
|
||||
) {
|
||||
void this.initializeApp();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
async ngAfterContentInit() {
|
||||
this.scheduleSyncService.init();
|
||||
void this.scheduleSyncService.enable();
|
||||
this.versionService.getPendingReleaseNotes().then(notes => {
|
||||
if (notes) {
|
||||
this.versionService.presentReleaseNotes(notes);
|
||||
}
|
||||
});
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
requestIdleCallback(this.hideSplash.bind(this));
|
||||
|
||||
@@ -18,6 +18,8 @@ import {SCAboutPage, SCAppConfiguration} from '@openstapps/core';
|
||||
import {ConfigProvider} from '../../config/config.provider';
|
||||
import packageJson from '../../../../../package.json';
|
||||
import config from 'capacitor.config';
|
||||
import {App} from '@capacitor/app';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
|
||||
@Component({
|
||||
selector: 'about-page',
|
||||
@@ -27,9 +29,11 @@ import config from 'capacitor.config';
|
||||
export class AboutPageComponent implements OnInit {
|
||||
content: SCAboutPage;
|
||||
|
||||
appName = config.appName;
|
||||
name = config.appName;
|
||||
|
||||
version = packageJson.version;
|
||||
stappsVersion = packageJson.version;
|
||||
|
||||
version: string;
|
||||
|
||||
constructor(private readonly route: ActivatedRoute, private readonly configProvider: ConfigProvider) {}
|
||||
|
||||
@@ -37,5 +41,6 @@ export class AboutPageComponent implements OnInit {
|
||||
const route = this.route.snapshot.url.map(it => it.path).join('/');
|
||||
this.content =
|
||||
(this.configProvider.getValue('aboutPages') as SCAppConfiguration['aboutPages'])[route] ?? {};
|
||||
this.version = Capacitor.getPlatform() === 'web' ? 'Web' : await App.getInfo().then(info => info.version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content parallax *ngIf="content">
|
||||
<ion-text>{{ appName }} v{{ version }}</ion-text>
|
||||
<ion-text>{{ 'about.VERSION_INFO' | translate: {name, version, stappsVersion} }}</ion-text>
|
||||
<div class="page-content">
|
||||
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
||||
</div>
|
||||
|
||||
60
frontend/app/src/app/modules/about/app-version.service.ts
Normal file
60
frontend/app/src/app/modules/about/app-version.service.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {StorageProvider} from '../storage/storage.provider';
|
||||
import {ConfigProvider} from '../config/config.provider';
|
||||
import {ModalController} from '@ionic/angular';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
import {ReleaseNotesComponent} from './release-notes.component';
|
||||
import {SCAppVersionInfo} from '@openstapps/core';
|
||||
import {App} from '@capacitor/app';
|
||||
|
||||
export const RELEASE_NOTES_SHOWN_KEY = 'release_notes_shown';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class AppVersionService {
|
||||
constructor(
|
||||
private storage: StorageProvider,
|
||||
private config: ConfigProvider,
|
||||
private modalController: ModalController,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the release notes of the latest published version
|
||||
*/
|
||||
get publishedVersions() {
|
||||
const platform = Capacitor.getPlatform() as 'android' | 'ios' | 'web';
|
||||
return this.config.config.app.versionHistory?.filter(({published}) => published[platform] !== undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest release notes that have not been presented yet
|
||||
*/
|
||||
async getPendingReleaseNotes() {
|
||||
if (Capacitor.getPlatform() === 'web') {
|
||||
return;
|
||||
}
|
||||
const storedVersion = (await this.storage.has(RELEASE_NOTES_SHOWN_KEY))
|
||||
? await this.storage.get<string>(RELEASE_NOTES_SHOWN_KEY)
|
||||
: '';
|
||||
const currentVersion = await App.getInfo().then(info => info.version);
|
||||
return this.publishedVersions?.find(({version}) => {
|
||||
const wasNotShown = version.localeCompare(storedVersion, undefined, {numeric: true}) === 1;
|
||||
const isNotFutureVersion = version.localeCompare(currentVersion, undefined, {numeric: true}) <= 0;
|
||||
return wasNotShown && isNotFutureVersion;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Present release notes
|
||||
*/
|
||||
async presentReleaseNotes(version: SCAppVersionInfo) {
|
||||
const modal = await this.modalController.create({
|
||||
component: ReleaseNotesComponent,
|
||||
componentProps: {
|
||||
versionInfo: version,
|
||||
},
|
||||
});
|
||||
await modal.present();
|
||||
await modal.onDidDismiss();
|
||||
await this.storage.put(RELEASE_NOTES_SHOWN_KEY, version.version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
|
||||
import {SCAppVersionInfo} from '@openstapps/core';
|
||||
import {MarkdownModule} from 'ngx-markdown';
|
||||
import {ThingTranslateModule} from '../../translation/thing-translate.module';
|
||||
import {IonicModule, ModalController} from '@ionic/angular';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {UtilModule} from '../../util/util.module';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-release-notes',
|
||||
templateUrl: 'release-notes.html',
|
||||
styleUrls: ['release-notes.scss'],
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [UtilModule, MarkdownModule, ThingTranslateModule, IonicModule, TranslateModule],
|
||||
})
|
||||
export class ReleaseNotesComponent {
|
||||
@Input() versionInfo: SCAppVersionInfo;
|
||||
|
||||
constructor(readonly modalController: ModalController) {}
|
||||
}
|
||||
16
frontend/app/src/app/modules/about/release-notes.html
Normal file
16
frontend/app/src/app/modules/about/release-notes.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title><span class="ion-text-wrap">{{'releaseNotes.TITLE_UPDATED' | translate}}</span></ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button [strong]="true" (click)="modalController.dismiss()"
|
||||
>{{'modal.DISMISS_NEUTRAL' | translate}}</ion-button
|
||||
>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content parallax>
|
||||
<markdown
|
||||
class="content-card ion-padding"
|
||||
[data]="'releaseNotes' | translateSimple: versionInfo"
|
||||
></markdown>
|
||||
</ion-content>
|
||||
13
frontend/app/src/app/modules/about/release-notes.scss
Normal file
13
frontend/app/src/app/modules/about/release-notes.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
@import '../../../theme/util/mixins';
|
||||
|
||||
ion-title {
|
||||
padding-block: var(--spacing-md);
|
||||
}
|
||||
|
||||
.content-card {
|
||||
@include border-radius-in-parallax(var(--border-radius-default));
|
||||
|
||||
display: block;
|
||||
margin: var(--spacing-md);
|
||||
background: var(--ion-item-background);
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import {DaytimeKeyPipe} from './daytime-key.pipe';
|
||||
import {LazyPipe} from './lazy.pipe';
|
||||
import {NextDateInListPipe} from './next-date-in-list.pipe';
|
||||
import {EditModalComponent} from './edit-modal.component';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {ElementSizeChangeDirective} from './element-size-change.directive';
|
||||
@@ -33,10 +32,11 @@ import {SectionComponent} from './section.component';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {IonContentParallaxDirective} from './ion-content-parallax.directive';
|
||||
import {FormatDistanceToNowStrictPipeModule, FormatRelativeToNowPipeModule} from 'ngx-date-fns';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule,
|
||||
ThingTranslateModule.forChild(),
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"toast": {
|
||||
"TITLE_COPIED": "In die Zwischenablage kopiert"
|
||||
},
|
||||
"about": {
|
||||
"VERSION_INFO": "{{name}} {{version}} basierend auf StApps {{stappsVersion}}"
|
||||
},
|
||||
"releaseNotes": {
|
||||
"TITLE_UPDATED": "Deine App wurde aktualisiert!"
|
||||
},
|
||||
"app": {
|
||||
"ui": {
|
||||
"CLOSE": "Schließen",
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"toast": {
|
||||
"TITLE_COPIED": "Copied to clipboard"
|
||||
},
|
||||
"about": {
|
||||
"VERSION_INFO": "{{name}} {{version}} based on StApps {{stappsVersion}}"
|
||||
},
|
||||
"releaseNotes": {
|
||||
"TITLE_UPDATED": "Your app was updated!"
|
||||
},
|
||||
"app": {
|
||||
"ui": {
|
||||
"CLOSE": "Close",
|
||||
|
||||
Reference in New Issue
Block a user