diff --git a/src/app/modules/schedule/page/modal/modal-event-creator.scss b/src/app/animation/easings.ts similarity index 67% rename from src/app/modules/schedule/page/modal/modal-event-creator.scss rename to src/app/animation/easings.ts index c933f1ae..4c84acee 100644 --- a/src/app/modules/schedule/page/modal/modal-event-creator.scss +++ b/src/app/animation/easings.ts @@ -1,5 +1,5 @@ -/*! - * Copyright (C) 2022 StApps +/* + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -13,23 +13,8 @@ * this program. If not, see . */ -:host { - height: 100%; - display: flex; - flex-direction: column; - flex: 1 1 20%; -} - -ion-button { - ion-label { - color: var(--ion-color-light); - } -} - -ion-card-content { - height: 100%; - padding: 0; - stapps-data-list { - height: 100%; - } -} +// these are the ionic values +export const iosEasing = 'cubic-bezier(0.32,0.72,0,1)'; +export const iosDuration = 540; +export const mdEasing = 'cubic-bezier(0.36,0.66,0.04,1)'; +export const mdDuration = 280; diff --git a/src/app/animation/fab-expand.ts b/src/app/animation/fab-expand.ts new file mode 100644 index 00000000..92823fb5 --- /dev/null +++ b/src/app/animation/fab-expand.ts @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {AnimationBuilder, AnimationController} from '@ionic/angular'; +import {AnimationOptions} from '@ionic/angular/providers/nav-controller'; +import {iosDuration, iosEasing, mdDuration, mdEasing} from './easings'; + +/** + * + */ +export function fabExpand(animationController: AnimationController): AnimationBuilder { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (_baseElement: HTMLElement, options: AnimationOptions | any) => { + const rootTransition = animationController + .create() + .duration(options.duration ?? (options.mode === 'ios' ? iosDuration : mdDuration * 1.4)) + .easing(options.mode === 'ios' ? iosEasing : mdEasing); + const back = options.direction === 'back'; + const fabView = back ? options.enteringEl! : options.leavingEl!; + const otherView = back ? options.leavingEl! : options.enteringEl!; + + const fab = fabView.querySelector('ion-fab-button').shadowRoot.querySelector('.button-native'); + const fabBounds = fab.getBoundingClientRect(); + const viewBounds = otherView.getBoundingClientRect(); + + const useReducedMotion = viewBounds.width > 500; + const reducedMotionTransform = `${Math.min(viewBounds.width * 0.3, 200)}px`; + const reducedMotionViewBorderRadius = '128px'; + const reducedMotionFabGrow = '2'; + const reducedMotionViewShrink = '0.9'; + + const viewCenterX = (viewBounds.width - viewBounds.x) / 2; + const viewCenterY = (viewBounds.height - viewBounds.y) / 2; + + const viewOnFab = useReducedMotion + ? `translate(${reducedMotionTransform}, ${reducedMotionTransform}) scale(${reducedMotionViewShrink})` + : `translate(${(fabBounds.x - viewBounds.x) / 2}px, ${(fabBounds.y - viewBounds.y) / 2}px) scale(${ + fabBounds.width / viewBounds.width + }, ${fabBounds.height / viewBounds.height})`; + const fabOnView = useReducedMotion + ? `translate(-${reducedMotionTransform}, -${reducedMotionTransform}) scale(${reducedMotionFabGrow})` + : `translate(${viewCenterX - fabBounds.x}px, ${viewCenterY - fabBounds.y}px) scale(${ + viewBounds.width / fabBounds.width + }, ${viewBounds.height / fabBounds.height})`; + const transformNormal = `translate(0px, 0px) scale(1, 1)`; + + const viewBorderRadius = useReducedMotion ? reducedMotionViewBorderRadius : '50%'; + + const fabViewFade = animationController + .create() + .beforeStyles({zIndex: -1}) + .fromTo('opacity', '1', '1') + .addElement(fabView); + const fabGrow = animationController + .create() + .beforeStyles({transformOrigin: 'center'}) + .fromTo('transform', back ? fabOnView : transformNormal, back ? transformNormal : fabOnView) + .fromTo('opacity', back ? '0' : '1', back ? '1' : '0') + .fromTo('borderRadius', back ? '0' : '50%', back ? '50%' : '0') + .addElement(fab); + const viewGrow = animationController + .create() + .beforeStyles({zIndex: 200, overflow: 'hidden', transformOrigin: 'center'}) + .fromTo('transform', back ? transformNormal : viewOnFab, back ? viewOnFab : transformNormal) + .fromTo('opacity', back ? '1' : '0', back ? '0' : '1') + .fromTo('borderRadius', back ? '0' : viewBorderRadius, back ? viewBorderRadius : '0') + .addElement(otherView); + + return rootTransition.addAnimation(fabGrow).addAnimation(viewGrow).addAnimation(fabViewFade); + }; +} diff --git a/src/app/modules/data/list/search-page.component.ts b/src/app/modules/data/list/search-page.component.ts index 0fb4ceb1..1b4ee9a5 100644 --- a/src/app/modules/data/list/search-page.component.ts +++ b/src/app/modules/data/list/search-page.component.ts @@ -46,7 +46,13 @@ import {searchPageSwitchAnimation} from './search-page-switch-animation'; providers: [ContextMenuService], }) export class SearchPageComponent implements OnInit, OnDestroy { - title = 'search.title'; + @Input() title = 'search.title'; + + @Input() placeholder = 'search.search_bar.placeholder'; + + @Input() searchInstruction = 'search.instruction'; + + @Input() backUrl?: string; isHebisAvailable = false; diff --git a/src/app/modules/data/list/search-page.html b/src/app/modules/data/list/search-page.html index 61793278..f795f974 100644 --- a/src/app/modules/data/list/search-page.html +++ b/src/app/modules/data/list/search-page.html @@ -15,9 +15,9 @@ - + - + {{ title | translate }} @@ -28,7 +28,7 @@ (search)="hideKeyboard()" [(ngModel)]="queryText" showClearButton="always" - placeholder="{{ 'search.search_bar.placeholder' | translate }}" + placeholder="{{ placeholder | translate }}" mode="md" type="search" enterkeyhint="search" @@ -61,7 +61,7 @@ [style.display]="!showDefaultData && !items && !loading ? 'block' : 'none'" > - {{ 'search.instruction' | translate }} + {{ searchInstruction | translate }} . + */ +import {Component} from '@angular/core'; +import {SCSearchFilter, SCThingType} from '@openstapps/core'; + +@Component({ + selector: 'stapps-choose-events-page', + templateUrl: 'choose-events-page.html', +}) +export class ChooseEventsPageComponent { + forcedFilter: SCSearchFilter = { + arguments: { + field: 'type', + value: SCThingType.AcademicEvent, + }, + type: 'value', + }; +} diff --git a/src/app/modules/schedule/page/modal/modal-event-creator.html b/src/app/modules/schedule/page/choose-events-page.html similarity index 50% rename from src/app/modules/schedule/page/modal/modal-event-creator.html rename to src/app/modules/schedule/page/choose-events-page.html index 7f7b39aa..55cc1c78 100644 --- a/src/app/modules/schedule/page/modal/modal-event-creator.html +++ b/src/app/modules/schedule/page/choose-events-page.html @@ -1,5 +1,5 @@ - - - {{ 'schedule.addEventModal.addEvent' | translate | titlecase }} - - - {{ 'modal.DISMISS' | translate }} - - - - - - - + diff --git a/src/app/modules/schedule/page/modal/modal-event-creator.component.ts b/src/app/modules/schedule/page/modal/modal-event-creator.component.ts deleted file mode 100644 index 767aa25a..00000000 --- a/src/app/modules/schedule/page/modal/modal-event-creator.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2022 StApps - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ -import {Component, OnDestroy, OnInit} from '@angular/core'; -import {SCSearchFilter, SCThingType} from '@openstapps/core'; -import {ModalController} from '@ionic/angular'; -import {DataRoutingService} from '../../../data/data-routing.service'; -import {DataDetailComponent} from '../../../data/detail/data-detail.component'; -import {Subscription} from 'rxjs'; - -/** - * TODO - */ -@Component({ - selector: 'modal-event-creator', - templateUrl: 'modal-event-creator.html', - styleUrls: ['modal-event-creator.scss'], -}) -export class ModalEventCreatorComponent implements OnInit, OnDestroy { - subscriptions: Subscription[] = []; - - constructor(readonly modalController: ModalController, readonly dataRoutingService: DataRoutingService) {} - - ngOnInit() { - this.subscriptions.push( - this.dataRoutingService.itemSelectListener().subscribe(async item => { - const modal = await this.modalController.create({ - component: DataDetailComponent, - componentProps: { - isModal: true, - inputItem: item, - }, - canDismiss: true, - }); - return modal.present(); - }), - ); - } - - ngOnDestroy() { - for (const subscription of this.subscriptions) subscription.unsubscribe(); - } - - /** - * Forced filter - */ - filter: SCSearchFilter = { - arguments: { - field: 'type', - value: SCThingType.AcademicEvent, - }, - type: 'value', - }; -} diff --git a/src/app/modules/schedule/page/schedule-page.component.ts b/src/app/modules/schedule/page/schedule-page.component.ts index 815772fa..73860815 100644 --- a/src/app/modules/schedule/page/schedule-page.component.ts +++ b/src/app/modules/schedule/page/schedule-page.component.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 StApps + * Copyright (C) 2023 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. @@ -15,12 +15,13 @@ import {AfterViewInit, Component, HostListener, Input, OnInit, ViewChild} from '@angular/core'; import {Location} from '@angular/common'; import {ActivatedRoute, Router} from '@angular/router'; -import {IonRouterOutlet} from '@ionic/angular'; +import {AnimationController, IonRouterOutlet} from '@ionic/angular'; import {SharedAxisChoreographer} from '../../../animation/animation-choreographer'; import {materialSharedAxisX} from '../../../animation/material-motion'; import {ScheduleResponsiveBreakpoint} from './schema/schema'; import {CalendarService} from '../../calendar/calendar.service'; import moment from 'moment'; +import {fabExpand} from '../../../animation/fab-expand'; /** * This needs to be sorted by break point low -> high @@ -93,6 +94,8 @@ export class SchedulePageComponent implements OnInit, AfterViewInit { isModalOpen = false; + fabAnimation = fabExpand(this.animationController); + /** * Amount of days that should be shown according to current display width */ @@ -111,6 +114,7 @@ export class SchedulePageComponent implements OnInit, AfterViewInit { private calendarService: CalendarService, readonly routerOutlet: IonRouterOutlet, private router: Router, + private animationController: AnimationController, private location: Location, ) {} @@ -168,12 +172,4 @@ export class SchedulePageComponent implements OnInit, AfterViewInit { onTodayClick() { this.calendarService.emitGoToDate(moment().startOf('day')); } - - onFABClick() { - this.isModalOpen = true; - } - - onModalDismiss() { - this.isModalOpen = false; - } } diff --git a/src/app/modules/schedule/page/schedule-page.html b/src/app/modules/schedule/page/schedule-page.html index 95f0e267..2ef773ca 100644 --- a/src/app/modules/schedule/page/schedule-page.html +++ b/src/app/modules/schedule/page/schedule-page.html @@ -63,15 +63,15 @@ - + - - - - - - diff --git a/src/app/modules/schedule/schedule.module.ts b/src/app/modules/schedule/schedule.module.ts index 46865bbc..4c9d2d38 100644 --- a/src/app/modules/schedule/schedule.module.ts +++ b/src/app/modules/schedule/schedule.module.ts @@ -1,16 +1,16 @@ /* - * Copyright (C) 2022 StApps - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, version 3. + * Copyright (C) 2023 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; @@ -26,7 +26,6 @@ import {DataModule} from '../data/data.module'; import {DataProvider} from '../data/data.provider'; import {CalendarViewComponent} from './page/calendar-view.component'; import {ScheduleCursorComponent} from './page/grid/schedule-cursor.component'; -import {ModalEventCreatorComponent} from './page/modal/modal-event-creator.component'; import {SchedulePageComponent} from './page/schedule-page.component'; import {ScheduleSingleEventsComponent} from './page/schedule-single-events.component'; import {ScheduleViewComponent} from './page/schedule-view.component'; @@ -37,6 +36,7 @@ import {ThingTranslateModule} from '../../translation/thing-translate.module'; import {InfiniteSwiperComponent} from './page/grid/infinite-swiper.component'; import {CalendarComponent} from './page/components/calendar.component'; import {IonIconModule} from '../../util/ion-icon/ion-icon.module'; +import {ChooseEventsPageComponent} from './page/choose-events-page.component'; const settingsRoutes: Routes = [ {path: 'schedule', redirectTo: 'schedule/calendar/now'}, @@ -45,6 +45,8 @@ const settingsRoutes: Routes = [ {path: 'schedule/single', redirectTo: 'schedule/single/now'}, // calendar | recurring | single {path: 'schedule/:mode/:date', component: SchedulePageComponent}, + // TODO: this is temporary until the new generalized search page is finished + {path: 'schedule/:mode/:date/event-picker', component: ChooseEventsPageComponent}, ]; /** @@ -54,7 +56,7 @@ const settingsRoutes: Routes = [ declarations: [ CalendarComponent, CalendarViewComponent, - ModalEventCreatorComponent, + ChooseEventsPageComponent, ScheduleCardComponent, ScheduleCursorComponent, SchedulePageComponent, diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 78e91970..e4554570 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -454,8 +454,10 @@ "recurring": "Stundenplan", "calendar": "Kalender", "single": "Einzeltermine", - "addEventModal": { - "addEvent": "Events Hinzufügen" + "addEventPage": { + "TITLE": "Termine Hinzufügen", + "PLACEHOLDER": "Termine", + "SEARCH_INSTRUCTION": "Termine finden" }, "card": { "forEach": "Alle", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 769c8618..664a7e34 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -454,8 +454,10 @@ "recurring": "Recurring", "calendar": "Calendar", "single": "Single Events", - "addEventModal": { - "addEvent": "Add Events" + "addEventPage": { + "TITLE": "Add Events", + "PLACEHOLDER": "Events", + "SEARCH_INSTRUCTION": "Find events" }, "card": { "forEach": "Every",