---
title: "shinyMobile Tools"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{shinyMobile Tools}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---
  
```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```


```{r setup, echo=FALSE, message=FALSE}
library(bslib)
```

# Tools for mobile development

`{shinyMobile}` provides a set of tools specifically designed to help you develop user-friendly mobile applications and leverage certain actions on mobile phones. The tools we discuss here are:

- `preview_mobile()`: a function to preview your app in a large range of mobile devices
- `pullToRefresh`: a feature to refresh the page content by pulling from top to bottom
- Predefined input values to get information about the device and Shiny inputs

## Mobile preview

Since V0.2.0, `{shinyMobile}` has a function to preview your app in a large range of 
mobile devices: iPhone X, iPhone 8+, iPhone8, iPhone 5s, iPhone 4s, iPad, Samsung galaxy S5, Samsung galaxy Note 8, ... either locally on online:

```{r, eval = FALSE}
library(shiny)
library(shinyMobile)

preview_mobile(appPath = system.file("examples/gallery/app.R", package = "shinyMobile"), device = "iphoneX")
# This also works with a remote app url hosted on shinyapps.io ...
```

The local preview is a 4 steps process:

- run `preview_mobile()` with `appPath`
- Copy the returned code `R -e "shiny::runApp('appPath', port = 3838)"` in a terminal to launch the app
- Press enter to run the wrapper app
- Have fun!

`preview_mobile()` has other options such as color and landscape (to preview in landscape mode).

## Pull to Refresh

`{shinyMobile}` introduces the pull to refresh feature. It may be used to refresh the page content by pulling from top to bottom. This feature is disabled by default but passing `pullToRefresh = TRUE` in `f7Page()` options will activate it. On the server side, an input, namely `input$ptr` is `TRUE` when ptr is refreshed and becomes `NULL` at the end of the animation (you may run the app below in full screen mode and hold a left click with your mouse from top to bottom):

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    "NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAdzgCMAnRRAVwhiLdIAoAdMAPQsAzgwEAbAJZ0BWCdIZQGATwHCAFpIjKAskTqTxcfgEpeEWo2ZbhpKOPF8IAAmf8NW3fsPG851wxwqETCzgC8zgR8YOqkpKjCiAICDFrkDABmUARwGADmkqTqLHQYkkQpaXCZ2XAAtNRQwjB1BIoQAqbmZhDmUoxKytwe2j39iirDmtp6BkY95iPKAILoTq4skuHOGQDsAApQeXDrrs6khUbb-DrKzmio-Lj+Z0SoFyShEVK23KgsDgAKkQsHAMoENNtAVgAKoAUVwzgAJkoANbbABiywAMgBlOEmZ4uM57XFaPJGbFQZScHgvM7OaAANzoSm2ewAclAWUpTgzXBdSFcIvx9gDxOciM5QeC4BonvT+eooJIGFIIHBMTj8YrXITdZKiOJWQx2btgUaTXz+cFhIVyi4RWA6EQ4kQYArifydrtsVpUdxxFA6HAJU6-RB0QBGJ7OdSBDLXGJxBJJATUDP5IhECm5YgesD6r38vYRgNBkNhtxgMvOABMsfjYKTsXiiWSGeoWZzRgw+a6xb1RO9AGJnDAVS5iGQKKQDaXJL8Da4bHBSFDYQjl85bAwSHkN-Dh97kZImZIkdUvs5oUftxxL0n4EjJFBPSfnEH0OIhlHEABmREMjYAgPggbgACsTGcEBt1cBdbAASXIGBrQ-QVhU-Nd0mEYAIIAXWPD8AkkPJYiTLBSPI8gAA85z8QcT2fV8zUQ6donsJQYAAfQyQxxAHYjnB6D8AF8RP5CS9ReItXGEaomWqdkQLA7gtH+UhEVpDTEXk4Q7RIaDYOLfR5IYRS-lSMg1IgDSABJ3gYEwpLcEy6DMxS4UU6z1JYUgHNIBhEWM71HNxOxSBEZwAB46mcSRExs+zHOg-gSH4eddgAEVfcQczQs5aPXCJUCachuAAckc+LhAqxEwoikRZJPUhlFQTUnXsap6INFzxJeUTujAUT8KAA", #nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```

<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#ptr" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="ptr">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    title = "My app",
    options = list(pullToRefresh = TRUE, dark = FALSE),
    f7SingleLayout(
      navbar = f7Navbar(
        title = "Pull to Refresh",
        hairline = FALSE
      ),
      toolbar = f7Toolbar(
        position = "bottom",
        f7Link(label = "Link 1", href = "https://www.google.com"),
        f7Link(label = "Link 2", href = "https://www.google.com")
      ),
      # main content
      f7List(
        lapply(1:3, function(j) {
          f7ListItem(
            letters[j],
            media = f7Icon("alarm_fill"),
            right = "Right Text",
            header = "Header",
            footer = "Footer"
          )
        })
      )
    )
  ),
  server = function(input, output, session) {
    observe(print(input$ptr))
    
    observeEvent(input$ptr, {
      ptrStatus <- if (input$ptr) "on"
      f7Dialog(
        text = paste('ptr is', ptrStatus),
        type = "alert"
      )
    })
  }
)
```
</div>

## Predefined input values

`{shinyMobile}` contains functionality to retrieve information about the device displaying your app, the last input that was used and other information about the running Shiny session. This information helps you to set the layout as best as possible. 

### Information about your current device

`{shinyMobile}` has a predefined input, namely **input$deviceInfo**.

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    "NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAdzgCMAnRRAVwhiLdIAoAdMAPQsAzgwEAbAJZ0BWCdIZQGATwHCAFpIjKAskTqTxcfgEpeEWo2ZbhpKOPF8IAAmf8NW3fsPG851wxwqETCzgC8zgR8YOqkpKjCiAICDFrkDABmUARwGADmkqTqLHQYkkQpaXCZ2XAAtNRQwjB1BIoQAqbmZhDmUoxKytwe2j39iirDmtp6BkY95iPKAILoTq4skuHOGQDsAApQeXDrrs5EqKTlEKERUrbcACZKANbbAGLLADIAygCiJlw-jOV1IRm2-B0ymcaFQ-CBLjOex+WjyRi+UGUnB4wLOzmgADc6EptnsAHJQIlKU541yg8ERfjLAg5YShR5wAmSHLOLQZIjw3G09RQSQMKQQOAfb7-BG0s4aKCPIjUbYAFSwAFU-kLXIDdc4AMTOGCilzEMgUUgGgnVYlXGBquAAD1IAHkWPFPdE+QKwD08QHnPrEcJqraGKS2AQriRuFpUJ7cOdPYnSCZnCAhdi0wASX3OAA8dWcgQgHIY+1SZG4WcReITntzHK5OQAkhB+UKAL5B7vdMDdgC6QA", #nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```

<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#deviceInfo" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="deviceInfo">
```{r, eval = FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    options = list(dark = FALSE),
    title = "My app",
    f7SingleLayout(
      navbar = f7Navbar(
        title = "Access device info",
        hairline = FALSE
      ),
      # main content
      verbatimTextOutput("info")
    )
  ),
  server = function(input, output) {
    output$info <- renderPrint({
      input$deviceInfo
    })
  }
)
```
</div>

All this information is related to the device running the application. The following information is returned:

- `input$deviceInfo$ios`: TRUE for iOS device
- `input$deviceInfo$android`: TRUE for Android device
- `input$deviceInfo$androidChrome`: TRUE for Android Chrome
- `input$deviceInfo$desktop`: TRUE for desktop browser
- `input$deviceInfo$iphone`: TRUE for iPhone
- `input$deviceInfo$ipod`: TRUE for iPod
- `input$deviceInfo$ipad`: TRUE for iPad
- `input$deviceInfo$cordova`: TRUE when app running in cordova environment
- `input$deviceInfo$capacitor`: TRUE when app running in capacitor environment
- `input$deviceInfo$windows`: TRUE for desktop windows
- `input$deviceInfo$macos`: TRUE for desktop macOS
- `input$deviceInfo$ie`: TRUE for Internet Explorer browser
- `input$deviceInfo$edge`: TRUE for MS Edge browser
- `input$deviceInfo$firefox`: TRUE for FireFox browser
- `input$deviceInfo$electron`: TRUE when app is running under Electron environment
- `input$deviceInfo$nwjs`: TRUE when app is running under NW.js environment
- `input$deviceInfo$webView`: TRUE if app runs in web view - webapp installed to home screen, valid for desktop PWAs installed to desktop
- `input$deviceInfo$standalone`: Same as webView
- `input$deviceInfo$os`: Contains OS can be ios, android, macos, windows
- `input$deviceInfo$osVersion`: Contains OS version, e.g. 11.2.0
- `input$deviceInfo$pixelRatio`: Device pixel ratio

Below an example that displays a card only when the app is on desktop:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    "NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAdzgCMAnRRAVwhiLdIAoAdMAPQsAzgwEAbAJZ0BWCdIZQGATwHCAFpIjKAskTqTxcfgEpeEWo2ZbhpKOPF8IAAmf8NW3fsPG851wxwqETCzgC8zgR8YOqkpKjCiAICDFrkDABmUARwGADmkqTqLHQYkkQpaXCZ2XAAtNRQwjB1BIoQAqbmZhDmUoxKytwe2j39iirDmtp6BkY95iPKAILoTq4skuHOGQDsAApQeXDrrs5EqKTlEKERUrbcACZKANbbAGLLADIAygCiJlw-jOV1IRm2-B0ymcaFQ-CBLjOex+WjyRi+UGUnB4wLOzmgADc6EptnsAHJQIlKU541yg8ERfjLAg5YShR5wAmSHLOLQZIjw3G09RQSQMKQQOAfb7-IWuQFy5wAYmcMFFLmIZAopEVmwA8ix4obogQlI9TAjaTtdgAhcREAgvbgACQAKjovsNUKkyBlogBJdlwYQvUgXRDOACkwnhznIAA9SAajTiwCJqstjmRY1oJVKIq6sABVAEmMuWs49eUV4TVAnVUlsAhXEjcLSoQ24c6GjukEzOEBCoXY3sAEnTDEz2ucAB46s5AhAOQxXXBE22IGOOVycv6IPzRxyQ2HUFWzkKVVnqlByDDImbzhBxND+QxnEfQxdhz3DaPTQxHlnedF2XIt-W4QdETxQIAEcNy3TluTgPcDw-E8zzxPYAGEzRpWl+FdTRQkkUIoGcYRJBgVBwX-QDqEKdRnGo9U4zXUgKytOhDXvADQlNFx7GEIhIhIOwtDjdQ4DFc5qBcSSoGXDjaX5Ih0i7e5SGcLk4GoLtKKOOAuyIN8oG0c4igbOAjHgMgMH4RUMOcABfM8nO6MAnIAXSAA", #nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```

<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#deviceInfoEx" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="deviceInfoEx">
```{r, eval = FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    options = list(dark = FALSE),
    title = "My app",
    f7SingleLayout(
      navbar = f7Navbar(
        title = "Access device info",
        hairline = FALSE
      ),
      # main content
      uiOutput("card"),
      textOutput("userAgent"),
    )
  ),
  server = function(input, output) {
    
    output$userAgent <- renderText(input$deviceInfo$desktop)
    
    # generate a card only for desktop
    output$card <- renderUI({
      req(input$deviceInfo$desktop)
      f7Card(
        "This is a simple card with plain text,
        but cards can also contain their own header,
        footer, list view, image, or any other element."
      )
    })
  }
)
```
</div>


### Information about Shiny inputs

`{shinyMobile}` has `input$lastInputChanged` which returns the name, value and type of the last changed input:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    "NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAdzgCMAnRRAVwhiLdIAoAdMAPQsAzgwEAbAJZ0BWCdIZQGATwHCAFpIjKAskTqTxcfgEpeEWo2ZbhpKOPF8IAAmf8NW3fsPG851wxwqETCzgC8zgR8YOqkpKjCiAICDFrkDABmUARwGADmkqTqLHQYkkQpaXCZ2XAAtNRQwjB1BIoQAqbmZhDmUoxKytwe2j39iirDmtp6BkY95iPKAILoTq4skuHOGQDsAApQeXDrrs5EqKTlEKERUrbcACZKANbbAGLLADIAygCiJlw-jOV1IRm2-B0ymcaFQ-CBLjOex+WjyRi+UGUnB4wLOzmgADc6EptnsAHJQIlKU541yg8ERfgASQgqBYpGc7MMwnhuNp6igkgYUggcA+33+fNcgKlzlIRCI4mJDFJuwAKgqldTZa5gsJCtcIWA6EQ4kQYLzEbSdrsvloXtxxFA6HBxEa7RA3gBGeHOdSBDJG2LxRLJajh-IKtG5YgWsAyq20vYeh1Ol1uxlgFPOABMvv9cEDmeDCSSAnD1EjRGjGFjXUT0oR1oAxM4YIKXMQyBRSLK9gBhJSPGm0+lizNaNmkAAkSxZGSIlutrgJ1WJVxgargAA9SAB5dlT6JaBc8+MJ63JyQPHU2re77iT9lMx5G8i731p11G++9+NN5dkSkR5qkfVln1fTN2mOH08GcL8MzcMAsCgCBjl9GAtG2AAGXA2ygbdthzPCCXsFhx2cL08NsIIcIwL0L0A3YfnIdBQKfUgXyNGi2IYWC8IQo0WKCVBqgwrCIlw-DCIiL0pNI8RyO2ABWHprUYvEByHEc8THI0OOnJ1bBZKd+wFNC4EeJdl1XBh10kTcd33Q92WiIzSDM1Djis+NZTUs5-MY4Rqls0k2AIK4SDAqc8OxKcTGcEA+Ti9lpxPEJnAAHjqZxAggECGH2VIyGi1K5wgBd-NcFKZ3czyLNfbLcooAqirSUraqaTjwI88zvP8gBfbowAGgBdIA", #nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```

<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#lastChanged" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="lastChanged">
```{r, eval = FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    options = list(dark = FALSE),
    title = "My app",
    f7SingleLayout(
      navbar = f7Navbar(
        title = "Single Layout",
        hairline = FALSE
      ),
      toolbar = f7Toolbar(
        position = "bottom",
        f7Link(label = "Link 1", href = "https://www.google.com"),
        f7Link(label = "Link 2", href = "https://www.google.com")
      ),
      # main content
      f7Card(verbatimTextOutput("infos")),
      f7Card(
        f7Text(inputId = "text", label = "Text"),
        f7Slider(inputId = "range1", label = "Range", min = 0, max = 2, value = 1, step = 0.1),
        f7Stepper(inputId = "stepper1", label = "Stepper", min = 0, max = 10, value = 5),
        verbatimTextOutput("lastChanged")
      )
    )
  ),
  server = function(input, output) {
    output$infos <- renderPrint(input$shinyInfo)
    output$lastChanged <- renderPrint(input$lastInputChanged)
  }
)
```
</div>

This is convenient since usually, there is no shortcut to get the last changed value and this needs to be done server side in Shiny.

### Other information

`input$shinyInfo` gives the current __workerId__ (for shinyapps.io, Shiny Server Pro, Posit Connect) and the unique __sessionId__ (equal to `session$token` on the server side).
