liveview-native / liveview-client-swiftui

MIT License
353 stars 31 forks source link

Platform / device specific template conventions #21

Closed bcardarella closed 1 year ago

bcardarella commented 1 year ago

We should probably start to develop what the naming convention should be for template extensions

I'd like to propose the following:

[name].swiftui.heex

All swift UI devices connecting could render the template of this name. We can offer more detail by targeting the platform to:

[name].ios.swiftui.heex [name].tvos.swiftui.heex [name].macos.swiftui.heex [name].watchos.swiftui.heex

XCode also supports multiplatform so maybe [name].swiftui.heex is just an alias for [name].multi.swiftui.heex

Similarly, on other devices we can follow this convention so for Android:

[name].jetpack.heex

We should package this up into a support lib for Phoenix so we can do away with:

  EEx.function_from_file(
    :def,
    :render,
    "lib/lvn_tutorial_web/live/cats_list_live.ios.heex",
    [:assigns],
    engine: Phoenix.LiveView.HTMLEngine
  )
bcardarella commented 1 year ago

So in other words, this is could be the breakdown:

[templatename].[platform].[UIframework].heex

bcardarella commented 1 year ago

@immranderson does Jetpack Compose have similar target platforms as SwiftUI?

Screen Shot 2022-08-13 at 9 13 20 AM
immranderson commented 1 year ago

@bcardarella I believe that Android might be a bit different here?

I know generally that the dependencies to enable support + utilize the components will need to be added to the build.gradle dependency file, but compose for wearos + androidtv is a bit of a newer thing. I'll check out how it's configured and get back to you with more specifics 👍

supernintendo commented 1 year ago

@bcardarella I have some abstractions I've been playing around with for narwin_chat.

  1. on_mount hook that provides a :platform assign to all LiveViews that inherit it via live_session.

  2. A helper module for generating platform-specific render functions. You just include it in your LiveView and it provides a render_native function which uses that :platform assign to determine which render function to call:

defmodule NarwinChatWeb.LoginLive do
  use NarwinChatWeb, :live_view
  use NarwinChatWeb.LiveViewNativeHelpers, template: "login_live"

  # ...

  @impl true
  def render(assigns) do
    # This will render one of the following templates depending on assigns.platform:
    #
    # :ios => login_live.ios.heex
    # :android => login_live.android.heex
    # :web => login_live.html.heex
    #
    render_native(assigns)
  end
end

It's pretty basic right now but could be extracted into a standalone library, extended, etc.

bcardarella commented 1 year ago

@supernintendo what is missing for me is how deep we should go. Does it make sense to differentiate between platform variants? Like do we need to care which ipad is rendering because of the screen size?

supernintendo commented 1 year ago

Hmm, that's a good question. I think rendering different templates across platform variants could get difficult to maintain for the end user / developer. It also seems impractical for Android.

One thing we might consider is passing device-specific information to LiveView via something like UIScreen which would then be included as assigns - that way, developers could target specific device sizes and configurations within platform templates without having to maintain separate templates for those variants.

bcardarella commented 1 year ago

I think rendering different templates across platform variants could get difficult to maintain for the end user / developer

This was my sense as well, I just don't know what is idiomatic on native for different screen sizes. Does SwiftUI adjust or must you render screen specific UI?

immranderson commented 1 year ago

@bcardarella

So for Android, it gets a bit tricky:

bcardarella commented 1 year ago

@immranderson I'm OK with this

shadowfacts commented 1 year ago

Does it make sense to differentiate between platform variants? Like do we need to care which ipad is rendering because of the screen size?

I don't think there need to be separate templates for different platform variants within an OS (e.g., iPhone vs iPad). Information about the current variant (device class, screen size) could be provided to the backend and then attached as socket assigns if the particular app needs it, letting the template make decisions about how it wants to render. That essentially mirrors how you'd go about handling different screen sizes in a purely native app.

Does SwiftUI adjust or must you render screen specific UI?

Some views do, but a good native app will make further changes to its layout (e.g., having a set of buttons be arranged horizontally or vertically) based on the device it's running on beyond just what SwiftUI will do.

ntodd commented 1 year ago

Information about the current variant (device class, screen size) could be provided to the backend and then attached as socket assigns if the particular app needs it, letting the template make decisions about how it wants to render. That essentially mirrors how you'd go about handling different screen sizes in a purely native app.

I can't speak to SwiftUI specifically, but this similar to how you would handle it in React Native. There is a Platform module that you can use to make small platform-specific conditionals. For templates, platform-specific extensions are used, but are only scoped to the OS ('.ios', '.android') level.

Also, it is fairly common to use something like react-native-device-info to get extra device information like isTablet(), hasNotch(), and isLandscape(), and use that to inform layout decisions. If device data like this could be optionally assigned in an on_mount hook, that would probably be enough to make all the layout tweaks you need.

Something to also consider is that the device can change over time. Orientation may change from landscape to portrait, an iPad app may enter splitview, iOS apps can run on Macs, iPads can have external screens, etc. Device state changes will need to be passed back to the LiveView process so templates can be re-rendered.

johankool commented 1 year ago

To style a SwiftUI view for a particular screen size you'd use @Environment(\.verticalSizeClass) var verticalSizeClass and the use the verticalSizeClass in the body of the view to make decisions. I suppose you would need to have a way to shuttle those to the live view template somehow.

struct ContentView: View {
    @Environment(\.verticalSizeClass) var verticalSizeClass

    @State var coordinator: LiveViewCoordinator<EmptyRegistry> = {
         var config = LiveViewConfiguration()
         config.navigationMode = .enabled
         return LiveViewCoordinator(URL(string: "http://localhost:4000/cats")!, config: config)
    }()

    var body: some View {
        LiveView(coordinator: coordinator, verticalSizeClass: verticalSizeClass)
    }
}

There are a lot of EnvironmentValues that exist though, and you would only want to have to do this for the once you need. So alternatively, perhaps the config of the coordinator would have a setting to enable those needed?

AZholtkevych commented 1 year ago

@supernintendo could you please advise here? i think it is done

AZholtkevych commented 1 year ago

@supernintendo could you please advise here? i think it is done

supernintendo commented 1 year ago

@AZholtkevych Sorry, just now getting to this. The original issue has been resolved. Platform libraries have been expected to define a platform_id for a few versions now (for this repo it's :swiftui). This is used to namespace HEEx templates both externally (i.e. home.swiftui.heex) and inline when rendering with the non-platform-specific ~Z (~LVN after Elixir 1.15) sigil:

# match on `platform_id` (automatically assigned via `use LiveViewNative.LiveView`)
def render(%{platform_id: :swiftui} = assigns) do
  ~LVN"""
    <VStack>
      <Text>Hello world!</Text>
    </VStack>
  """swiftui # render sigil is modified with `platform_id`
end

We've also since added various device-specific fields as part of the connection handshake (https://github.com/liveview-native/liveview-client-swiftui/pull/900). Those fields along with their possible values for the SwiftUI platform are as follows:

:os_name - "iOS", "macOS", "tvOS", "watchOS" :os_version - any OS version represented with semantic versioning (i.e. "16.4.1") :user_interface_idiom - "watch", "mac", "phone", "pad", "tv", or "unspecified"

@bcardarella Let me know if you feel like anything has been left unaddressed but my sense is that this issue has long been resolved and we can go ahead and close it.