Quantcast
Channel: Ionic Forum - Latest topics
Viewing all 70911 articles
Browse latest View live

How can I go from ionic 4.0 to version 8.0?

$
0
0

Please describe the question in detail and share your code, configuration, and other relevant info.

A while ago I made an application using ionic 4.0 and I would like to update it to version 8.0. I would like to know how I can do it and what changes I should make in the code so that there is no problem.

For example, should I make changes to the dependencies?

2 posts - 2 participants

Read full topic


React.js IonTabs: How to define pages that do not include the tab bar at the bottom

$
0
0

I am wondering:
How to define routes of pages that do not include the tab bar at the bottom, in Ionic React.

I used to use Angular.
In Angular Ionic I defined the tab routes as such, inside routes.ts:

export const routes: Routes = [
  {
    path: '',
    loadChildren: () => import('./pages/tabs/tabs.routes').then((m) => m.tabsRoutes),
  },
  {
    path: 'tabs/news/profile/:uid',
    loadComponent: () => import('./pages/profile/profile.page').then((m) => m.ProfilePage),
  },
...
]

Here, the profile.page would not have the tab bar, since it’s not defined inside tabsRoutes.

For further reference, here is how my tabsRoutes looks like in Angular:

export const tabsRoutes: Routes = [
  {
    path: '',
    redirectTo: '/tabs/home',
    pathMatch: 'full'
  },
  {
    path: 'tabs',
    component: TabsPage,
    canActivate: [entryDialogGuard],
    children: [
      {
        path: '',
        redirectTo: '/tabs/home',
        pathMatch: 'full'
      },
      {
        path: 'home',
        loadComponent: () => import('../home/home.page').then((m) => m.HomePage),
      },
      ...
    ]
  }

The home.page of course includes the tab bar at the bottom, since it’s defined as a child of tabs.routes.

I am wondering whether ionic react.js provides any similar clean way to achieve the same.

All I saw was this post:

But it only provided a few strange hacks and is quite outdated. Not sure if this is the recommended solution.

I also checked the docs but I did not find anything on this matter, which I find weird since this is quite a common requirement for tabbed apps.

1 post - 1 participant

Read full topic

Capacitor Android Edge-to-Edge Support Plugin - Capawesome

Capacitor 7, Android 29, SplashScreen.hide.. not hiding

$
0
0

i just updated my app to Capacitor v7, and am testing it to be sure that all is good.

Being the glutton for punishment that I am, i’ve had a low bar of Android SDK v29. I’m finding that in my app initialization i need to put a delay of a couple hundred msec before I call SplashScreen.hide() or it never hides. Android SDK 33 and 35 seem just fine without it.

Anyone else seeing that? (or did I miss the part where you have to be on something > Android 11)

snippet from my AppShell.tsx

if (Capacitor.isNativePlatform()) {
  setTimeout(() => { // new code as a workaround.
    SplashScreen.hide(); // original code
  }, 200); 
} else {
// web things
}

Note, that I’m using the emulator, not a real device. I don’t have a device that old handy.

1 post - 1 participant

Read full topic

Ionic gallery permission not working

$
0
0

I am trying simple task to open the gallery via btn click. If gallery permissions are not given to application it should prompt the user to enable them else gallery should be opened.

I am trying to check the gallery permission before opening the gallery using requestPermission() Camera Plugin. Here is sample code

const openGallery = () => {
  debugger;
  Camera.requestPermissions({ permission: ["photos"] })
    .then((permission) => {
      console.log("photo permission");
      console.log(permission.photos);
    })
    .catch((err) => {
      console.log(err);
    });

  Camera.requestPermissions()
    .then((permission) => {
      console.log("All permission");
      console.log(permission.photos);
      console.log(permission.camera);
    })
    .catch((err) => {
      console.log(err);
    });
};

I’ve tried calling Camera.requestPermission() method in both ways(with passing “photos” prop and without it). In android logcat I can see camera permission is denied but gallery permission is enabled even though in app settings both the permissions are denied.

Here is my android manifest file showing permissions required.

<!-- Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- For Android 12 and below -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- For Android 13+ -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

Logcat output:

1 post - 1 participant

Read full topic

Ionic Build debug vs production

$
0
0

Hi

How can I verify that Ionic Build – prod has improved my App, as the generated www folder seems to have the same file size?

3 posts - 2 participants

Read full topic

My app doesn't work with Capacitor 7 (Android) - All plugins are "not implemented"

$
0
0

I’m using all the latest packages, ios works fine, android gives me this error:

6 posts - 2 participants

Read full topic

Styling all parts of component?

$
0
0

Hi!
why in component like ion-checkbox not all parts of component have part attribute ? for example i have margin on class native-wrapper but i cant fix it (or i can?), may be we need more flexible ui components?
thanks

<div class="native-wrapper" part="?">
  <svg class="checkbox-icon" viewBox="0 0 24 24" part="container">
    {path}
  </svg>
</div>

1 post - 1 participant

Read full topic


Capacitor community plugins camera-preview and video-recorder conflicting

$
0
0

Hello!
I’m somewhat new to ionic and capacitor, so far I’ve managed to teach myself well enough and being able to solve most problems, but this one is driving me crazy.
So, I’ve implemented two capacitor community plugins in my app, which I’m developing by using live-reload on my android device. The app is mostly a bunch of forms which the user fills with typed in data and also pictures, videos and files. For the first two I’m using the mentioned plugins, and I implemented them by making a page for each one and then calling those pages as modals in the forms, I mention this because its necessary for the plugins to be implemented as modals.
I noticed a weird behavior that only happens when I use the video-recorder plugin first, whenever this plugin is used, no matter if I correctly destroy the VideoRecorder instance or not, my implementation of camera-preview wont work as intended, the camera will work fine, buttons too If I take a picture it will display, but the modal screen/layer will stay white, it wont become transparent. BUT, if I use the camera-preview modal first, it will work perfectly, until I use the video-recorder again.
From my testing I’ve gathered the following:

  • No matter how the video-recorder is implemented, directly on a page or called as a modal, using it causes problems to the other plugin.
  • If I implement camera-preview in a page without being called as a modal, it works well, background becomes transparent (same code).
  • If I close the modal of camera-preview without stopping the camera properly I can see what the camera sees in the page that called the camera as a modal (background is transparent here).
  • Usage of camera-preview as a page wont conflict with the instance implemented as modal.

I’ve tried everything, styling, settings redoing the code, I don’t seem to be able to correctly pinpoint the problem to solve it, my best guess is that the video-recorder plugin.

The following is the relevant code for the tabs which contain the plugins and/or modal calls, its mostly borrowed code from guides and repos and its implemented this way because its easier to test.

I’m using the base tabs template of the ionic project, I implemented video-recorder in tab5, in tab6 I have a button to call the camera-preview modal, which is implemented on a page called ‘preview’.

tab5.page.ts

import { Capacitor } from '@capacitor/core';
import { SqliteService } from '../services/sqlite.service';
import { CapacitorSQLite, capSQLiteChanges, capSQLiteValues } from '@capacitor-community/sqlite';
import { ActivatedRoute } from '@angular/router';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { IonModal, ModalController, NavController } from '@ionic/angular';
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
import {
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonButton, IonFooter, IonLabel, IonIcon, IonList, IonItem, IonPopover,
  Platform} from '@ionic/angular/standalone';
import {
  VideoRecorder,
  VideoRecorderCamera,
  VideoRecorderPreviewFrame,
  VideoRecorderQuality,
} from '@capacitor-community/video-recorder';
import { CommonModule } from '@angular/common';
import { addIcons } from 'ionicons';
import {
  videocam,
  stopCircleOutline,
  cameraReverseOutline,
  folderOutline,
  folderOpenOutline,
  settingsOutline
} from 'ionicons/icons';
import { Directory, Encoding, Filesystem } from '@capacitor/filesystem';
import { ScreenOrientation } from '@capacitor/screen-orientation';
import { FilePicker, PickedFile } from '@capawesome/capacitor-file-picker';
import { ReactiveFormsModule, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { AuthService } from '../services/auth.service';
import { Preferences } from '@capacitor/preferences';
import { Device } from '@capacitor/device';


const VID_LIST = 'my-vid-list';

@Component({
  selector: 'app-tab5',
  templateUrl: './tab5.page.html',
  styleUrls: ['./tab5.page.scss'],
})

export class Tab5Page implements OnInit, OnDestroy {
  private platform = inject(Platform);
  videos: { url: string, metadata?: { size: string; duration: string } }[] = [];
  video_paths: string;
  initialized = false;
  isRecording = false;
  showVideos = false;
  durationIntervalId!: any;
  duration = "00:00";
  quality = VideoRecorderQuality.MAX_720P;
  public files: PickedFile[] = [];
  video_lists: string[];

  public formGroup = new UntypedFormGroup({
    types: new UntypedFormControl([]),
    limit: new UntypedFormControl(0),
    readData: new UntypedFormControl(false),
    skipTranscoding: new UntypedFormControl(false),
  });

  videoQualityMap = [
    { command: VideoRecorderQuality.HIGHEST, label: 'Highest' },
    { command: VideoRecorderQuality.MAX_2160P, label: '2160P' },
    { command: VideoRecorderQuality.MAX_1080P, label: '1080p' },
    { command: VideoRecorderQuality.MAX_720P, label: '720p' },
    { command: VideoRecorderQuality.MAX_480P, label: '480p' },
    { command: VideoRecorderQuality.QVGA, label: 'QVGA' },
    { command: VideoRecorderQuality.LOWEST, label: 'Lowest' },
  ]
  constructor(private authService: AuthService) {
    addIcons({
      videocam,
      stopCircleOutline,
      cameraReverseOutline,
      folderOutline,
      folderOpenOutline,
      settingsOutline
    })
   }

  async ngOnInit() {
    // this.initialise();
  }

  async ionViewWillEnter() {
    const ionContent = document.querySelector('ion-content');

    if (ionContent) {
      const styles = getComputedStyle(ionContent);
      console.log('Background:', styles.getPropertyValue('--background'));
      console.log('Color:', styles.getPropertyValue('color'));
    }

    this.initialise();

    ScreenOrientation.removeAllListeners();
    ScreenOrientation.addListener('screenOrientationChange', async (res) => {
      if (this.initialized && !this.isRecording) {
        await this.destroyCamera();
        await this.initialise();
      }
    });

    await this.getVids();
  }

  ngOnDestroy(): void {
    this.destroyCamera();
    
    ScreenOrientation.removeAllListeners();
  }

  private async getVids(){
    const list = await Preferences.get({ key: VID_LIST });
		if (list && list.value) {
			console.log('Video list: ', list.value);
			this.video_lists = await JSON.parse(list.value);
		} else {
      await Preferences.set({ key: VID_LIST, value: JSON.stringify([])});
      console.log('Video list 2: ', list.value);
      this.video_lists = await JSON.parse(list.value);
		}
  }

  private async saveNewVid(){
    await Preferences.set({ key: VID_LIST, value: JSON.stringify(this.video_lists)});
  }

  public async pickFile(): Promise<void> {
    const types = this.formGroup.get('types')?.value || [];
    const limit = this.formGroup.get('limit')?.value || false;
    const readData = this.formGroup.get('readData')?.value || false;
    const { files } = await FilePicker.pickFiles({ types, limit, readData });
    this.files = files;
  }

  private numberToTimeString(time: number) {
    const minutes = Math.floor(time / 60);
    const seconds = time % 60;
    return `${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds}`;
  }

  private bytesToSize(bytes: number) {
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
    if (bytes === 0) {
      return '0 Byte';
    }
    const i = Math.floor(Math.log(bytes) / Math.log(1024));
    return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
  }

  async initialise() {
    const info = await Device.getInfo();
    const config: VideoRecorderPreviewFrame = {
      id: 'video-record',
      stackPosition: 'front',
      width: 'fill',
      height: 'fill',
      x: 0,
      y: 0,
      borderRadius: 0,
    };
    if (this.initialized) {
      return;
    }
    await VideoRecorder.initialize({
      camera: VideoRecorderCamera.BACK,
      previewFrames: [config],
      quality: this.quality,
      videoBitrate: 4500000,
    });

    if (info.platform == 'android') {
      console.log("Platform: ", info.platform);
      // Only used by Android
      await VideoRecorder.showPreviewFrame({
        position: 0, // 0:= - Back, 1:= - Front
        quality: this.quality
      });
    }

    this.initialized = true;
  }

  async startRecording() {
    try {
      const info = await Device.getInfo();
      await this.initialise();
      await VideoRecorder.startRecording();
      this.isRecording = true;
      this.showVideos = false;

      // lock screen orientation when recording
      const orientation = await ScreenOrientation.orientation();

      if (info.platform == 'ios') {
        // On iOS the landscape-primary and landscape-secondary are flipped for some reason
        if (orientation.type === 'landscape-primary') {
          orientation.type = 'landscape-secondary'
        } else if (orientation.type === 'landscape-secondary') {
          orientation.type = 'landscape-primary'
        }
      }

      await ScreenOrientation.lock({ orientation: orientation.type });

      this.durationIntervalId = setInterval(() => {
        VideoRecorder.getDuration().then((res) => {
          this.duration = this.numberToTimeString(res.value);
        });
      }, 1000);
    } catch (error) {
      console.error(error);
    }
  }

  flipCamera() {
    VideoRecorder.flipCamera();
  }

  async toggleVideos() {
    this.showVideos = !this.showVideos;
    if (this.showVideos) {
      this.destroyCamera();
    } else {
      this.initialise();
    }
  }

  async stopRecording() {
    const res = await VideoRecorder.stopRecording();
    clearInterval(this.durationIntervalId);
    this.duration = "00:00";
    this.isRecording = false;

    // unlock screen orientation after recording
    await ScreenOrientation.unlock();

    
    // The video url is the local file path location of the video output.
    // eg: http://192.168.1.252:8100/_capacitor_file_/storage/emulated/0/Android/data/io.ionic.starter/files/VID_20240524110208.mp4

    const filePath = 'file://' + res.videoUrl.split('_capacitor_file_')[1];
    this.video_paths = filePath;
    this.video_lists.push(filePath);
    this.saveNewVid();

    const file = await Filesystem.stat({ path: filePath }).catch((err) => {
      console.error(err);
    });

    // file.ctime - file.mtime gives the duration in milliseconds
    // Convert it to human readable format
    let duration = '';
    if (file) {
      const durationInSeconds = Math.floor((file.mtime! / 1000) - (file.ctime! / 1000));
      duration = this.numberToTimeString(durationInSeconds);
    }

    this.videos.push({
      url: res.videoUrl,
      metadata: {
        size: file ? this.bytesToSize(file.size) : '',
        duration,
      }
    })
    console.log("Videos: ", this.videos[0]['url']);

    this.toggleVideos();
  }

  async videoQualityChanged(quality: VideoRecorderQuality) {
    this.quality = quality;
    await this.destroyCamera();
    this.showVideos = false;
    await this.initialise();
  }

  async destroyCamera() {
    await VideoRecorder.destroy();
    this.initialized = false;
  }

}

tab5.page.html

<ion-header [translucent]="true">
  <ion-toolbar color="primary">
    <h6 class="ion-text-center">&#64;capacitor-community/video-recorder!!</h6>
  </ion-toolbar>
</ion-header>

<ion-content id="video-record" [fullscreen]="true"
  [ngClass]="{ isRecording: isRecording || initialized }">
  <div *ngIf="showVideos && videos.length; else NoVideos" style="display: flex; flex-direction: column; justify-content: center; margin: 16px 0px;">
    <div *ngFor="let video of videos" style="display: flex; flex-direction: column; justify-content: center; margin: 16px 0px;">
      <video [height]="200" [src]="video.url" controls></video>
      <div *ngIf="video.metadata">
        <ion-list>
          <ion-item>
            <ion-label>Video Metadata</ion-label>
          </ion-item>
          <ion-item>
            <ion-label>Size</ion-label>
            <ion-label>{{ video.metadata.size }}</ion-label>
          </ion-item>
          <ion-item>
            <ion-label>Duration</ion-label>
            <ion-label>{{ video.metadata.duration }}</ion-label>
          </ion-item>
        </ion-list>
      </div>
    </div>
  </div>
  <ng-template #NoVideos>
    <div *ngIf="showVideos" style="display: flex; flex-direction: column; justify-content: center; align-items: center;">
      <ion-icon name="videocam-off-outline" style="font-size: 100px;"></ion-icon>
      <ion-label class="ion-text-center">No Videos</ion-label>
      <ion-label class="ion-text-center">Record some videos and come back later</ion-label>
    </div>
  </ng-template>
</ion-content>
<ion-footer style="background-color: white;">
  <div class="duration">
    <ion-label>{{ duration }}</ion-label>
  </div>
  <div class="footer">
    <ion-button *ngIf="!isRecording" (click)="startRecording()">
      <ion-icon  name="videocam"></ion-icon>
    </ion-button>
    <ion-button *ngIf="isRecording" (click)="stopRecording()">
      <ion-icon name="stop-circle-outline"></ion-icon>
    </ion-button>
    <ion-button *ngIf="!isRecording && !showVideos" (click)="flipCamera()">
      <ion-icon name="camera-reverse-outline"></ion-icon>
    </ion-button>
    <ion-button *ngIf="!isRecording" (click)="toggleVideos()">
      <ion-icon [name]="showVideos ? 'folder-open-outline' : 'folder-outline'"></ion-icon>
    </ion-button>
    <ion-button
      *ngIf="!isRecording"
      class="text-center text-sm w-1/2 rounded-2xl p-2 app-button"
      (click)="settingsPopover.present()">
      <ion-icon name="settings-outline"></ion-icon>
    </ion-button>
    <ion-popover #settingsPopover>
      <ng-template>
        <ion-list mode="md">
          <ion-item
            button
            *ngFor="let quality of videoQualityMap"
            (click)="settingsPopover.dismiss(); videoQualityChanged(quality.command)">
            <ion-label>{{ quality.label }}</ion-label>
          </ion-item>
        </ion-list>
      </ng-template>
    </ion-popover>
  </div>
</ion-footer>

tab5.page.scss

ion-content {
  --background: transparent;
}
  
  ion-footer {
    border-top-right-radius: 100%;
    border-top-left-radius: 100%;
    border-top: 1px solid black;
  }
  
  .footer {
    display: flex;
    gap: 8px;
    justify-content: center;
    margin-bottom: 8px;
  }
  
  .duration {
    display: flex;
    justify-content: center;
    align-items: center;
    padding-top: 16px;
    padding-bottom: 8px;
  }

tab6.page.ts

import { Component, OnInit, inject } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { PreviewPage } from '../preview/preview.page';
import { Directory, Filesystem } from '@capacitor/filesystem';
import { CameraModalComponent } from '../camera-modal/camera-modal.component';

@Component({
  selector: 'app-tab6',
  templateUrl: './tab6.page.html',
  styleUrls: ['./tab6.page.scss'],
})
export class Tab6Page implements OnInit {

  image = null;
  image_uri: string | null = null;

  constructor(private modal: ModalController) { }

  ngOnInit() {
  }

  async openCamera() {
    const modal = await this.modal.create({
        component: PreviewPage
    });

    modal.onDidDismiss().then((data) => {
      if (data !== null) {
        console.log("Data: ",data.data.img_uri);
        this.image_uri = data.data.img_uri;
        this.loadStoredImage();
      }
      else{
        console.log("none!");
      }
    });

    return await modal.present();
  }

}

tab6.page.html

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Camera Preview Demo
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
    <div *ngIf="image">
        <img [src]="image" alt="" srcset="">
    </div>
    <ion-button (click)="openCamera()" color="primary" expand="block" fill="solid" size="default">
       Open Camera
    </ion-button>
    
</ion-content>

tab6.page.scss

ion-content {
    --background: transparent;
  }

preview.page.ts

import { AfterViewChecked, AfterViewInit, Component, OnInit } from '@angular/core';

import { CameraPreview, CameraPreviewOptions, CameraPreviewPictureOptions } from '@capacitor-community/camera-preview';
import '@capacitor-community/camera-preview';
import { Capacitor } from '@capacitor/core';
import { Directory, Filesystem, WriteFileResult } from '@capacitor/filesystem';
import { ModalController } from '@ionic/angular';
import {
  VideoRecorder,
  VideoRecorderCamera,
  VideoRecorderPreviewFrame,
  VideoRecorderQuality,
} from '@capacitor-community/video-recorder';

@Component({
  selector: 'app-preview',
  templateUrl: './preview.page.html',
  styleUrls: ['./preview.page.scss'],
})
export class PreviewPage implements OnInit {
  image = null;
  image_uri = null;
  cameraActive = false;
  confirmScreen = false;
  constructor(private modalCtrl: ModalController) { }

  ngOnInit() {
    // this.launchCamera();
  }

  async ionViewWillEnter() {
    const { value } = await CameraPreview.isCameraStarted();
    console.log("Value: ", value);
    console.log('Preview page is about to enter');

    if(value){
      await CameraPreview.stop();
      this.launchCamera();
    }else{
      this.launchCamera();
    }
    
  }


  async stopVideoRecorder() {
    const videoElement = document.querySelector('video');
    if (videoElement) {
      videoElement.srcObject = null;
      videoElement.remove();
    }
  }

  async launchCamera() {
    // await CameraPreview.stop();
    const cameraPreviewOptions: CameraPreviewOptions = {
      position: 'rear', // front or rear
      parent: 'content', // the id on the ion-content
      className: '',
      width: window.screen.width, //width of the camera display
      height: window.screen.height - 200, //height of the camera
      toBack: false,
    };
    setTimeout(async () => {
      await CameraPreview.start(cameraPreviewOptions);
      this.cameraActive = true;
    }, 500);
    
    
  }

  async takePicture() {
    const cameraPreviewPictureOptions: CameraPreviewPictureOptions = {
      quality: 10
    };
    const result = await CameraPreview.capture(cameraPreviewPictureOptions);

    console.log("Result: ", result.value);
    
    const base64Image = `data:image/jpeg;base64,${result.value}`;

    

    this.image = `data:image/jpeg;base64,${result.value}`;
    
    
    // await this.stopCamera();
    await CameraPreview.stop();
    this.cameraActive = false;
  }

  async saveImage(base64Image: string): Promise<string> {
    const fileName = Date.now()+".jpeg";

    try {
      const savedFile = await Filesystem.writeFile({
        path: fileName,
        data: base64Image,
        directory: Directory.Data, // Guarda en almacenamiento interno accesible por la app
      });

      console.log("RUTA: ", savedFile.uri);

      return savedFile.uri;
    } catch (error) {
      console.error('Error saving image:', error);
      return base64Image; // Si falla, usa la imagen en base64
    }
  }

  async acceptPhoto() {
    const savedImage = await this.saveImage(this.image);
    this.image_uri = savedImage;

    if(this.image && this.image_uri){
      this.modalCtrl.dismiss({img: this.image, img_uri: this.image_uri});
    }else{
      this.modalCtrl.dismiss(this.image);
    }
  }

  async retakePhoto() {
    this.confirmScreen = false;
    this.image = null;
    this.image_uri = null;
    this.launchCamera(); // Reiniciar la cámara
  }

  async stopCamera() {
    await CameraPreview.stop();
    if(this.image && this.image_uri){
      this.modalCtrl.dismiss({img: this.image, img_uri: this.image_uri});
    }else{
      this.modalCtrl.dismiss(this.image);
    }
  }

  async flipCamera() {
    await CameraPreview.flip();
  }


}

preview.page.html

<ion-content id="d" [fullscreen]="true">
  <div *ngIf="cameraActive" class="preview">
    hia
      <ion-button (click)="stopCamera()" expand="block" id="close">
          <ion-icon slot="icon-only" name="close-circle"></ion-icon>
      </ion-button>

      <ion-button (click)="takePicture()" expand="block" id="capture">
          <ion-icon slot="icon-only" name="camera"></ion-icon>
      </ion-button>

      <ion-button (click)="flipCamera()" expand="block" id="flip">
          <ion-icon slot="icon-only" name="repeat"></ion-icon>
      </ion-button>
  </div>

  <div *ngIf="!cameraActive" class="confirm-container">
    <div *ngIf="image">
        <img [src]="image" alt="" srcset="">
    </div>
    <div class="button-container">
      <ion-button color="success" (click)="acceptPhoto()">Aceptar</ion-button>
      <ion-button color="danger" (click)="retakePhoto()">Reintentar</ion-button>
    </div>
  </div>
</ion-content>

preview.page.scss

ion-content {

  --background: rgba(185, 32, 32, 0);
  background: transparent !important;
}

#capture {
  position: absolute;
  bottom: 30px;
  left: calc(50% - 25px);
  width: 50px;
  height: 50px;
  z-index: 99999;
}

#flip {
  position: absolute;
  bottom: 30px;
  left: calc(50% + 125px);
  width: 50px;
  height: 50px;
  z-index: 99999;
}

#close {
  position: absolute;
  bottom: 30px;
  left: calc(50% - 175px);
  width: 50px;
  height: 50px;
  z-index: 99999;
}

#capture::part(native) {
  border-radius: 30px;
}

#close::part(native) {
  border-radius: 30px;
}

#flip::part(native) {
  border-radius: 30px;
}

There’s some spanish comments that can be safely ignored in the code, I didn’t write anything relevant.

I’ve also implemented the same as in ‘preview’ in ‘tab8’ without the modal code, and as I said before, works well even after using the ‘tab5’ code. Lastly, in the forms, 'tab5’s code is implemented similarly to preview, being called as a modal, and it works exactly the same.

Any help is welcomed, any extra information I will provide. Thanks in advance!

1 post - 1 participant

Read full topic

Recommended method for "Sign in with Google" + Firebase Auth on Capacitor?

$
0
0

Hi, I have initialized a new ionic / react / capacitor app with the latest versions. I have a simple auth page that needs a button to sign in with google. I am using firebase auth for my project, and it works with no issue when I run the app on my web browser.

Once I compile to iOS, this no longer works. It seems like I have a few potential options, but I am seeking the community recommended way to do this seemingly very simple app feature. Here are some options I have found that look promising:

If you have personally implemented this feature before with Firebase auth, would love to know how you went about it before I sink hours into trying to get a scuffed version set up. Thanks!

1 post - 1 participant

Read full topic

Ion-Segment Not Rendering Properly in MD Mode on iOS After Navigation

$
0
0

Description:

I’m facing an issue with <ion-segment> when forcing md mode (mode="md") on an iOS device. The problem occurs when navigating back to the page containing the segment—it doesn’t render properly. The segment sometimes appears broken, invisible, or doesn’t reflect the selected value correctly.

What I Tried:

I have already tried the following, but none of them worked:
:heavy_check_mark: Forcing change detection using ChangeDetectorRef.detectChanges()
:heavy_check_mark: Using setTimeout to delay segment initialization
:heavy_check_mark: Wrapping <ion-segment> in ngIf or toggling hidden to force a reflow
:heavy_check_mark: Forcing a repaint by toggling display: nonedisplay: block

Expected Behavior:

When I navigate back to the page, the <ion-segment> should render properly and reflect the correct selected value.

Issue Details:

  • Ionic Version: (Ionic 8)
  • Angular Version: (Angular 17)
  • Device: iPhone X / iOS 17 (any ios)
  • Mode: mode="md" (forced on iOS)

Question:

Has anyone faced this issue when using md mode on iOS? Is there an official workaround to make <ion-segment> render correctly after navigation?

Any help or insights would be greatly appreciated! :rocket:

1 post - 1 participant

Read full topic

White screen with iOS 16.2

$
0
0

Hello everyone,

We have a Capacitor+Angular app and we are having issues launching it on iPhone 8 devices running iOS 16.2. We have also tested it on iPhone 8 devices with iOS 16.4 and in these cases the application starts without problems.

The problem we are experiencing on iOS 16.2 is that the application remains on a blank screen and does not load any content, throwing errors in the XCode console that I attach below.

Log console with the application running on iOS 16.2:

And log console with the application running on iOS 16.4:

Both devices (XCode simulators) have the same version of the app installed. This application simply displays a splash screen after having completely emptied it to test this error and try to isolate it.

We use Capacitor 6.1.0 with Angular 17.

Thank you very much for your help.

All the best!

2 posts - 2 participants

Read full topic

I want to create a stepper component in reactjs

$
0
0

hello,
I’m trying to make a stepper component like in the image in ionic framework using reactjs and i’m trying to find if someone did it ?
thanks in advance,
2025-02-10_11-43

1 post - 1 participant

Read full topic

How to Disable Clipboard Content Restriction into Input field

$
0
0

When User is Trying to enter Content from Clipboard I need to Prevent it from entering into input or Disable Clipboard in Keyboard itself

1 post - 1 participant

Read full topic

Half dil ish mid DM mc stood still


Ionic build into wrong directory: www/browser?

$
0
0

I am reimplementing my Ionic+Angular+PWA app using updated versions of “everything”.

I’ve ported everything over to Ionic + Angular v.19 and worked out all the bugs.

When I do an ionic build --prod it seems to produce everything in www/browser (except for the files 3rdpartylicenses.txt and prerendered-routes.json).

When I do firebase deploy to host on firebase, it assumes everything is in www – at least that’s where my build put everything previously.

Here is my current config:

Ionic:

   Ionic CLI                     : 7.2.0 (/Users/vtn2/.nvm/versions/node/v20.9.0/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 8.4.1
   @angular-devkit/build-angular : 19.0.6
   @angular-devkit/schematics    : 19.0.6
   @angular/cli                  : 19.0.6
   @ionic/angular-toolkit        : 12.1.1

Capacitor:

   Capacitor CLI      : 6.2.0
   @capacitor/android : not installed
   @capacitor/core    : 6.2.0
   @capacitor/ios     : not installed

Utility:

   cordova-res : not installed globally
   native-run  : 2.0.1

System:

   NodeJS : v20.9.0 (/Users/vtn2/.nvm/versions/node/v20.9.0/bin/node)
   npm    : 10.1.0
   OS     : macOS Unknown

Here is my old config:

Ionic:

   Ionic CLI                     : 7.2.0 (/Users/vtn2/.nvm/versions/node/v20.9.0/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 6.5.3
   @angular-devkit/build-angular : 14.2.10
   @angular-devkit/schematics    : 14.2.10
   @angular/cli                  : 14.2.10
   @ionic/angular-toolkit        : 7.0.0

Capacitor:

   Capacitor CLI      : 4.6.3
   @capacitor/android : 4.6.3
   @capacitor/core    : 4.6.3
   @capacitor/ios     : 4.6.3

Utility:

   cordova-res                          : not installed globally
   native-run (update available: 2.0.1) : 1.7.1

System:

   NodeJS : v20.9.0 (/Users/vtn2/.nvm/versions/node/v20.9.0/bin/node)
   npm    : 10.1.0
   OS     : macOS Unknown

Here is my new angular.json:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "app": {
      "projectType": "application",
      "schematics": {
        "@ionic/angular-toolkit:page": {
          "styleext": "scss",
          "standalone": true
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:application",
          "options": {
            "outputPath": "www",
            "index": "src/index.html",
            "polyfills": ["src/polyfills.ts"],
            "tsConfig": "tsconfig.app.json",
            "inlineStyleLanguage": "scss",
            "assets": [
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "assets"
              }
            ],
            "styles": ["src/global.scss", "src/theme/variables.scss"],
            "scripts": [],
            "browser": "src/main.ts"
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "outputHashing": "all",
              "serviceWorker": "ngsw-config.json"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            },
            "ci": {
              "progress": false
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "buildTarget": "app:build:production"
            },
            "development": {
              "buildTarget": "app:build:development"
            },
            "ci": {
              "progress": false
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "buildTarget": "app:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "inlineStyleLanguage": "scss",
            "assets": [
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "assets"
              }
            ],
            "styles": ["src/global.scss", "src/theme/variables.scss"],
            "scripts": []
          },
          "configurations": {
            "ci": {
              "progress": false,
              "watch": false
            }
          }
        },
        "lint": {
          "builder": "@angular-eslint/builder:lint",
          "options": {
            "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
          }
        }
      }
    }
  },
  "cli": {
    "schematicCollections": ["@ionic/angular-toolkit"]
  },
  "schematics": {
    "@ionic/angular-toolkit:component": {
      "styleext": "scss"
    },
    "@ionic/angular-toolkit:page": {
      "styleext": "scss"
    },
    "@angular-eslint/schematics:application": {
      "setParserOptionsProject": true
    },
    "@angular-eslint/schematics:library": {
      "setParserOptionsProject": true
    }
  }
}

and my firebase.json:

{
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "ignore": [
        "node_modules",
        ".git",
        "firebase-debug.log",
        "firebase-debug.*.log",
        "*.local"
      ],
      "predeploy": [
        "npm --prefix \"$RESOURCE_DIR\" run lint",
        "npm --prefix \"$RESOURCE_DIR\" run build"
      ]
    }
  ],
  "hosting": {
    "public": "www",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

I’ve been looking at the old versions of these files and they seem basically the same as the new versions.

Does anyone have any idea why my stuff is being built into www/browsers, if that is correct, and if it is correct, how I get the firebase deploy to work correctly?

Thanks.

2 posts - 1 participant

Read full topic

Has anyone tried to upgrade an old ionic3 project to the latest version using Cursor, the AI IDE?

$
0
0

Has anyone tried to upgrade an old ionic3 project to the latest version using Cursor, the AI IDE?

1 post - 1 participant

Read full topic

How to change the font color of the label displayed as "Email"

$
0
0
      <ion-item>
        <ion-input label="Email" placeholder="email@domain.com">
          <ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
          <ion-button fill="clear" slot="end" aria-label="Show/hide">
            <ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
          </ion-button>
        </ion-input>
      </ion-item>

How to change the font color of the label displayed as “Email”

1 post - 1 participant

Read full topic

Filesystem.writeFile not working for me with capacitor 6. Please help me out

$
0
0

Here I am sharing my package.json and my code block.

"dependencies": {
    "@capacitor/android": "^7.0.1",
    "@capacitor/app": "6.0.2",
    "@capacitor/core": "^7.0.1",
    "@capacitor/filesystem": "^6.0.2",
    "@capacitor/haptics": "6.0.2",
    "@capacitor/keyboard": "6.0.3",
    "@capacitor/network": "^6.0.3",
    "@capacitor/status-bar": "6.0.2",
    "@emotion/react": "^11.14.0",
    "@emotion/styled": "^11.14.0",
    "@ionic/react": "^8.4.1",
    "@ionic/react-router": "^8.4.1",
    "@mui/icons-material": "^6.2.1",
    "@mui/material": "^6.2.1",
    "@mui/styles": "^6.2.1",
    "@mui/x-date-pickers": "^7.23.3",
    "@react-spring/web": "^9.7.5",
    "@types/react-router": "^5.1.20",
    "@types/react-router-dom": "^5.3.3",
    "dayjs": "^1.11.13",
    "ionicons": "^7.4.0",
    "moment": "^2.30.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-quill": "^2.0.0",
    "react-router": "^5.3.4",
    "react-router-dom": "^5.3.4"
  },
  "devDependencies": {
    "@capacitor/assets": "^3.0.5",
    "@capacitor/cli": "^6.2.0",
    "@testing-library/dom": ">=7.21.4",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^14.0.0",
    "@testing-library/user-event": "^14.4.3",
    "@types/react": "^18.0.27",
    "@types/react-dom": "^18.0.10",
    "@vitejs/plugin-legacy": "^5.0.0",
    "@vitejs/plugin-react": "^4.0.1",
    "cypress": "^13.5.0",
    "eslint": "^8.35.0",
    "eslint-plugin-react": "^7.32.2",
    "jsdom": "^22.1.0",
    "terser": "^5.4.0",
    "typescript": "^5.1.6",
    "vite": "~5.2.0",
    "vitest": "^0.34.6"
  }
const createOfflineResponseDataFile = async () => {
  try {
    // Check for write permission
    const permissionStatus = await Filesystem.checkPermissions();
    alert("Permission status:" + JSON.stringify(permissionStatus));
    if (permissionStatus.publicStorage !== "granted") {
      const requestStatus = await Filesystem.requestPermissions();
      if (requestStatus.publicStorage !== "granted") {
        throw new Error("Write permission not granted");
      }
    }

    const result = await Filesystem.writeFile({
      path: OFFLINE_RESPONSE_DATA_FILE,
      data: JSON.stringify({}), // Initial empty data
      directory: Directory.Documents,
      encoding: Encoding.UTF8,
    });

    if (result) {
      return {
        status: true,
        message: "File created successfully",
        result,
      };
    }
  } catch (e) {
    alert("Unable to create file" + (e as any).message);
    return {
      status: false,
      message: "Failed to create file",
      error: e,
    };
  }
};

Filesystem.checkPermissions() method returning publicStorage: “granted” but unable to write file. Testing on android 13. Device samsung.

2 posts - 2 participants

Read full topic

Load styles in Ionic dynamically from a file downloaded via http

$
0
0

First of all, sorry for my English…

I’m trying to develop an Ionic app with Capacitor using standalone Angular.

I would like it to have pre-established styles by default and for them to be declared in my variables file within the theme folder.

At the beginning of loading the app I check whether or not it is configured to redirect to the configuration page in the ‘/config’ path to collect some user values ​​and download its configuration file:

private async ionViewDidEnter(): Promise<void> {
    Promise.all([
        this._services._storage.getItem('configClient', true),
        this._services._storage.getItem('news', true),
        this._services._storage.getItem('chg_pin', true),
        this._services._storage.getItem('user', true),
        this._services._storage.getItem('remember', true),
    ])
    .then(async (responses) => {
        // Show configuration page or not if it is the first time you connect.
        if(responses[0] == null) {
            this._services._router.navigate(['/config']);
        }else {
            // Loading client settings and styles if you have ever connected
            await this._services._configuration.getStorageClientConfig();
      
        }  
    })
    .catch((e) => {
        console.error('ErrorInitLogin', e.message);
    })
    .finally(() => {
        this._services.splashscreen.hide();
    });
}

If you do not have previous configuration:

public async sendConfig(configForm): Promise<void> {
    if(this.configForm.invalid) return;
    await this._services._loading.showLoading(this._services._translate.instant('pages.configuration.sending_data'), 0);
    this._services._configuration.initClientConfig()
    .then(() => {
        this._services._loading.dismiss();
        this._services._menu.swipeGesture(true, 'menu-ctx');
        this._services._router.navigate(['login']); 
    })
    .catch(() => {
        this._services._loading.dismiss();
        this._services._toast.showToast(this._services._translate.instant('pages.configuration.error_sending_data'), 'bottom', 'danger');
    });
}

Then I call a configuration service that downloads a configuration file for that user from an ftp server.

public initClientConfig(): Promise<boolean> {
    return new Promise((resolve, reject) => {
        // Recover Settings from Server
        this.getConfig()
        .then((response) => {
            // Set Servers
            this.setAppServer();
            // Initialize Modules
            this.setAppModules();
            resolve(true);
        });
    });
}

The service downloads the file as follows:

public getConfig(): Promise<ConfiguracionCliente> {
    return new Promise((resolve) => {
        this._http.send(
            this.currentClient.url, 
            'GET', 
            {}, 
            [
                {
                    "x-auth-header": "**********************************"
                }
            ]
        )
        .then(async (response: ConfiguracionCliente) => {
            this.clientConfig = response;
            if(this.clientConfig && !GenericMethods.isNullOrEmpty(this.clientConfig.build)) {
               this._storage.setItem('configClient', this.clientConfig, true);
               this._theme.initClientTheme(this.clientConfig);
                if(this.clientConfig.servers?.length > 0) {
                    this._server.servers = this.clientConfig.servers;
                }
                if(this.clientConfig.modulos?.length > 0) {
                    this._modules.arrayModules = this.clientConfig.modulos;
                }
                if(!GenericMethods.isNullOrEmpty(this._app.appInfo.version) && (this._app.appInfo.version < this.clientConfig.version)) {
                    this.showPermissions = true;
                    const version = await this._modal.create({
                        component: ModalVersion,
                        id: 'version',
                        initialBreakpoint: 1,
                        breakpoints: [0, 1],
                        backdropDismiss: false
                    });
                    await version.present();
                }
            }
            resolve(this.clientConfig);
        })
        .catch((e) => {
            console.error('ErrorGetClientConfig: ', e.message);
            this.getStorageClientConfig()
            .then((response) => {
                if(response) {
                    resolve(this.clientConfig);
                }else {
                    resolve(null);
                }
            })
        });
    })
}

From that file I get the name of the client, its logo, servers, modules and a property with the styles as follows

{
"version": "3.0.0",
"build": 81,
"usuario": "NombreUsuario",
"logo": "https://url_que_apunta_al_logo",
"servers": [
    {...},
    {...}
],
"modules": [
    {...},
    {...}
],
"theme": {
    "primary": "#FF6600 !important",
    "primary-rgb": "255,102,0 !important",
    "primary-contrast": "#ffffff !important",
    "primary-contrast-rgb": "0,0,0 !important",
    "primary-shade": "#e05a00 !important",
    "primary-tint": "#ff751a !important",

    "secondary": "#FF8D00 !important",
    "secondary-rgb": "255,141,0 !important",
    "secondary-contrast": "#ffffff !important",
    "secondary-contrast-rgb": "0,0,0 !important",
    "secondary-shade": "#e07c00 !important",
    "secondary-tint": "#ff981a !important",

    "tertiary": "#FFC299 !important",
    "tertiary-rgb": "255,194,153 !important",
    "tertiary-contrast": "#ffffff !important",
    "tertiary-contrast-rgb": "0,0,0 !important",
    "tertiary-shade": "#e0ab87 !important",
    "tertiary-tint": "#ffc8a3 !important",

    "success": "#439467 !important",
    "success-rgb": "67,148,103 !important",
    "success-contrast": "#ffffff !important",
    "success-contrast-rgb": "255,255,255 !important",
    "success-shade": "#3b825b !important",
    "success-tint": "#569f76 !important",

    "warning": "#FF8D00 !important",
    "warning-rgb": "255,141,0 !important",
    "warning-contrast": "#000000 !important",
    "warning-contrast-rgb": "0,0,0 !important",
    "warning-shade": "#e07c00 !important",
    "warning-tint": "#ff981a !important",

    "danger": "#CE352C !important",
    "danger-rgb": "206,53,44 !important",
    "danger-contrast": "#ffffff !important",
    "danger-contrast-rgb": "255,255,255 !important",
    "danger-shade": "#b52f27 !important",
    "danger-tint": "#d34941 !important",

    "dark": "#222222 !important",
    "dark-rgb": "34,34,34 !important",
    "dark-contrast": "#ffffff !important",
    "dark-contrast-rgb": "255,255,255 !important",
    "dark-shade": "#1e1e1e !important",
    "dark-tint": "#383838 !important",

    "medium": "#999999 !important",
    "medium-rgb": "153,153,153 !important",
    "medium-contrast": "#000000 !important",
    "medium-contrast-rgb": "0,0,0 !important",
    "medium-shade": "#878787 !important",
    "medium-tint": "#a3a3a3 !important",

    "light": "#f7f7f7 !important",
    "light-rgb": "247,247,247 !important",
    "light-contrast": "#000000 !important",
    "light-contrast-rgb": "0,0,0 !important",
    "light-shade": "#d9d9d9 !important",
    "light-tint": "#f8f8f8 !important",

    "header": "#e05a00 !important",
    "header-rgb": "224,90,0 !important",
    "header-contrast": "#ffffff !important",
    "header-contrast-rgb": "255,255,255 !important",
    "header-shade": "#c54f00 !important",
    "header-tint": "#e36b1a !important"
    }
}

The configuration service, once the file is downloaded, calls the theme service to start the theme configuration with the values ​​brought from the downloaded file as follows:

public initClientTheme(clientConfig: ConfiguracionCliente): Promise<boolean> {
    return new Promise((resolve) => {
        if(clientConfig && clientConfig.theme) {
            this.nameEmpresa = clientConfig.empresa;
            this.logo = clientConfig.logo;
            this.temaCliente = clientConfig.theme;
            this.ngZone.run(() => {
                Object.keys(this.temaCliente).forEach((key) => {
                    this.render.setStyle(document.documentElement, `--ion-color-${key}`, this.temaCliente[key]);
                });
            });
            resolve(true);
        }else {
            console.error('ErrorInitClientTheme');
            resolve(false);
        }
        console.log(`--ion-color-primary: ${JSON.stringify(document.documentElement.style)}`);
    });
}

The fact is that in that last ‘console.log(–ion-color-primary: ${JSON.stringify(document.documentElement.style)})’ it prints the values ​​of the :root with the variables and values ​​brought from the configuration file, but it is not rendered with the new colors, but with the previous ones…

Any ideas on how to dynamically change the styles of the application based on the downloaded file and how to make it persistent so that those styles are loaded whenever the app is reopened once configured to start?

Thanks guys!!!

I would like to know if any of you can think of how I can download that configuration file and with it configure my application based on the logged in user, placing their styles and making them persistent whenever the application is reopened.

Someone know any guide? Thanks in advance!!

1 post - 1 participant

Read full topic

Viewing all 70911 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>