Keyboard pushing action-bar up. It need it to stay always top


#1

Hi everyone,

I have a chat application,
with an action bar.

When the user press to type,
the keyboard slides in and drag everything up.

Which’s okay for the conversation,
but the action-bar should always remain on top.

At the moment, the action bar is being pushed up with the whole view (except the TextView to type; already fixed).

This only happens on iOS.
In Android works fine.

Pictures
Init of the messages-profile

Notice how the action-bar’s already being in the status-bar. This is when I press the keyboard but hide the keyboard.

And this is what happens when the keyboard is on

Thanks in advance.
mQtik


#2

Init of messages profile

The action bar is there.


#3

Now, let’s open the keyboard hided, and notice how the action-bar is scrolled.


#4

HTML Code

<action-bar-general
    [title]="getPartnerName()"
>
    <ActionItem
        [visibility]="isInfo ? 'visible' : 'collapsed'"
        (tap)="goToProfile()"
        android.position="right"
        class="action-bar-item"
        icon="res://ic_info_outline"
        ios.position="right"
    >
        <StackLayout
            *ngIf="inIOS"
        >
            <Image
                height="25"
                src="res://ic_info_outline"
                stretch="aspectFit"
                width="25"
            ></Image>
        </StackLayout>
    </ActionItem>
</action-bar-general>
<GridLayout
    [visibility]="isLoading ? 'collapsed' : 'visible'"
    class="message-profile"
    columns="*"
    rows="auto, *, auto"
>
    <StackLayout
        [height]="loadMoreHeight"
        [visibility]="showLoadMore ? 'visible' : 'collapsed'"
        class="messages-load-more"
        horizontalAlignment="center"
        orientation="horizontal"
        verticalAlignment="center"
        col="0"
        row="0"
    >
        <ActivityIndicator
            busy="true"
            class="activity-indicator"
            horizontalAlignment="center"
            verticalAlignment="center"
        ></ActivityIndicator>
    </StackLayout>
    <ScrollView
        #chatMessages
        (scroll)="onScroll($event)"
        (touch)="onTouch($event)"
        [visibility]="isLoading ? 'collapsed' : 'visible'"
        class="list-messages"
        col="0"
        row="1"
    >
        <StackLayout>
            <item-conversation
                *ngFor="let item of listElements"
                [item]="item"
            ></item-conversation>
        </StackLayout>
    </ScrollView>
    <!-- Message form -->
    <GridLayout
        [formGroup]="messageForm"
        col="0"
        columns="*, 40"
        row="2"
        rows="auto"
        class="form-message"
    >
        <StackLayout
            [height]="textViewHeight"
            class="m-r-10"
            col="0"
            row="0"
        >
            <TextView
                #chatTextView
                [hint]="'messages:form:placeholder' | L"
                (textChange)="onTextChange($event)"
                formControlName="messageText"
                class="form-input-message"
                returnKeyType="send"
            ></TextView>
        </StackLayout>
        <StackLayout
            col="1"
            row="0"
            verticalAlignment="top"
        >
            <Button
                [isEnabled]="messageForm.valid"
                (tap)="sendMessage()"
                class="btn-message"
            ></Button>
        </StackLayout>
    </GridLayout>
</GridLayout>
<!-- Loading -->
<loading
    *ngIf="isLoading"
></loading>


#5

TS Code

import { MessagesService } from '~/modules/ng-services/messages.service';
import { Component, OnInit, ViewChild, ElementRef, NgZone } from "@angular/core";
import { Page } from "tns-core-modules/ui/page/page";
import { isIOS } from "tns-core-modules/platform/platform";
import { TextView } from "ui/text-view";
import { RouterExtensions } from 'nativescript-angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ScrollView, ScrollEventData } from 'ui/scroll-view';
import { TouchGestureEventData } from "tns-core-modules/ui/gestures/gestures";
import * as GoogleAnalytics from "nativescript-google-analytics";
import { ActivatedRoute } from '@angular/router';
import { LocalStorage } from '~/modules/common/storage/local-storage';
import { TnsEcho } from 'nativescript-laravel-echo';
import { on as applicationOn, launchEvent, suspendEvent, resumeEvent, exitEvent, lowMemoryEvent, uncaughtErrorEvent, ApplicationEventData, start as applicationStart } from "application";
import * as application from 'application';
const TnsOneSignal = require("nativescript-onesignal").TnsOneSignal;
const config = require('../../../config.json');
@Component({
    moduleId: module.id,
    templateUrl: "./message-profile.component.html"
})
export class MessageProfileComponent implements OnInit {

    @ViewChild('chatMessages') chatMessages: ElementRef;
    @ViewChild('chatTextView') chatTextView: ElementRef;

    public readonly maxTextHeight: number = isIOS ? 106 : 100;
    public readonly loadMoreHeight: number = 50;
    public readonly loadMoreScrollTrigger: number = isIOS ? this.loadMoreHeight : 0;

    private Echo: TnsEcho;
    public listElements: Array<Object> = [];
    public isLoading: boolean = true;
    public isLoadingMore: boolean = false;
    public showLoadMore: boolean = false;
    public inIOS: boolean = isIOS;
    public textViewHeight: string = 'auto';
    public messageForm: FormGroup;
    public currentTextViewHeight: number = 0;
    public lastTextViewHeight: number = 0;
    public currentScroll: number = 0;
    public conversationId: number;
    public mconversationId: number;
    public userOrBusinessId: number;
    public conversationType: string;
    public receiverId: string;
    public currentPage: any;
    public lastPage: any;
    public toType: string;
    public receiverName: string;
    public isInfo: boolean;
    public user: any;

    constructor(
        private ngZone: NgZone,
        private formBuilder: FormBuilder,
        private messagesService: MessagesService,
        private activatedRoute: ActivatedRoute,
        private page: Page,
        private localStorage: LocalStorage,
        private routerExtensions: RouterExtensions
    ) {

        this.receiverName = this.activatedRoute.snapshot.queryParams['name'];
        this.conversationId = this.activatedRoute.snapshot.queryParams['conversationId'];
        this.mconversationId = this.activatedRoute.snapshot.queryParams['mconversationId'];
        this.userOrBusinessId = this.activatedRoute.snapshot.queryParams['userOrBusinessId'];
        this.conversationType = this.activatedRoute.snapshot.queryParams['conversationType'];
        this.toType = this.activatedRoute.snapshot.queryParams['toType'];

        this.page.actionBarHidden = false;

        this.messageForm = this.formBuilder.group({
            messageText: ['', [Validators.required]],
        });

        this.user = this.localStorage.getData('user');

    }

    /**
     * Init
     */
    public ngOnInit(): void {

        GoogleAnalytics.logView("Message Profile View");

        this.validateIfExistsMessages();
        this.validateIfUserStandard();
        this.initChannel();

    }

    public initChannel() {

        this.Echo = new TnsEcho({
            broadcaster: 'socket.io',
            host: 'http://app.rideguidemobileapp.com:6001',
            auth: {
                headers: {
                    Authorization: 'Bearer ' + this.user['access_token']
                }
            }
        });

        let channel = this.conversationType == 'standard' ? 'messages.standard.' : 'messages.business.';
        this.Echo.private(channel + this.userOrBusinessId)
            .listen('NewMessage', response => {

                this.ngZone.run(() => {

                    let theTime: Date = new Date(response['message']['created_at']);
                    // Notifica la carga de los mensajes nuevamente.
                    this.messagesService.messagesEvent();
                    this.listElements.push(
                        {
                            image: '',
                            date: theTime.getHours() + ':' + theTime.getMinutes(),
                            text: response['message']['message'],
                            id: 12 // default any number
                        }
                    );

                    this.scrollMessagesToBottom(250, true);

                });

            });

    }

    /**
     * Check if there is a conversation
     */
    public validateIfExistsMessages() {

        if (this.mconversationId != undefined) {

            this.loadData();

        } else {

            this.isLoading = false;

        }

    }

    /**
     * Valid if you are a standard user with whom you will talk
     */
    public validateIfUserStandard() {

        if (this.conversationType == 'business' && this.toType == 'standard') {

            this.isInfo = false;

        } else {

            this.isInfo = true;

        }

    }

    /**
     * Scroll messages to bottom
     */
    public scrollMessagesToBottom(scrollDelay: number = 250, animate: boolean = false): void {

        let messagesScrollView: ScrollView = this.chatMessages.nativeElement;

        if (!this.listElements.length) {

            return;

        }

        setTimeout(() => {

            messagesScrollView.scrollToVerticalOffset(messagesScrollView.scrollableHeight, animate);

        }, scrollDelay);

    }

    /**
     * Load data
     */
    public loadData(): void {

        this.isLoading = true;
        this.listElements.length = 0;

        setTimeout(() => {

            let type = this.conversationType == 'business' ? 'business' : 'users';
            this.messagesService.getMessage(
                type + "/" + this.userOrBusinessId + "/conversations/" + this.mconversationId + "/messages",
                this.userOrBusinessId
            ).subscribe(response => {

                this.currentPage = response['paginator']['current_page'];
                this.lastPage = response['paginator']['last_page'];
                this.listElements.push(...response['data']);
                this.scrollMessagesToBottom(150);
                this.isLoading = false;

            }, error => {

                console.log(error);

            });

        }, 150);

    }

    /**
     * Messages list scroll event
     * @param args
     */
    public onScroll(args: ScrollEventData): void {

        this.currentScroll = args.scrollY;

    }

    /**
     * Touch for the scrollview, decides when / if to show the "loading more" animation
     * @param args
     */
    public onTouch(args: TouchGestureEventData) {

        // user is scrolling
        if (args.action == "move") {

            if ((this.currentScroll <= -(this.loadMoreScrollTrigger)) && !this.showLoadMore) {

                this.showLoadMore = true;
                return;

            }

        }

        // user lifts their finger, begin loading older messages
        if (args.action == "up" || args.action == "cancel") {

            if (this.showLoadMore) {

                this.loadMore();

            }

        }

    }

    /**
     * Add older messages
     */
    public loadMore(): void {

        if (this.isLoadingMore) {
            return;
        }

        const currentScrollHeight: number = this.chatMessages.nativeElement.scrollableHeight + 50;

        this.isLoadingMore = true;

        setTimeout(() => {

            if (this.currentPage < this.lastPage) {

                let type = this.conversationType == 'business' ? 'business' : 'users';
                this.messagesService.getMessage(
                    type + "/" + this.userOrBusinessId + "/conversations/" + this.mconversationId + "/messages?page=" + (this.currentPage + 1),
                    this.userOrBusinessId
                ).subscribe(response => {

                    this.currentPage = response['paginator']['current_page'];
                    this.lastPage = response['paginator']['last_page'];
                    this.listElements.unshift(...response['data']);

                }, error => {

                    console.log(error);

                });

            }

            this.showLoadMore = false;
            this.isLoadingMore = false;

            // scroll to latest loaded message
            setTimeout(() => {

                this.chatMessages.nativeElement.scrollToVerticalOffset((this.chatMessages.nativeElement.scrollableHeight - currentScrollHeight), false);

            }, 150);

        }, 250);

    }

    /**
     * Get partner name
     */
    public getPartnerName(): string {

        return this.receiverName;

    }

    /**
     * Go to user profile
     */
    public goToProfile(): void {

        this.routerExtensions.navigate(['/business/business-profile/', this.conversationId]);

    }

    /**
     * User writes something
     */
    public onTextChange(args: any): void {

        let textview: TextView = <TextView>args.object;

        if (textview.getActualSize().height > this.maxTextHeight) {

            this.textViewHeight = this.maxTextHeight.toString();
            this.scrollMessagesToBottom(0);

        }
        else {

            this.textViewHeight = 'auto';
            this.scrollMessagesToBottom(0);

        }

    }

    /**
     * Send message
     */
    public sendMessage(): void {

        let theTime: Date = new Date();
        let textview: TextView = <TextView>this.chatTextView.nativeElement;

        let from = this.conversationType == "standard" ? "users" : this.conversationType;
        let to = this.toType == "standard" ? "users" : this.toType;
        let query = from + "/" + this.userOrBusinessId + "/" + to + "/" + this.conversationId + "/messages";
        let iduser = this.userOrBusinessId;

        this.messagesService.sendMessage(query, this.messageForm.controls['messageText'].value, iduser)
            .subscribe(response => {


                let image = response['data']['from']['logo'];
                // Notifica la carga de los mensajes nuevamente.

                this.messagesService.messagesEvent();
                this.listElements.push(
                    {
                        image: image == null ? "" : image['small'],
                        date: theTime.getHours() + ':' + theTime.getMinutes(),
                        text: this.messageForm.controls['messageText'].value,
                        id: 'me'
                    }
                );

                this.messageForm.controls['messageText'].reset();
                textview.height = 0;    // <hack: if we don't do this height won't reset>
                textview.height = 'auto'; // </hack>
                this.textViewHeight = 'auto';

                this.scrollMessagesToBottom(250, true);

            }, error => {

                console.log(error);

            });

    }

}


#6

Are you using a Dock layout for the chat textbox at bottom? In iOS you should use a keyboard toolbar for that purpose.