HabitRPG / habitica-ios

Native iOS app for Habitica
GNU General Public License v3.0
686 stars 229 forks source link

Feature/new ionic portal from scratch #1337

Closed emmanuelOpenForge closed 3 months ago

emmanuelOpenForge commented 3 months ago

Steps to Create a New Ionic Portal

Initialize an Angular Project

To begin, we will initialize an Angular project at the root of your project directory using Angular CLI.
Note: Ensure you have Angular CLI installed with the command:

npm install -g @angular/cli

Now, create an Angular application with the command:

ng new HabiticaAboutOFPortal

Install Ionic Portals

Once the project is created, proceed to install Ionic Portals in the Angular project to facilitate communication between the web app and the native application. Use the command:

npm install @ionic/portals

Additionally, add Ionic to design your loading page:

ng add @ionic/angular

Modify the Main View

In the main view of the web app (app.component.html), add elements to show the information about Open Forge:

<ion-content>
  <div class="container">
    <ion-title>About Open Forge</ion-title>
    <ion-card>
      <ion-card-header>
        <ion-card-title>Our commitment</ion-card-title>
      </ion-card-header>
      <ion-card-content>
        <p>
          We believe in the power of mobile transformation. Partner with us to elevate your strategy and lead the digital frontier with unparalleled business growth and innovation.
        </p>
      </ion-card-content>
      <ion-list>
        <ion-item>
          <div class="centered">
            <a href="https://openforge.io">Site</a>
          </div>
        </ion-item>
        <ion-item>
          <div class="centered">
            <a href="https://openforge.io/about-us/">About Us</a>
          </div>
        </ion-item>
        <ion-item lines="none">
          <ion-button size="md" (click)="goBack()">OK</ion-button>
        </ion-item>
      </ion-list>
    </ion-card>
  </div>
</ion-content>

Add Event for the Button

In app.component.ts, add an event for the button to return to the previous scene from the About view:

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import {
  IonButton,
  IonCard,
  IonCardContent,
  IonCardHeader,
  IonCardTitle,
  IonContent, IonItem,
  IonList,
  IonTitle
} from "@ionic/angular/standalone";
import { publish } from "@ionic/portals";

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, IonContent, IonCardHeader, IonTitle, IonCard, IonCardTitle, IonCardContent, IonList, IonItem, IonButton],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {
  title = 'HabiticaAboutOFPortal';

  goBack() {
    publish({ topic: "navigate", data: "back" });
  }
}

We have imported the publish method from Ionic Portals, which will be used to communicate with the native application through PubSub communication. Here, our web app will be the publisher, notifying that we want to navigate back by communicating via the channel/topic navigate with data back, indicating we want to go back to the previous scene. Our native application will be the subscriber, and upon receiving this message, it will terminate the Ionic Portal and navigate to the previous scene in the menu list.

Build the Application

Finally, build the application with the command:

npm run build

Then, copy the generated code from the dist folder to the main folder of your native app (in this case, HabiticaRPG), and rename the folder to HabiticaAboutOFPortal as this name will be used to initialize our Ionic portal later.

Create a New Scene in iOS

The first step to create our Ionic portal from the native application side is to create a new UIView scene which will use the AboutOFViewController controller with the following code:

//
//  AboutViewController.swift
//  Habitica
//
//  Created by Phillip Thelen on 09.10.18.
//  Copyright © 2018 HabitRPG Inc. All rights reserved.
//

import UIKit
import Realm
import Habitica_Models
import MessageUI
import IonicPortals
import Combine

class AboutOFViewController: UIViewController {

    private var dismissCancellable: AnyCancellable?

    override func loadView() {
        super.loadView()
        self.view = PortalUIView(portal: "HabiticaAboutOFPortal")
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        dismissCancellable = PortalsPubSub.shared.publisher(for: "navigate")
                   .data(as: String.self)
                   .filter { $0 == "back" }
                   .receive(on: DispatchQueue.main)
                   .sink { [weak self] _ in
                       guard let self = self else { return }
                       self.dismiss(animated: true, completion: nil)
                       if let navigationController = self.navigationController {
                           navigationController.popViewController(animated: true)
                       }
                   }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.navigationBar.backgroundColor = .clear
    }
}

In the code above, HabiticaAboutOFPortal is the name of the folder containing your web app generated with npm run build.

This allows us to initialize our Ionic portal by overriding the loadView() method:

override func loadView() {
    super.loadView()
    self.view = PortalUIView(portal: "LoadingScreenPortal")
}

Subscribe to the Event

The code that listens for the event from our web app in the viewDidLoad lifecycle hook is:

override func viewDidLoad() {
    super.viewDidLoad()
    dismissCancellable = PortalsPubSub.shared.publisher(for: "navigate")
               .data(as: String.self)
               .filter { $0 == "back" }
               .receive(on: DispatchQueue.main)
               .sink { [weak self] _ in
                   guard let self = self else { return }
                   self.dismiss(animated: true, completion: nil)
                   if let navigationController = self.navigationController {
                       navigationController.popViewController(animated: true)
                   }
               }
}

Where dismissCancellable is of type AnyCancellable?. This code subscribes to the navigate topic (the channel), receives data as a String on the main thread, and navigates to the previous scene in our storyboard.

Modify Main Menu for Navigation

Also, define in MainMenuViewController to enable navigation in the menu by adding the following code at line 109:

MenuItem(key: .aboutOF, title: L10n.Titles.aboutOf, vcInstantiator: StoryboardScene.Main.aboutOFViewController.instantiate),

In our Strings.swift file, add the label for the menu button:

public static var aboutOf: String { return L10n.tr("Mainstrings", "titles.about_of") }

Finally, in the StoryboardScene file:

internal static let aboutOFViewController = SceneType<Habitica.AboutOFViewController>(storyboard: Main.self, identifier: "AboutOFViewController")

This guide demonstrates how to use micro frontends in your native application and communicate between the web app and the native app. I hope this information was useful and enjoyable. Please share this publication, as we frequently share similar material to keep everyone updated. Thank you for your attention, and see you in the next guide.

phillipthelen commented 3 months ago

I'm sorry, but I don't quite understand what is happening here. It's adding ionic for a loading and about screen replacement? I'm not really sure this is a direction we want to move towards honestly.

tjb commented 3 months ago

was there ever any intention of integrating with Ionic? 🤔

saraolson commented 3 months ago

this is not something we want to implement. when submitting pull requests, please work from issues marked 'Help Wanted'!