From 37945f7d19be9c6c0a2dc3d64fbbdd3a3853a318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Fri, 1 Dec 2023 12:34:20 +0000 Subject: [PATCH] feat: app release notes --- backend/backend/config/default/app/index.js | 1 + .../backend/config/default/tools/version.js | 42 +++++++++++++ backend/backend/config/f-u/backendrc.js | 2 + .../config/f-u/version-history/3.1.0.de.md | 52 ++++++++++++++++ .../config/f-u/version-history/3.1.0.en.md | 49 +++++++++++++++ .../config/f-u/version-history/index.js | 12 ++++ frontend/app/src/app/app.component.spec.ts | 5 +- frontend/app/src/app/app.component.ts | 9 ++- .../about/about-page/about-page.component.ts | 9 ++- .../modules/about/about-page/about-page.html | 2 +- .../app/modules/about/app-version.service.ts | 60 +++++++++++++++++++ .../modules/about/release-notes.component.ts | 21 +++++++ .../src/app/modules/about/release-notes.html | 16 +++++ .../src/app/modules/about/release-notes.scss | 13 ++++ frontend/app/src/app/util/util.module.ts | 4 +- frontend/app/src/assets/i18n/de.json | 6 ++ frontend/app/src/assets/i18n/en.json | 6 ++ packages/core/src/config/app.ts | 33 ++++++++++ 18 files changed, 335 insertions(+), 7 deletions(-) create mode 100644 backend/backend/config/default/tools/version.js create mode 100644 backend/backend/config/f-u/version-history/3.1.0.de.md create mode 100644 backend/backend/config/f-u/version-history/3.1.0.en.md create mode 100644 backend/backend/config/f-u/version-history/index.js create mode 100644 frontend/app/src/app/modules/about/app-version.service.ts create mode 100644 frontend/app/src/app/modules/about/release-notes.component.ts create mode 100644 frontend/app/src/app/modules/about/release-notes.html create mode 100644 frontend/app/src/app/modules/about/release-notes.scss diff --git a/backend/backend/config/default/app/index.js b/backend/backend/config/default/app/index.js index 4f8b3525..5652b88c 100644 --- a/backend/backend/config/default/app/index.js +++ b/backend/backend/config/default/app/index.js @@ -22,6 +22,7 @@ const app = { name: 'Goethe-Uni', privacyPolicyUrl: 'https://mobile.server.uni-frankfurt.de/_static/privacy.md', settings: [userGroupSetting, languageSetting], + versionHistory: [], }; export default app; diff --git a/backend/backend/config/default/tools/version.js b/backend/backend/config/default/tools/version.js new file mode 100644 index 00000000..bb709980 --- /dev/null +++ b/backend/backend/config/default/tools/version.js @@ -0,0 +1,42 @@ +// @ts-check +import {readFile} from 'fs/promises'; + +/** + * @example version(1, import.meta.url) + * @param options {Omit} + * @param base {string} + * @returns {Promise} + */ +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} + * @param base {string} Base path of the file as `import.meta.url` + * @returns {Promise} + */ +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}))); +} diff --git a/backend/backend/config/f-u/backendrc.js b/backend/backend/config/f-u/backendrc.js index 6665ee13..24fce993 100644 --- a/backend/backend/config/f-u/backendrc.js +++ b/backend/backend/config/f-u/backendrc.js @@ -2,6 +2,7 @@ import aboutPages from './about-pages/index.js'; import defaultApp from '../default/app/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 @@ -76,6 +77,7 @@ const config = { } */ }, }, + versionHistory, aboutPages, }, backend: defaultBackend, diff --git a/backend/backend/config/f-u/version-history/3.1.0.de.md b/backend/backend/config/f-u/version-history/3.1.0.de.md new file mode 100644 index 00000000..ee23a22d --- /dev/null +++ b/backend/backend/config/f-u/version-history/3.1.0.de.md @@ -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. diff --git a/backend/backend/config/f-u/version-history/3.1.0.en.md b/backend/backend/config/f-u/version-history/3.1.0.en.md new file mode 100644 index 00000000..158389a7 --- /dev/null +++ b/backend/backend/config/f-u/version-history/3.1.0.en.md @@ -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. diff --git a/backend/backend/config/f-u/version-history/index.js b/backend/backend/config/f-u/version-history/index.js new file mode 100644 index 00000000..68243ace --- /dev/null +++ b/backend/backend/config/f-u/version-history/index.js @@ -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; diff --git a/frontend/app/src/app/app.component.spec.ts b/frontend/app/src/app/app.component.spec.ts index 073828a1..8afad80d 100644 --- a/frontend/app/src/app/app.component.spec.ts +++ b/frontend/app/src/app/app.component.spec.ts @@ -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; let simpleBrowser: jasmine.SpyObj; + let modalController: jasmine.SpyObj; 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(); diff --git a/frontend/app/src/app/app.component.ts b/frontend/app/src/app/app.component.ts index 29194cf8..ed2daedc 100644 --- a/frontend/app/src/app/app.component.ts +++ b/frontend/app/src/app/app.component.ts @@ -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)); diff --git a/frontend/app/src/app/modules/about/about-page/about-page.component.ts b/frontend/app/src/app/modules/about/about-page/about-page.component.ts index abe311a0..818c973e 100644 --- a/frontend/app/src/app/modules/about/about-page/about-page.component.ts +++ b/frontend/app/src/app/modules/about/about-page/about-page.component.ts @@ -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); } } diff --git a/frontend/app/src/app/modules/about/about-page/about-page.html b/frontend/app/src/app/modules/about/about-page/about-page.html index 166c0655..ce90c470 100644 --- a/frontend/app/src/app/modules/about/about-page/about-page.html +++ b/frontend/app/src/app/modules/about/about-page/about-page.html @@ -25,7 +25,7 @@ - {{ appName }} v{{ version }} + {{ 'about.VERSION_INFO' | translate: {name, version, stappsVersion} }}
diff --git a/frontend/app/src/app/modules/about/app-version.service.ts b/frontend/app/src/app/modules/about/app-version.service.ts new file mode 100644 index 00000000..bb3cbb1b --- /dev/null +++ b/frontend/app/src/app/modules/about/app-version.service.ts @@ -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(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); + } +} diff --git a/frontend/app/src/app/modules/about/release-notes.component.ts b/frontend/app/src/app/modules/about/release-notes.component.ts new file mode 100644 index 00000000..98d6ace4 --- /dev/null +++ b/frontend/app/src/app/modules/about/release-notes.component.ts @@ -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) {} +} diff --git a/frontend/app/src/app/modules/about/release-notes.html b/frontend/app/src/app/modules/about/release-notes.html new file mode 100644 index 00000000..dd50f0bb --- /dev/null +++ b/frontend/app/src/app/modules/about/release-notes.html @@ -0,0 +1,16 @@ + + + {{'releaseNotes.TITLE_UPDATED' | translate}} + + {{'modal.DISMISS_NEUTRAL' | translate}} + + + + + + diff --git a/frontend/app/src/app/modules/about/release-notes.scss b/frontend/app/src/app/modules/about/release-notes.scss new file mode 100644 index 00000000..c31b0543 --- /dev/null +++ b/frontend/app/src/app/modules/about/release-notes.scss @@ -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); +} diff --git a/frontend/app/src/app/util/util.module.ts b/frontend/app/src/app/util/util.module.ts index 60b91b37..60cc603f 100644 --- a/frontend/app/src/app/util/util.module.ts +++ b/frontend/app/src/app/util/util.module.ts @@ -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(), diff --git a/frontend/app/src/assets/i18n/de.json b/frontend/app/src/assets/i18n/de.json index 24b4fd14..214344ad 100644 --- a/frontend/app/src/assets/i18n/de.json +++ b/frontend/app/src/assets/i18n/de.json @@ -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", diff --git a/frontend/app/src/assets/i18n/en.json b/frontend/app/src/assets/i18n/en.json index 567697c9..9c7abe84 100644 --- a/frontend/app/src/assets/i18n/en.json +++ b/frontend/app/src/assets/i18n/en.json @@ -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", diff --git a/packages/core/src/config/app.ts b/packages/core/src/config/app.ts index 19a58ad8..3b3c1b84 100644 --- a/packages/core/src/config/app.ts +++ b/packages/core/src/config/app.ts @@ -18,6 +18,7 @@ import {SCMap} from '../general/map.js'; import {SCLanguageSetting, SCSetting, SCUserGroupSetting} from '../things/setting.js'; import {SCAuthorizationProviderType} from './authorization.js'; import {SCFeatureConfiguration} from './feature.js'; +import {SCISO8601Date} from '../general/time.js'; /** * An app configuration menu item @@ -136,6 +137,38 @@ export interface SCAppConfiguration { * URL where a web instance of the app is available */ 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>; + /** + * Version index that increments by one each version + */ + version: string; + /** + * Release notes of the version + */ + releaseNotes: string; + /** + * Translations + */ + translations: SCTranslations<{releaseNotes: string}>; } /**