A LILT app for iOS/Android smartphones and tablets about breast cancer prevention.
The following guide assumes that node
and npm
commands are available in the system, and Xcode is installed.
The Lilt Xcode project has some Cocoa dependencies. If you do not have cocoapods installed in your system, you have to install it with the following command:
$ brew install cocoapods
Or, if you prefer gem
:
$ gem install cocoapods
If this is your first time using CocoaPods, run
to create a local CocoaPods spec mirror.
Now go into app/ios
folder and run pod install
to install the dependencies (the Mixpanel dependency for now).
The react-native mobile app is located in the app
directory.
To setup react-native dependencies run the following commands inside the app
directory (assuming that node
and npm
commands are available):
$ npm install
...
$ react-native link
Application code is partially generated from a Python script which requires a virtualenv.
Just cd
to the app
dir and load the Python (2.7) virtualenv from requirements file.
Example using virtualenvwrapper
:
$ mkvirtualenv lilt
...
$ (lilt) pip install -r requirements.txt
Now you can launch the code generation and packager:
$ npm run gen
...
$ npm start
Now you can open the Xcode app project and launch the app.
App settings are stored inside a json file.
We use different json files for development and production environment.
The development settings are stored inside config.dev.json
file under the app
directory.
Production settings must be stored inside a config.prod.json
file in the same directory.
The production settings file is git-ignored.
The App is also available for the Android platform.
In addition to the configuration steps described in the above sections, you have to modify your
gradle.properties
file (usually located in ./gradle
directory of the home folder).
Inside the gradle.properties
file you have to specify some variables:
LILT_FIRENZE_SENO_RELEASE_STORE_FILE=...
LILT_FIRENZE_SENO_RELEASE_KEY_ALIAS=...
LILT_FIRENZE_SENO_RELEASE_STORE_PASSWORD=...
LILT_FIRENZE_SENO_RELEASE_KEY_PASSWORD=...
as described in the following guide: https://facebook.github.io/react-native/docs/signed-apk-android.html.
Application content is partially generated by a gen.py
Python script.
To generate the application react-native code, cd to the app
directory, setup the Python virtualenv (see this section for details) and run:
(lilt-venv)$ npm run gen
If you see a warning message in terminal output, maybe you did not set the
LILT_EDITOR_DATA_DIR
environment variable (see next section for details).
The app code generation is splitted in two steps: the first one imports app data from editor output directory (see App Content Editor section for details about the app data editor) into a processable directory structure (app/content
directory); the second step generates the actual code.
To properly execute the first step, the generation script must know where the editor output directory is located. You can specify it through the LILT_EDITOR_DATA_DIR
environment variable (if you do not set it a warning message is displayed).
The second generation step takes an array of page descriptors as input, and generates the corresponding react native page components. Page descriptors array is the union of:
- page descriptors imported from step 1
- page descriptors defined in
pages.json
(insidecontent
directory)
In general, all the stuff used as input for the second generation step process is located under the app/content
directory.
To simplify the explanation we will focus on pages.json
file structure, which looks like this:
[
{ ... }, // page 1 descriptor
{ ... }, // page 2 descriptor
...,
{ ... } // page n descriptor
]
All page descriptors have the same structure. For example, suppose that we want to generate a page that contains only one button; when the user press the button we want to switch to an hardcoded page. We can define our page in this way:
{
"id": "1", // unique page identifier
"title": "My Title", // page title
"style": "my_page", // page style (see later)
"content": { // page content
"myButton": {
"type": "button",
"text": "Press Me!",
"link": "hardcoded_page"
}
}
}
Each page has a unique id and a title.
The style
property identifies the page template. In the example we use the template my_page
.
Page templates are located inside the templates
directory.
The template filename is computed from the style
property in the json by adding the .tmpl.js
suffix.
So in the example the targeted template would be my_page.tmpl.js
.
Let's pretend that our page template is the following:
'use strict';
import React, { Component } from 'react';
import { View } from 'react-native';
import * as style from '../../style';
import * as blocks from '../../blocks';
export default class MyPage extends Component {
render() {
const { flexible, stretch } = style.common;
const { Button } = blocks;
return (
<View style={[flexible, stretch]}>
{{myButton}}
</View>
);
}
}
The content
object describes the page content to be generated.
Each content item is identified by a name: the key of the item (myButton
in the example).
The "myButton" string must refer to a placeholder in the corresponding page template, so that the generation script knows where to put the generated code.
Each item has at least the type
property that identifies the kind of item (button
in the example).
For now, the supported types are button
and markdown
.
Each item can then have other properties, that are specific to the particular item type. The generation script iterates through the page content and generates the react-native code relative to the content items.
After the code generation step, the script places the generated react-native components inside the js/pages/generated
directory. This is an important detail, because every relative path you use in page templates (e.g. import paths, require paths) must be relative to the generated page directory (not the page template directory).
The following sections describe some components generated by the script, but this is not a comprehensive list.
An example of button item is:
{
"type": "button",
"text": "Press Me!",
"link": "#1"
}
All properties are required.
type
: identifies the item type and must be the "button" string;text
: this is the button text;link
: identifies the page this button leads to. As of now, two type of links are supported:- links to a generated page: in this case the link must be in the form "#[target_page_id]", where target_page_id is a valid page identifier in
pages.json
file; - links to custom (i.e. not-generated) pages: in this case the link string must be the navigator route key explicitly defined in
navigator_data.tmpl.js
file.
- links to a generated page: in this case the link must be in the form "#[target_page_id]", where target_page_id is a valid page identifier in
A markdown item example is:
{
"type": "markdown",
"source": "my_markdown.md"
}
All properties are required.
type
: identifies the item type and must be the "markdown" string;source
: the name of source markdown file. In the above example, the generation script searches for a file namedmy_markdown.md
inside theapp/content/markdown
directory.
A ScrollView
react-native component is generated for each markdown item.
The scroll view shows the rendered markdown inside it.
Navigation between app pages is partially generated, too.
The reference template file is navigator_data.tmpl.js
inside the templates
directory.
Navigator routes are aggregated into the routes
object.
Each route is identified by its key in the routes
object, and it has a title
and component
property.
The final routes
object is the aggregation of two sub-objects:
- the
customRoutes
object, which hold "hardcoded" routes; - the
generatedRoutes
object, generated by the script from the informations inpages.json
file.
Another important variable here is the initialRouteId
string, which must be set to the id of app start route (either a generated route or an "hardcoded" route).
The markdown syntax that is currently supported is a subset of gfm (Github Flavoured Markdown):
-
headers (e.g.
## <header-string>
); -
paragraphs (blocks of text separated by a blank line);
-
bold style (e.g.
** bold text **
); -
images (e.g.
![alt-text](img)
) An image can be either "local" or identified by an url:- "local" image example:
![local-image](my_local_image.png)
In case of local images the markdown renderer "requires" (in react-native sense) that image from theapp/content/images/static
folder; - "remote" image example:
![remote-image](http://www.mysite.com/img.png)
In case of "remote" image, the markdown renderer downloads the image and put it into theapp/content/images/downloaded
directory. Once the download is finished, the renderer "requires" the downloaded image from thedownloaded
folder.
- "local" image example:
-
bulleted lists
-
links
The mobile app sends registered users data to a Google Spreadsheet.
The spreadsheet is accessed through a Google Apps Script, that exposes a POST endpoint to the mobile app.
The Google Apps Script can be found in users-backend
directory.
If you want to deploy the webapp elsewhere in order to use another spreadsheet, follow these steps:
- create the Google Spreadsheet and identify its "spreadsheet id".
The spreadsheet id is part of the spreadsheet url:
https://docs.google.com/spreadsheets/d/<spreadsheet-id>/edit#gid=0
- annotate the name of the sheet where you want to store users data (this will be referred as
sheet-id
); - create a new Google Apps Script project, then copy and paste the code from
users_manager.gs
into an emptygs
file; - In the "Main Variables" section fill the
spreadsheetId
variable with the spreadsheet id and theusersSheetName
variable withsheet-id
; - deploy the script as a webapp
The repository contains a simple markdown editor based on electron inside the content-editor
folder.
To launch the editor from scratch:
cd content-editor
npm install
npm start
Next time you will use the npm start
command only.
The markdown editor allows you to edit md
documents inside a folder.
By default, the ./markdown
folder is used (relative to the app directory), but you can pass another folder:
- With the
LILT_EDITOR_DATA_DIR
environment variable - Via command-line as the first (and only) parameter (in this case the command-line argument has priority over the environment variable)
- At program startup through the interface
Optionally you can show the developer tools specifying the LILT_EDITOR_SHOW_DEVTOOLS
environment variable, and setting its value to 1.
To deploy a new editor version from a Mac, make sure you have electron-packager
installed globally:
npm install -g electron-packager
The wine
command must be available too, because we are going to generate a Windows executable.
At this point:
- adjust the editor version in
package.json
file - run the
deploy.sh
script
The deploy.sh
script packages the app for mac (x64) and Windows platform.
If everything goes well an out
folder containing two zip files (one per platform) will be created.
You can now git-tag the new version and upload the new release on github.
See the LICENSE file for license rights and limitations (BSD-3).