mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2025-12-13 17:56:20 +00:00
fix: make keyboard dismissable on mobile devices
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest package="de.anyschool.app" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
|
||||
<activity android:exported="true" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:label="@string/title_activity_main" android:launchMode="singleTask" android:name="de.anyschool.app.MainActivity" android:theme="@style/AppTheme.NoActionBarLaunch">
|
||||
<activity android:exported="true" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:windowSoftInputMode="adjustPan" android:label="@string/title_activity_main" android:launchMode="singleTask" android:name="de.anyschool.app.MainActivity" android:theme="@style/AppTheme.NoActionBarLaunch">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
||||
@@ -22,6 +22,7 @@ import {environment} from '../environments/environment';
|
||||
import {StatusBar, Style} from '@capacitor/status-bar';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.service';
|
||||
import {Keyboard, KeyboardResize} from '@capacitor/keyboard';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@@ -45,6 +46,11 @@ export class AppComponent implements AfterContentInit {
|
||||
title: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Angular component selectors that should not infulence keyboard state
|
||||
*/
|
||||
ommitedEventSources = ['ion-input', 'ion-searchbar'];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param platform TODO
|
||||
@@ -104,6 +110,11 @@ export class AppComponent implements AfterContentInit {
|
||||
'others',
|
||||
]);
|
||||
});
|
||||
|
||||
window.addEventListener('touchmove', this.touchMoveEvent, true);
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
Keyboard.setResizeMode({mode: KeyboardResize.None});
|
||||
}
|
||||
}
|
||||
|
||||
private async authNotificationsInit() {
|
||||
@@ -129,4 +140,30 @@ export class AppComponent implements AfterContentInit {
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if keyboard should be dissmissed
|
||||
*/
|
||||
touchMoveEvent = (event: Event): void => {
|
||||
if (
|
||||
this.ommitedEventSources.includes(
|
||||
(event?.target as unknown as Record<string, string>)?.[
|
||||
's-hn'
|
||||
]?.toLowerCase(),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.unfocusActiveElement();
|
||||
};
|
||||
|
||||
/**
|
||||
* Loses focus on the currently active element (meant for input fields).
|
||||
* Results in virtual keyboard being dissmissed on native and web plattforms.
|
||||
*/
|
||||
unfocusActiveElement() {
|
||||
const activeElement = document.activeElement;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(activeElement as any)?.blur();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,10 @@
|
||||
|
||||
<ion-content fullscreen="true" #ionContent>
|
||||
<stapps-navigation-section></stapps-navigation-section>
|
||||
<stapps-search-section></stapps-search-section>
|
||||
<stapps-search-section
|
||||
#search
|
||||
(focusin)="onSearchBarFocus($event)"
|
||||
></stapps-search-section>
|
||||
<stapps-news-section></stapps-news-section>
|
||||
<stapps-mensa-section></stapps-mensa-section>
|
||||
<stapps-favorites-section></stapps-favorites-section>
|
||||
|
||||
@@ -52,6 +52,8 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('schedule', {read: ElementRef}) scheduleRef: ElementRef;
|
||||
|
||||
@ViewChild('search', {read: ElementRef}) searchRef: ElementRef;
|
||||
|
||||
@ViewChild('ionContent') ionContentRef: IonContent;
|
||||
|
||||
collapseAnimation: DashboardCollapse;
|
||||
@@ -88,6 +90,11 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Offset from search bar to top
|
||||
*/
|
||||
searchToTopOffset = 0;
|
||||
|
||||
constructor(
|
||||
private readonly dataRoutingService: DataRoutingService,
|
||||
private scheduleProvider: ScheduleProvider,
|
||||
@@ -121,6 +128,11 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
ionViewDidEnter() {
|
||||
this.searchToTopOffset =
|
||||
this.searchRef.nativeElement?.getBoundingClientRect().top - 100;
|
||||
}
|
||||
|
||||
async loadNextEvent() {
|
||||
const dataSeries = await this.scheduleProvider.getDateSeries(
|
||||
this.uuids,
|
||||
@@ -142,4 +154,15 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
this._uuidSubscription.unsubscribe();
|
||||
this.collapseAnimation.destroy();
|
||||
}
|
||||
|
||||
async onSearchBarFocus(_event: Event) {
|
||||
this.ionContentRef.getScrollElement().then(element => {
|
||||
if (
|
||||
element.scrollHeight - element.clientHeight >=
|
||||
this.searchToTopOffset
|
||||
) {
|
||||
this.ionContentRef.scrollToPoint(0, this.searchToTopOffset, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
<div class="searchbar">
|
||||
<ion-input
|
||||
type="search"
|
||||
enterkeyhint="search"
|
||||
placeholder="{{ 'search.search_bar.placeholder' | translate }}"
|
||||
(submit)="onSubmitSearch()"
|
||||
(keyup.enter)="onSubmitSearch()"
|
||||
(search)="onSubmitSearch()"
|
||||
[(ngModel)]="searchTerm"
|
||||
></ion-input>
|
||||
<ion-icon
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
border-radius: var(--border-radius-default);
|
||||
--padding-start: var(--spacing-md);
|
||||
--padding-end: var(--spacing-md);
|
||||
--padding-top: var(--spacing-xl);
|
||||
--padding-bottom: var(--spacing-xl);
|
||||
font-size: var(--font-size-xs);
|
||||
--placeholder-font-weight: var(--font-weight-bold);
|
||||
--padding-top: var(--spacing-md);
|
||||
--padding-bottom: var(--spacing-md);
|
||||
box-shadow: var(--shadow-default);
|
||||
}
|
||||
ion-icon {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
*/
|
||||
import {Component} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
import {Keyboard} from '@capacitor/keyboard';
|
||||
|
||||
/**
|
||||
* Shows a search input field
|
||||
@@ -32,6 +34,17 @@ export class SearchSectionComponent {
|
||||
* User submits search
|
||||
*/
|
||||
onSubmitSearch() {
|
||||
this.router.navigate(['/search', this.searchTerm]);
|
||||
this.router
|
||||
.navigate(['/search', this.searchTerm])
|
||||
.then(() => this.hideKeyboard());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides keyboard in native app environments
|
||||
*/
|
||||
hideKeyboard() {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
Keyboard.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<ion-searchbar
|
||||
(ngModelChange)="searchStringChanged($event)"
|
||||
(keyup.enter)="hideKeyboard()"
|
||||
(search)="hideKeyboard()"
|
||||
[(ngModel)]="queryText"
|
||||
showClearButton="always"
|
||||
placeholder="{{ 'search.search_bar.placeholder' | translate }}"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ion-searchbar
|
||||
(ngModelChange)="searchStringChanged($event)"
|
||||
(keyup.enter)="hideKeyboard()"
|
||||
(search)="hideKeyboard()"
|
||||
[(ngModel)]="queryText"
|
||||
mode="md"
|
||||
placeholder="{{ 'hebisSearch.search_bar.placeholder' | translate }}"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<ion-searchbar
|
||||
(keyup)="searchKeyUp($event)"
|
||||
(keyup.enter)="hideKeyboard()"
|
||||
(search)="hideKeyboard()"
|
||||
[(ngModel)]="queryText"
|
||||
(ionClear)="searchStringChanged()"
|
||||
mode="md"
|
||||
|
||||
Reference in New Issue
Block a user