ionic-team / capacitor

Build cross-platform Native Progressive Web Apps for iOS, Android, and the Web ⚡️
https://capacitorjs.com
MIT License
11.31k stars 962 forks source link

[Feature]: change native background in runtime #7389

Closed stewones closed 1 month ago

stewones commented 1 month ago

Description

there's a use case where changing the backgroundColor value in runtime would be awesome. Why? depending on the app theme (light/dark) and how you set those values in capacitor.config, there's a little flash when closing the native keyboard. (see video demo below)

Platforms

Request or proposed solution

import { Capacitor } from '@capacitor/core';

Capacitor.setBackgroundColor('#000');
Capacitor.ios.setBackgroundColor('#000');
Capacitor.android.setBackgroundColor('#000')

Alternatives

none that I can think of. so today we can either please dark mode users or light mode users. not both.

Additional Information

see the flash with native background set to #FFF and ionic dark theme. being able to change the native background in runtime would make hybrid apps' UX magnificence.

https://github.com/ionic-team/capacitor/assets/719763/a0114755-27f9-4ac7-8d85-302cc63226ac

DwieDima commented 1 month ago

Hi @stewones

The issue you're encountering is because the HTML content is moved on top of the keyboard (flickering). This is not a issue of Ionic or Capacitor, but how the Webview is handling resizing. You can manage the resizing on your own by changing capacitor.config.ts

const config: CapacitorConfig = {
   ...
    Keyboard: {
      resize: KeyboardResize.None,
    },
  ...
  },

};

https://capacitorjs.com/docs/apis/keyboard#configuration

You can also work with Ionic Keyboard Events to implement your own smooth keyboard transition like described in this blog.

You can read more about this issue here.

stewones commented 1 month ago

Hi @stewones

The issue you're encountering is because the HTML content is moved on top of the keyboard (flickering). This is not a issue of Ionic or Capacitor, but how the Webview is handling resizing. You can manage the resizing on your own by changing capacitor.config.ts

const config: CapacitorConfig = {
   ...
    Keyboard: {
      resize: KeyboardResize.None,
    },
  ...
  },

};

https://capacitorjs.com/docs/apis/keyboard#configuration

You can also work with Ionic Keyboard Events to implement your own smooth keyboard transition like described in this blog.

You can read more about this issue here.

I don't want to change the resize behavior, it doesn't work well for my use case.

yes I believe this is an issue with Capacitor and the webview. have you watched the video in full? see what happens in the end, the flash stops when I switch the OS theme to light.

That's true the other way around too. here's a video with the setting inverted (native background #000 and ionic light theme)

// capacitor.json
{
    "backgroundColor": "#000000",
    "ios": {
        "backgroundColor": "#000000"
    },
    "android": {
        "backgroundColor": "#000000"
    }
}

https://github.com/ionic-team/capacitor/assets/719763/ee5e119d-d9d5-4df7-942d-2df5b45eda1c

jcesarmobile commented 1 month ago

As DwieDima said, this is really the WebView internals resizing, in the Capacitor Testapp I've set the background color to red to make it more evident and I see black flashes instead of red flashes. So adding this feature might fix the problem for you, but not for all users, will depend on the app internals.

The backgroundColor was added for preventing a flash when changing from the splash screen to the WebView, that's why it's only considered to be set on the app startup and not programmatically, because in most cases the background color will not be seen again once the WebView appears.

The backgroundColor configuration just sets the background color of the Webview on Android and the WebView and it's ScrollView on iOS, that's accessible from plugins code, so you could create a plugin that sets the background color from native side.

stewones commented 1 month ago

As DwieDima said, this is really the WebView internals resizing, in the Capacitor Testapp I've set the background color to red to make it more evident and I see black flashes instead of red flashes. So adding this feature might fix the problem for you, but not for all users, will depend on the app internals.

The backgroundColor was added for preventing a flash when changing from the splash screen to the WebView, that's why it's only considered to be set on the app startup and not programmatically, because in most cases the background color will not be seen again once the WebView appears.

The backgroundColor configuration just sets the background color of the Webview on Android and the WebView and it's ScrollView on iOS, that's accessible from plugins code, so you could create a plugin that sets the background color from native side.

Thanks @jcesarmobile that makes sense. Yeah I recall having long conversations on Slack with you about the backgroundColor capability, back in the Capacitor alpha/beta days.

While I disagree with you about the functionality only benefiting my use case, I agree that a plugin could make this happen and that was my plan since the beginning. I just thought that having it in the core would make more sense.

btw here's a video with the native background set as red as you mentioned. I barely see any black flash on both themes. so I still believe this could be a great addition.

https://github.com/ionic-team/capacitor/assets/719763/b3adb789-9017-4418-b62f-98ec8c52eea9

stewones commented 1 month ago

and this is why KeyboardResize.None doesn't work for me. I doubt this is only my case.

https://github.com/ionic-team/capacitor/assets/719763/fddb9b1f-c73b-47c6-9d09-75b0ebb3d9ab

DwieDima commented 1 month ago

@stewones if you use Angular I can share a snippet where im also using KeyboardResize.None and implemented my own css animation using a directive where the ion-footer get animated on top of keyboard:

import { Directive, ElementRef } from '@angular/core';
import { Capacitor, PluginListenerHandle } from '@capacitor/core';
import { Keyboard, KeyboardResize } from '@capacitor/keyboard';
import { AnimationController } from '@ionic/angular';

@Directive({
  selector: '[cdfyAnimateWithKeyboard]'
})
export class AnimateWithKeyboardDirective {
  public keyboardWillShowListener?: PluginListenerHandle;
  public keyboardWillHideListener?: PluginListenerHandle;
  public resizeModeBackup?: KeyboardResize;
  public keyBoardHeight = 0;
  public isKeyboardVisible = false;
  public isIos = Capacitor.getPlatform() === 'ios';
  public animation = this.animationController
    .create()
    .addElement(this.elementRef.nativeElement)
    .easing('cubic-bezier(0.17,0.54,0.42,0.79)')
    .duration(250);

  public constructor(
    private elementRef: ElementRef<HTMLElement>,
    private animationController: AnimationController
  ) {}

  public async ngOnInit(): Promise<void> {
    // Only handle keyboard events on ios
    if (this.isIos) {
      await this.handleKeyboardWillShow();
      await this.handleKeyboardWillHide();
    }
  }

  public async handleKeyboardWillShow(): Promise<void> {
    this.resizeModeBackup = await Keyboard.getResizeMode().then(
      (result) => result.mode
    );
    await Keyboard.setResizeMode({ mode: KeyboardResize.None });

    this.keyboardWillShowListener = await Keyboard.addListener(
      'keyboardWillShow',
      ({ keyboardHeight }) => {
        this.keyBoardHeight = keyboardHeight;
        this.animation.keyframes([
          { offset: 0, transform: 'translate3d(0, 0, 0)' },
          {
            offset: 1,
            transform: `translate3d(0, -${this.keyBoardHeight}px, 0)`
          }
        ]);
        if (!this.isKeyboardVisible) {
          this.animation.play().catch(() => {});
          this.isKeyboardVisible = true;
        }
      }
    );
  }

  public async handleKeyboardWillHide(): Promise<void> {
    this.keyboardWillHideListener = await Keyboard.addListener(
      'keyboardWillHide',
      () => {
        this.animation.keyframes([
          {
            offset: 0,
            transform: `translate3d(0, -${this.keyBoardHeight}px, 0)`
          },
          { offset: 1, transform: 'translate3d(0, 0, 0)' }
        ]);
        if (this.isKeyboardVisible) {
          this.animation.play().catch(() => {});
          this.isKeyboardVisible = false;
        }
      }
    );
  }

  public async ngOnDestroy(): Promise<void> {
    const promises: Promise<void>[] = [];
    if (this.resizeModeBackup) {
      promises.push(Keyboard.setResizeMode({ mode: this.resizeModeBackup }));
    }

    if (this.keyboardWillShowListener) {
      promises.push(this.keyboardWillShowListener.remove());
    }

    if (this.keyboardWillHideListener) {
      promises.push(this.keyboardWillHideListener.remove());
    }

    await Promise.all(promises);
  }
}
<ion-footer cdfyAnimateWithKeyboard>
  <ion-toolbar color="light">
    <ion-button
      color="primary"
      expand="block"
    >
      Fortfahren
    </ion-button>
  </ion-toolbar>
</ion-footer>

Using this approach, there is also no flicker. Here's the result:

https://github.com/ionic-team/capacitor/assets/26873275/2ea5e3a1-f0ce-451e-929b-aa1734d85b98

stewones commented 1 month ago

Hell yeah, that's even better. Thanks for sharing 👍

ionitron-bot[bot] commented 3 weeks ago

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Capacitor, please create a new issue and ensure the template is fully filled out.