mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-09 19:22:51 +00:00
feat: app release notes
This commit is contained in:
@@ -22,6 +22,7 @@ const app = {
|
|||||||
name: 'Goethe-Uni',
|
name: 'Goethe-Uni',
|
||||||
privacyPolicyUrl: 'https://mobile.server.uni-frankfurt.de/_static/privacy.md',
|
privacyPolicyUrl: 'https://mobile.server.uni-frankfurt.de/_static/privacy.md',
|
||||||
settings: [userGroupSetting, languageSetting],
|
settings: [userGroupSetting, languageSetting],
|
||||||
|
versionHistory: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
42
backend/backend/config/default/tools/version.js
Normal file
42
backend/backend/config/default/tools/version.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// @ts-check
|
||||||
|
import {readFile} from 'fs/promises';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @example version(1, import.meta.url)
|
||||||
|
* @param options {Omit<import('@openstapps/core').SCAppVersionInfo, 'releaseNotes' | 'translations'>}
|
||||||
|
* @param base {string}
|
||||||
|
* @returns {Promise<import('@openstapps/core').SCAppVersionInfo>}
|
||||||
|
*/
|
||||||
|
export async function version(options, base) {
|
||||||
|
const de = await readFile(new URL(`${options.version}.de.md`, base), 'utf8');
|
||||||
|
const en = await readFile(new URL(`${options.version}.en.md`, base), 'utf8');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
releaseNotes: de,
|
||||||
|
translations: {
|
||||||
|
en: {
|
||||||
|
releaseNotes: en,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param infos {Record<string, import('@openstapps/core').SCAppVersionInfo['published']>}
|
||||||
|
* @param base {string} Base path of the file as `import.meta.url`
|
||||||
|
* @returns {Promise<import('@openstapps/core').SCAppVersionInfo[]>}
|
||||||
|
*/
|
||||||
|
export async function versions(infos, base) {
|
||||||
|
return Promise.all(
|
||||||
|
Object.entries(infos).map(([versionName, published]) =>
|
||||||
|
version(
|
||||||
|
{
|
||||||
|
published,
|
||||||
|
version: versionName,
|
||||||
|
},
|
||||||
|
base,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then(it => it.sort(({version: a}, {version: b}) => -a.localeCompare(b, undefined, {numeric: true})));
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import aboutPages from './about-pages/index.js';
|
import aboutPages from './about-pages/index.js';
|
||||||
import defaultApp from '../default/app/index.js';
|
import defaultApp from '../default/app/index.js';
|
||||||
import {backend as defaultBackend, internal as defaultInternal} from '../default/backend/index.js';
|
import {backend as defaultBackend, internal as defaultInternal} from '../default/backend/index.js';
|
||||||
|
import versionHistory from './version-history/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the default configuration for the Goethe university of Frankfurt
|
* This is the default configuration for the Goethe university of Frankfurt
|
||||||
@@ -76,6 +77,7 @@ const config = {
|
|||||||
} */
|
} */
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
versionHistory,
|
||||||
aboutPages,
|
aboutPages,
|
||||||
},
|
},
|
||||||
backend: defaultBackend,
|
backend: defaultBackend,
|
||||||
|
|||||||
52
backend/backend/config/f-u/version-history/3.1.0.de.md
Normal file
52
backend/backend/config/f-u/version-history/3.1.0.de.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Goethe-Uni App 3.1
|
||||||
|
|
||||||
|
Wir freuen uns euch mehr in der Goethe-Uni App
|
||||||
|
bieten zu können.
|
||||||
|
|
||||||
|
## Navigation zu Gebäuden und Orten
|
||||||
|
|
||||||
|
Als eines der Ergebnisse des Ideenwettbewerbs wurde jetzt
|
||||||
|
ein Navigationsfeature in die App integriert.
|
||||||
|
|
||||||
|
Orte auf der Karte, Mensen, sowie sogar Termine (wenn hinterlegt)
|
||||||
|
bieten jetzt direkt die Option eine Verbindung zu finden, gestützt
|
||||||
|
durch die Karten App auf deinem Gerät.
|
||||||
|
|
||||||
|
## Integration der Jobbörse
|
||||||
|
|
||||||
|
Jobs findest du ab sofort auch in der Goethe-Uni App.
|
||||||
|
|
||||||
|
Auch das ist ein Ergebnis des Ideenwettbewerbs,
|
||||||
|
und wir freuen uns es euch hier präsentieren zu können!
|
||||||
|
|
||||||
|
## Der Umweltscore
|
||||||
|
|
||||||
|
Der Umweltscore für Gerichte wird nun auch in der App angezeigt.
|
||||||
|
|
||||||
|
> Nachhaltigkeit, Umweltschutz, Gesundheit und Klimawandel sind
|
||||||
|
> zentrale Begriffe im gesellschaftlichen Miteinander.
|
||||||
|
> Unsere Ernährung spielt hierbei eine wichtige Rolle.
|
||||||
|
> Das Studierendenwerk Frankfurt am Main zeichnet seine Speisenpläne
|
||||||
|
> ab sofort mit einem Umweltscore aus.
|
||||||
|
> Anhand dieser Bewertung können Sie direkt ersehen,
|
||||||
|
> welchen Einfluss Ihre Essenauswahl auf das Klima hat.
|
||||||
|
|
||||||
|
## Weitere Verbesserungen
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
Die Performance der App beim Navigieren wurde stark verbessert und ist datensparender.
|
||||||
|
|
||||||
|
### Kalender
|
||||||
|
|
||||||
|
Die Kalenderabschnitte haben jetzt neue Namen bekommen:
|
||||||
|
|
||||||
|
- Der _Kalender_ zeigt Termine für spezifische Tage
|
||||||
|
- Die _Wochenübersicht_ ist ein Stundenplan mit allen Termine, die sich wiederholen (z. B. Vorlesungen)
|
||||||
|
- Die _Einzeltermine_ zeigen alle Termine, die sich nicht wiederholen
|
||||||
|
(z. B. Klausuren)
|
||||||
|
|
||||||
|
### Meine App
|
||||||
|
|
||||||
|
Der "Meine Kurse" Abschnitt wurde überarbeitet, und zeigt jetzt Termine
|
||||||
|
für die nächsten Tage und mit mehr Details an.
|
||||||
49
backend/backend/config/f-u/version-history/3.1.0.en.md
Normal file
49
backend/backend/config/f-u/version-history/3.1.0.en.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Goethe-Uni App 3.1
|
||||||
|
|
||||||
|
The Goethe-Uni App got even better!
|
||||||
|
|
||||||
|
## Navigation to buildings and places
|
||||||
|
|
||||||
|
As part of the "Ideenwettbewerb," the idea competition,
|
||||||
|
we have now integrated a navigation feature into the app.
|
||||||
|
|
||||||
|
Orte auf der Karte, Mensen, sowie sogar Termine (wenn hinterlegt)
|
||||||
|
bieten jetzt direkt die Option eine Verbindung zu finden, gestützt
|
||||||
|
durch die Karten App auf deinem Gerät.
|
||||||
|
|
||||||
|
## Integration of the job market
|
||||||
|
|
||||||
|
Jobs are now also available in the Goethe-Uni App.
|
||||||
|
|
||||||
|
This feature is also a result of the idea competition,
|
||||||
|
and we're happy to be able to present it to you here!
|
||||||
|
|
||||||
|
## The environment score
|
||||||
|
|
||||||
|
The environment score for dishes is now displayed inside the app.
|
||||||
|
|
||||||
|
> Sustainability, environment protection, health, and climate change are
|
||||||
|
> central topics in how we live today in our society.
|
||||||
|
> Our eating habits play an important role in it.
|
||||||
|
> The "Studierendenwerk Frankfurt am Main" is marking up its menus
|
||||||
|
> from now on with the so-called "Umweltscore," the environment score.
|
||||||
|
> Based on this rating, you can see the impact your meal choice would have on our climate.
|
||||||
|
|
||||||
|
## Further improvements
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
The performance while navigating around the app has been heavily improved and requires less data to work.
|
||||||
|
|
||||||
|
### Calendar
|
||||||
|
|
||||||
|
The calendar sections have new names:
|
||||||
|
|
||||||
|
- The _calendar_ shows appointments on specific days
|
||||||
|
- The _week overview_ is a schedule with all events that repeat (e.g. lectures)
|
||||||
|
- The _single events_ show all appointments that don't repeat (e.g. exams)
|
||||||
|
|
||||||
|
### My App
|
||||||
|
|
||||||
|
The "my courses" section has been revamped,
|
||||||
|
and now shows events for the next days and with more detail.
|
||||||
12
backend/backend/config/f-u/version-history/index.js
Normal file
12
backend/backend/config/f-u/version-history/index.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// @ts-check
|
||||||
|
import {versions} from '../../default/tools/version.js';
|
||||||
|
|
||||||
|
/** @type {import('@openstapps/core').SCAppVersionInfo[]} */
|
||||||
|
const versionHistory = await versions(
|
||||||
|
{
|
||||||
|
'3.1.0': {},
|
||||||
|
},
|
||||||
|
import.meta.url,
|
||||||
|
);
|
||||||
|
|
||||||
|
export default versionHistory;
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
import {Platform} from '@ionic/angular';
|
import {ModalController, Platform} from '@ionic/angular';
|
||||||
|
|
||||||
import {TranslateService} from '@ngx-translate/core';
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
import {ThingTranslateService} from './translation/thing-translate.service';
|
import {ThingTranslateService} from './translation/thing-translate.service';
|
||||||
@@ -45,6 +45,7 @@ describe('AppComponent', () => {
|
|||||||
let platformIsSpy;
|
let platformIsSpy;
|
||||||
let storageProvider: jasmine.SpyObj<StorageProvider>;
|
let storageProvider: jasmine.SpyObj<StorageProvider>;
|
||||||
let simpleBrowser: jasmine.SpyObj<SimpleBrowser>;
|
let simpleBrowser: jasmine.SpyObj<SimpleBrowser>;
|
||||||
|
let modalController: jasmine.SpyObj<ModalController>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
platformReadySpy = Promise.resolve();
|
platformReadySpy = Promise.resolve();
|
||||||
@@ -71,6 +72,7 @@ describe('AppComponent', () => {
|
|||||||
ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']);
|
ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']);
|
||||||
storageProvider = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']);
|
storageProvider = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']);
|
||||||
simpleBrowser = jasmine.createSpyObj('SimpleBrowser', ['open']);
|
simpleBrowser = jasmine.createSpyObj('SimpleBrowser', ['open']);
|
||||||
|
modalController = jasmine.createSpyObj('ModalController', ['create', 'dismiss', 'getTop']);
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule, AuthModule],
|
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule, AuthModule],
|
||||||
@@ -85,6 +87,7 @@ describe('AppComponent', () => {
|
|||||||
{provide: NGXLogger, useValue: ngxLogger},
|
{provide: NGXLogger, useValue: ngxLogger},
|
||||||
{provide: StorageProvider, useValue: storageProvider},
|
{provide: StorageProvider, useValue: storageProvider},
|
||||||
{provide: SimpleBrowser, useValue: simpleBrowser},
|
{provide: SimpleBrowser, useValue: simpleBrowser},
|
||||||
|
{provide: ModalController, useValue: modalController},
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {environment} from '../environments/environment';
|
|||||||
import {Capacitor} from '@capacitor/core';
|
import {Capacitor} from '@capacitor/core';
|
||||||
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
|
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
|
||||||
import {Keyboard, KeyboardResize} from '@capacitor/keyboard';
|
import {Keyboard, KeyboardResize} from '@capacitor/keyboard';
|
||||||
|
import {AppVersionService} from './modules/about/app-version.service';
|
||||||
import {SplashScreen} from '@capacitor/splash-screen';
|
import {SplashScreen} from '@capacitor/splash-screen';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,13 +60,19 @@ export class AppComponent implements AfterContentInit {
|
|||||||
private readonly authHelper: AuthHelperService,
|
private readonly authHelper: AuthHelperService,
|
||||||
private readonly toastController: ToastController,
|
private readonly toastController: ToastController,
|
||||||
private readonly scheduleSyncService: ScheduleSyncService,
|
private readonly scheduleSyncService: ScheduleSyncService,
|
||||||
|
private readonly versionService: AppVersionService,
|
||||||
) {
|
) {
|
||||||
void this.initializeApp();
|
void this.initializeApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterContentInit() {
|
async ngAfterContentInit() {
|
||||||
this.scheduleSyncService.init();
|
this.scheduleSyncService.init();
|
||||||
void this.scheduleSyncService.enable();
|
void this.scheduleSyncService.enable();
|
||||||
|
this.versionService.getPendingReleaseNotes().then(notes => {
|
||||||
|
if (notes) {
|
||||||
|
this.versionService.presentReleaseNotes(notes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (document.readyState === 'complete') {
|
if (document.readyState === 'complete') {
|
||||||
requestIdleCallback(this.hideSplash.bind(this));
|
requestIdleCallback(this.hideSplash.bind(this));
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {SCAboutPage, SCAppConfiguration} from '@openstapps/core';
|
|||||||
import {ConfigProvider} from '../../config/config.provider';
|
import {ConfigProvider} from '../../config/config.provider';
|
||||||
import packageJson from '../../../../../package.json';
|
import packageJson from '../../../../../package.json';
|
||||||
import config from 'capacitor.config';
|
import config from 'capacitor.config';
|
||||||
|
import {App} from '@capacitor/app';
|
||||||
|
import {Capacitor} from '@capacitor/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'about-page',
|
selector: 'about-page',
|
||||||
@@ -27,9 +29,11 @@ import config from 'capacitor.config';
|
|||||||
export class AboutPageComponent implements OnInit {
|
export class AboutPageComponent implements OnInit {
|
||||||
content: SCAboutPage;
|
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) {}
|
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('/');
|
const route = this.route.snapshot.url.map(it => it.path).join('/');
|
||||||
this.content =
|
this.content =
|
||||||
(this.configProvider.getValue('aboutPages') as SCAppConfiguration['aboutPages'])[route] ?? {};
|
(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-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content parallax *ngIf="content">
|
<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">
|
<div class="page-content">
|
||||||
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
||||||
</div>
|
</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 {LazyPipe} from './lazy.pipe';
|
||||||
import {NextDateInListPipe} from './next-date-in-list.pipe';
|
import {NextDateInListPipe} from './next-date-in-list.pipe';
|
||||||
import {EditModalComponent} from './edit-modal.component';
|
import {EditModalComponent} from './edit-modal.component';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
|
||||||
import {IonicModule} from '@ionic/angular';
|
import {IonicModule} from '@ionic/angular';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
import {ElementSizeChangeDirective} from './element-size-change.directive';
|
import {ElementSizeChangeDirective} from './element-size-change.directive';
|
||||||
@@ -33,10 +32,11 @@ import {SectionComponent} from './section.component';
|
|||||||
import {RouterModule} from '@angular/router';
|
import {RouterModule} from '@angular/router';
|
||||||
import {IonContentParallaxDirective} from './ion-content-parallax.directive';
|
import {IonContentParallaxDirective} from './ion-content-parallax.directive';
|
||||||
import {FormatDistanceToNowStrictPipeModule, FormatRelativeToNowPipeModule} from 'ngx-date-fns';
|
import {FormatDistanceToNowStrictPipeModule, FormatRelativeToNowPipeModule} from 'ngx-date-fns';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
CommonModule,
|
||||||
IonicModule,
|
IonicModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
ThingTranslateModule.forChild(),
|
ThingTranslateModule.forChild(),
|
||||||
|
|||||||
@@ -28,6 +28,12 @@
|
|||||||
"toast": {
|
"toast": {
|
||||||
"TITLE_COPIED": "In die Zwischenablage kopiert"
|
"TITLE_COPIED": "In die Zwischenablage kopiert"
|
||||||
},
|
},
|
||||||
|
"about": {
|
||||||
|
"VERSION_INFO": "{{name}} {{version}} basierend auf StApps {{stappsVersion}}"
|
||||||
|
},
|
||||||
|
"releaseNotes": {
|
||||||
|
"TITLE_UPDATED": "Deine App wurde aktualisiert!"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"ui": {
|
"ui": {
|
||||||
"CLOSE": "Schließen",
|
"CLOSE": "Schließen",
|
||||||
|
|||||||
@@ -28,6 +28,12 @@
|
|||||||
"toast": {
|
"toast": {
|
||||||
"TITLE_COPIED": "Copied to clipboard"
|
"TITLE_COPIED": "Copied to clipboard"
|
||||||
},
|
},
|
||||||
|
"about": {
|
||||||
|
"VERSION_INFO": "{{name}} {{version}} based on StApps {{stappsVersion}}"
|
||||||
|
},
|
||||||
|
"releaseNotes": {
|
||||||
|
"TITLE_UPDATED": "Your app was updated!"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"ui": {
|
"ui": {
|
||||||
"CLOSE": "Close",
|
"CLOSE": "Close",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {SCMap} from '../general/map.js';
|
|||||||
import {SCLanguageSetting, SCSetting, SCUserGroupSetting} from '../things/setting.js';
|
import {SCLanguageSetting, SCSetting, SCUserGroupSetting} from '../things/setting.js';
|
||||||
import {SCAuthorizationProviderType} from './authorization.js';
|
import {SCAuthorizationProviderType} from './authorization.js';
|
||||||
import {SCFeatureConfiguration} from './feature.js';
|
import {SCFeatureConfiguration} from './feature.js';
|
||||||
|
import {SCISO8601Date} from '../general/time.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An app configuration menu item
|
* An app configuration menu item
|
||||||
@@ -136,6 +137,38 @@ export interface SCAppConfiguration {
|
|||||||
* URL where a web instance of the app is available
|
* URL where a web instance of the app is available
|
||||||
*/
|
*/
|
||||||
url?: string;
|
url?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version history in sequence of their publishing date.
|
||||||
|
*
|
||||||
|
* The items are ordered in descending version index order,
|
||||||
|
* where the first item is always the most recent release.
|
||||||
|
*/
|
||||||
|
versionHistory?: SCAppVersionInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Info about app version releases
|
||||||
|
*/
|
||||||
|
export interface SCAppVersionInfo {
|
||||||
|
/**
|
||||||
|
* Published date for each platform.
|
||||||
|
*
|
||||||
|
* Missing entries mean the version has not been published on that platform yet.
|
||||||
|
*/
|
||||||
|
published: Partial<Record<'web' | 'android' | 'ios', SCISO8601Date>>;
|
||||||
|
/**
|
||||||
|
* Version index that increments by one each version
|
||||||
|
*/
|
||||||
|
version: string;
|
||||||
|
/**
|
||||||
|
* Release notes of the version
|
||||||
|
*/
|
||||||
|
releaseNotes: string;
|
||||||
|
/**
|
||||||
|
* Translations
|
||||||
|
*/
|
||||||
|
translations: SCTranslations<{releaseNotes: string}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user