Open Tracked Times

The List of Open Tracked Data contains all Tracked Times where no Accounting Report exists. As soon as you completed an Accounting Period all corresponding Data will be moved to History. While you have started the next Time-Tracking without finishing it’s not visible in the List of Open Times, but the HomePage will show you how long it’s already running.

The List

Open List of Open Tracked Times from TabbedPane:

T

Here’s the List grouped by days and sorted by Timestamp:

T01

If there’s no ‘Add Data‘ Action you have no permission to insert data manually (Application Menu Settings Permissions) – opening this list from History ActionBar will also look different. Action ‘Summary Open‘ will show a chart diagram of all entries from this list. (see below). Action ‘Reporting‘ will directly jump to Accounting to create a new Accounting Report.

Dimmed Rows

Data already transmitted to server are dimmed and read-only.

DimmedZ30

List Menu

Here’s the complete Menu:

T01menu

You also have access to Charts per Week or Month (see below) or jump directly to History.

List Charts

Summary All Open

Directly from ActionBar Action ‘Summary Open‘ will tell you how many hours are tracked since last Accounting Report.

Charts can be summarized per legal/other, paid/unpaid or per Category:

T02suma

T02sumb

…per Month

Sums per Month (from Overflow Menu):

T02montha

T02monthb

Sums start with newest month and you can go back and forth. (Previous / Next Actions)

To see older sums from History: there’s a quickl-jump Action in Overflow Menu:

T02menu

… per Week

Sums per Week (from Overflow Menu)

T02weeka

T02weekb

Sums start with newest week and you can go back and forth. (Previous / Next Actions)

… per Day

Sums per Day (from List Header Rows)

List is grouped by Day and ech Group Header gives you access to sums of this specific day: simply click on the Chart Icon:

T02header

T02daya

T02dayb

 

Time Data Row in detail

Tapping on a List Row you’ll get a Page with all the details:

T04

The content on this Page can be very different – it depends on Time Category, Device (Touch-Only or Keyboard) and Orientation (Portrait/Landscape). Chapter ‘UX Highligths‘ will explain in more detail.

On Top of Page there’s a Segmented TitleBar with up to 4 Segments:

  1. Category (Overview)
  2. Details
  3. Time
  4. Geo

Toggeling between these Segments is easy: Tap on a Title, use the Action from Overflow Menu, use the Navigation-Icons on Bottom of Page or Keyboard Shortcuts.

First Segment Category is also a Overview Page: you see from available Images what kind of data is there, tapping on a Image will go directly to corresponding Segment etc. If there’s a signed Customer Report: toggle ON and get the Report. If you have the Permission to modify Data you can change Values – even the Category can be changed using the Toggle.

Here’s a sample of Details Segment:

T05

Sample of Time Stamps:

T06

and here a sample of Geo Data:

T07

Data Menu

This is the full Menu:

T08

 

 

Transmitted or Queued Data

To see the current state of transmitted data to Server, select the Tab from tabbedPane:

Zt1

Running the Personal Edition ?

There’s nothing to transmit yet:

Zt2

Running the Group Edition ?

work-in-progress

TBD

Running the Enterprise Edition ?

Content of this Page depends on the way how you’re synchronizing your transmitted data: manually, every day, every week, …

The Upload Page tells you about the last sync done and next one scheduled.

Starting with Enterprise Edition your first steps probably will be to try it  out for FREE: there’s a 30 Day Test Account available from CoMo Solution.

To make it simple and easy to understand and follow the workflow using the Test Account you have to start transmission of tracked data manually.

Here’s the Upload Page for Test Accounts:

UploadData

There’s a DropDown to select the Date: all Open Tracked Data including the selected Date will be transmitted to Server. While doing your first tests it could make sense to switch Logging ON – this will give you a short protocol what happened. We’ll take a look on this later.

The ActionBar provides 3 ActionItems:

  • Network gives you some informations on Network State
  • Send Now will select all open records until selected Date and send to the Server
  • Admin opens the Browser and gives you access to TimeTracker Server Web Portal. Attention: you need username / password credentials !

Info on Network State opens a Dialog:

NetworkInfoDialog

Please take a look at the TitleBar: the Icons also tell you about Network State. More info on Dynamic TitleBars here.

ServerTitlebarAll

Tapping on “Send Now” Action will

  1. select the data from open records
  2. add data to the Queue
  3. mark data as transmitted

From List of Open Tracked Data you can see which data already was transmitted: transmitted rows are ‘dimmed

NotTransmitted

You can take a look at the Queue tapping on the Tab:

TabbedPaneServer

The ‘Queue‘ tab is only visible if you’re using the Enterprise Edition !

Normaly TimeTracker will transmit the data faster then switching to the Queue Tab, so it’s OK if there’s nothing inside the Queue:

QueueEmpty

It’s always a good idea to try out what happens if there’s no internet connection. Easiest way to test: Set your Device to ‘Airplane Mode‘ , switch ‘Logging‘ ON and Tap on ‘Send Now

SendNow

Watch the TitleBar: No Network, No Internet

We got a Toast telling us that 3 Records are transmitted and immediately we got a Logging entry: ‘Cannot transmit to Server’

Looking at the List of Open Tracked Data you’ll see: all are dimmed – all is marked as transmitted:

AllTransmitted

In reality the data was not sent to the Server because of missing network connection. Now there’s an entry in your Queue:

QueuedData

The Queue can contain different kind of data: ‘Tracked Data’ as seen above, Signature Files, Customer Reports, Accounting Reports. There are different Icons to see what kind of Data is waiting for transmission. All Actions trying to access your server will go into the Queue and sent to server step-by-step if network connection comes back. (FIFO – First-In-First-Out)

Tapping on a row opens a details Page:

QueuedDetail1

Server Action describes the type of data.

Messages: an Array of Server Messages: in this case telling you that there’s no connection. There could be other cases where you get HTTP Status Errors or so if something is wrong from Server. TimeTracker then will try again after 500 ms if there’s an Internet Connection.

JSON Data contains the Payload. You can do a long-press, select all and copy and send via email to your Admin.

Hint: In Test Mode this is always possible – otherwise there’s a property in settings if this is allowed or not – so maybe the Payload is invisible for users.

QueuedDetail2

Below the Payload there’s the Creation Timestamp and your Device PIN, userID and Transmission UUID.

Switch ‘Airplane Mode’ OFF and automagically the entry disappears from Queue and Logging shows success:

LogSuccess

Tap on ‘Admin‘ to Open the TimeTracker Server. (You need Username/Password)

Here are the 3 transmitted records:

ServerComo

You can tap on a row to see the Details. This is the ‘User’ Details View – there’s also a special ‘Admin’ Details View where you can see and edit all properties.

ServerComoDetails

Besides the TimeLog properties you’ll see Contact info (if a Contact was selected from your Device) and also all Geo Locations with Coordinates and Address from Reverse Geocoding. From GeoCoding DropDown you can select a specific location – this one will be marked green inside the embedded Map.

You can also see the Locations on a large fullscreen map:

ServerComoBigMap

Normally you will use the Server Webportal from your Desktop or Laptop, but I integrated the direct access into TimeTracker to enable this while you’re on-the-road. More Info on the Server Webportal can be found here (TBD)

To open the Browser from inside TimeTracker I’m using the Cascades Invokation Framework as usual. (See also chapter ‘Deep Integration‘)

Here’s the ActionItem (QML Code)

ActionItem {
    title: qsTr("Admin")
    imageSource: "asset:///images/server.png"
    ActionBar.placement: ActionBarPlacement.OnBar
    onTriggered: {
        app.invokeBrowser(REST.serverUrl() + "/TimeTrackerAdmin/")
    }
}

We’re asking the Server Class (C++ – mapped as ‘REST’) for the serverUrl from settings, then calling the method to invoke the Browser (C++)

void ApplicationUI::invokeBrowser(const QString& uri) {
    InvokeRequest request;
    request.setUri(uri);
    request.setTarget("sys.browser");
    request.setAction("bb.action.OPEN");
    mInvokeManager->invoke(request);
}

Have Fun with TimeTracker Enterprise Edition and TimeTracker Server by CoMo.

History

In ApplicationMenu Accounting you can specify how many months should be stored in local History. A maximum of 12 months is allowed.

From TabbedPane tap on History Tab:

H1

Next Page shows you only months where History exists – in this sample for January, February and March:

H2

Tap on a Month from History or use the corresponding ActionItem from Overflow Menu (three dots) and you’ll see exactly your Accounting Report:

H3

Of course the Report now is read-only, but you can send the Report again via Mail.

Also from ActionBar you have access to the details: all tracked data:

H4

This List of History Data is similar to the List of Open Tracked Data:

H5

Please notice the only difference:the ActionBar is missing all Actions.

Tapping on a row you’ll see the Details – also similar to Details from List of Open Data:

H6

Only difference again the ActionBar: from History it’s read-only so there’s only a Back Action and no Cancel / Save.

Hint: you cannot set ActionItems to invisible, you have to remove them:

You can remove all ActionItems:

//
trackedListPage.removeAllActions()
//

or you can remove single ActionItems:

if (! monitorTime.isAddTrackingPermitted()) {
    trackedListPage.removeAction(addDataAction)
}

 

From History Page you can also take a look at Monthly Charts:

H7

Using Previous and Next Actions you can get Charts from all Months in History.

There’s another Action to show the sums for Categories:

H8

Here’s the same Page in Landscape:

H9

There’s a chapter about Layouting if Orientation changes here.

Navigation Concepts

navigate_compass

Your application should make it easy to navigate through all the features and topics.

Tapping through Pages

As described I’m using a combination of TabbedPane as root with NavigationPanes as Tab and Pages or Sheets put on top.

tabbed_nav_panes

Users can always use the standard way: Tap on a Tab to get the root Page of this Tab, then use Actions or tap on rows of Lists to get more informations from Pages pushed on top. To go back swipe back from page to Page or use the back Action from ActionBar or swipe back from ActionBar to get direct access to the root TabbedPane.

N1_7

Use Shortcuts

Alway think on your customers using devices with physical keyboard and provide Shortcuts:

Scat

Shortcuts can make Navigation easy and fast on Keyboard Devices. Don’t forget to translate the keys to avoid collision with system-wide shortcuts translated by BlackBerry 10.

Shortcut {
    key: qsTr("Alt + r")
}

More infos on Shortcuts see here: Touch vs Keyboard

Provide alternate ways

It’s always a good idea to provide some alternative ways: let the user decide HowTo work with your app.

One of the most important Pages are the Details of Tracked Times. To get a clean UI and to avoid too many informations / fields on one single Page I segmented the content using a Segmented TitleBar:

titleBar: TitleBar {
    kind: TitleBarKind.Segmented
    options: [
        Option {
            text: qsTr("1. Category")
            imageSource: OrientationSupport.orientation != UIOrientation.Landscape ? "" : "asset:///images/category_tb.png"
            value: 0
            selected: true
        },
        Option {
            text: qsTr("2. Detail")
            imageSource: OrientationSupport.orientation != UIOrientation.Landscape ? "" : "asset:///images/details_tb.png"
            value: 1
            selected: false
        },
        Option {
            text: qsTr("3. Time")
            imageSource: OrientationSupport.orientation != UIOrientation.Landscape ? "" : "asset:///images/ic_clock_stopwatch_tb.png"
            value: 2
            selected: false
        },
        Option {
            text: qsTr("4. Geo")
            imageSource: OrientationSupport.orientation != UIOrientation.Landscape ? "" : "asset:///images/details_tb.png"
            value: 3
            selected: false
        }
    ]

Here’s the TitleBar:

SegmentedTitle

Images will only be displayed in Landscape:

LandscapeSegm

Segmented TitleBars are really helpful to segment the content …

Seg1_4

… but from my POV there’s a drawback on large devices like 5″ Z30:

it’s a long way from the bottom to the top and to tap on a Title to switch the content. So I added two alternate ways to navigate through the content:

  • ActionItems from Overflow Menu
  • NavigationRow at the bottom directly above the ActionBar

NavAlt

Now it’s up to the User: tap on the title directly, tap an ‘More’ and select Action or Tap on the Bottom-Navigation-Row.

Current selected TitleBar-Segment is disabled in ActionItems and displayed using a slightly larger Icon in Bottom-Navigation-Row.

Rotating to Landscape: the Bottom-Navigation-Row disappears, because now the way is much shorter and I need the space to display content:

Nxl

Working on a Keyboard Device ActionItems have shortcuts to navigate by Keys.

Content is king

You know: Content is King – don’t let the User think about Navigation Structures or how content is split using Segments. Provide some more natural Navigation: This Page also has Content-Based-Navigation:

Perhaps a new user of the app doesn’t know where to find Contact Info: simply tap on the blue colored Icon and TimeTracker automatically switches to 2. Detail – Segment, where all the Contact Data is displayed:

content_nav

From this kind of implicite navigation users learn it the easy way how your app is structured.

Using GestureHandlers it’s easy done:

gestureHandlers: [
    TapHandler {
        onTapped: {
            // do your stuff
        }
    }
]

To make segment-switching more visible to the user I also added an Animation. You’ll see this from the Videos.

Provide short ways

There are some situations where it makes sense to offer users a direct Navigation. per ex. from Tab Reports ActionBar User can directly jump to History or to all Open Tracked Times.

Short

There are more concepts HowTo make navigation easy for TimeTracker – will write later on this – for now I hope you got some ideas.

More UX highlights:

Lists: Colored, Dimmed, Markers

Colored Bars
ColoredZ30_Z10

List of Tracked Times can be a long list and you get this list for all ‘Open’ Tracked Times or from History all tracked times of a month or while creating a new accounting report.

List is ordered by Day and Time and each (Day) Header has an Icon:

line_chart_color

This Icon works as a Marker Icon telling the User that there’s a Chart Diagram available. Tapping on the Header opens the Diagram for this specific day:

Daychart

Take a look at the Chart Icon again:

header_iconZ30_Z10

it works well on dark and bright themes because the line is transparent and blue looks good on both themes.

Going back to the list: there are some rows marked orange:

row_orange

Orange marked rows tells the user: this is a row where data was modified after tracking.

To control tap on a row to get the details and make database info visible (from Overflow menu)

db_info

To add such a small colored bar directly on the right site take a look at this code:

Container {
    id: outerItemContainer
    layout: DockLayout {
    }
    horizontalAlignment: HorizontalAlignment.Fill
    Container {
        minWidth: 12
        maxWidth: 12
        verticalAlignment: VerticalAlignment.Fill
        horizontalAlignment: HorizontalAlignment.Right
        background: itemRoot.ListItem.view.rightMarkerColor(ListItemData)
    }
    // now layout all fields for this ListItem
}

The outer Container uses a DockLayout with HorizontalAlignment.Fill so we’re sure that always all available space is used – doesn’t matter if Portrait or Landscape.

As next we define the Container  for the colored bar and set minWidth and maxWidth to same size (12 px in this case), also we use VerticalAlignment.Fill. We don’t know the height of the Container but using VerticalAlignment.Fill we’re sure that always the height of surrounding Container was used.

HorizontalAlignment.Right will guarantee that the bar will always be placed at the right site.

Only left thing is to know if we have to colorize the row or not, so we ask a function: rightMarkerColor() is a function placed at ListView. There we do our business logic – in this case take the data of the row and test if we should color it because it’s modified or added manually:

function rightMarkerColor(data){
    if('insertedByUser' in data && data.insertedByUser == true){
        return insertedColor
    }
    if (data.modifiedUTC.length > 0){
        return modifiedColor
    }
    return nothingColor
}

we’re returning a property from ListView:

ListView {
    id: trackedList
     property variant insertedColor: rootPane.isDark() ? Color.Yellow : Color.Magenta
     property variant modifiedColor: Color.create("#FF9900")
     property variant nothingColor: Color.Transparent
     // ...
}

Creating a Color costs ressources, so if same color is used more then one time, it’s better to define the Color once and use the variable as we do here in our ListView.

In this App it’s the only place where I’m using these Colors – otherwise I would have defined them at root object (my tabbedPane)

Other rows are marked yellow on dark theme and magenta on bright theme:

yellow_magenta_rows

yellow doesn’t look good on the bright theme and magenta doesn’t look good on dark theme. So for now I’m using different colors but will change this later using another color working well on both themes and also in combination with the orange marked rows in same List.

You can track times (Start / Stop with current System time) or – if you have the permission – you can also add Times manually.

To distinguish manually added rows these are marked yellow / magenta. You can verify this from database info:

db_info_add

Marker Icons

If you inspect the list of tracked times you’ll find another Marker Icon:

overnight_rows

This time we have two similar but different Icons for dark and bright themes. To make the differences visible I put both icons on black and white background:

overnight_themes

On dark the right Icon fits best and there’s a great contrast between yellow and black.

On bright at first you could think the same Icon will work, but on the list it’s a really small Icon and you would overlook it easy. So I spent a blue background to the Icon to make it recognizable. To use different Icons for dark and bright Cascades uses static asset selectors.

What does this Icon mean ? All tracked records where the time spans over midnight will get this ‘overnight – marker – icon’. To control tap on the row and inspect start and stop times:

Times_overnight

User will find the same Icon with some text explaining: “Time tracked ‘Over Midnight'” – next time he/she sees the same Icon on a list row it’s clear: overnight !

Dimmed Rows

Using Enterprise or Group Editions from time to time you’re uploading your tracked data to server or send to group manager. As soon as data is uploaded you cannot edit tracked data. To visualize already transmitted data I’m dimming the rows. Here’s how it looks using a dark theme:

DimmedZ30

…and using a light theme:

DimmedZ10

Without taking a look at the details you immediately know which data is already transmitted to server or not.

Dimming is easy done by setting opacity of outer container. Here’s the QML code:

Container {
    id: outerItemContainer
    opacity: itemRoot.ListItem.view.myOpacity(ListItemData.isTransmitted)
    layout: DockLayout {
    }
    horizontalAlignment: HorizontalAlignment.Fill
    // ....
}

To get outer container filled completely it’s a good idea to use a DockLayout and HorizontalAlignment.Fill – vertical will be dynamic and depends from content.

To calculate the value of opacity I’m calling a function added to the ListView:

function myOpacity(isTransmitted){
    if(isTransmitted && isOpen()){
        if(Application.themeSupport.theme.colorTheme.style == VisualStyle.Dark){
            return 0.6
        } else {
            return 0.5
        }
    } else {
        return 1.0
    }
}

This ListView is used from list of open tracked data and also from history of tracked data. History data is always be sent to server – only if displaying open data I want to distinguish.

If data was transmitted and list is list of open tracked data I want to change opacity – otherwise I’m setting the default 1.0

I did some tests to find out the best value for opacity where users will notice if a row is dimmed, but on the other side the text should be readable. Setting 0.6 for a dark theme and 0.5 for a light theme works well. (see screenshots above)

Summary: dimming rows, using small colored bars or marker icons additional informations become ‘visible’ without any extra actions or taps by user.

More on UX Highlights:

Charts

At the end of a day, week or month it’s good to know the sums of all tracked times for a given period.

TimeTracker does this for you automatically and to make it easy to read, I added some diagrams.

Your next Chart is only a click away

Doesn’t matter where you are in the APP, you have access to sums and charts:

  • List of Tracked Times
    • daily statistic from Day Header
    • summary of all open from ActionBar
    • summary per month from Overflow Menu
    • summary per week from Overflow menu
  • Creating Accounting Report
    • directly from main Reporting Page
  • History Accounting Reports
    • Monthly Charts from ActionBar

Charts per Kind of Work / Category

Here you can see that all work is summarized in two blocks:

  • Legal Work vs Other Work
  • Paid Work vs. Unpaid Work

Ch1

… and here the same amount of work is summarized by different Categories:

Ch2

To make it easy to understand, the Bars and corresponding Legend have same Color: per ex. yellow for  Working Time.

Cascades doesn’t provide an API to draw diagrams – so i did this using plain Containers and some UI Logic. See this blog post here at BlackBerry Developer Blog where I described this in detail.

TimeTracker Diagrams are created similar. My custom ChartContainer needs some properties to set:

Container {
    id: categorySummaryChartContainer
    background: Color.Black
    // .....
    property int realMaxMinutes: 0
    property int cat0Minutes: 0
    property int cat1Minutes: 0
    property int cat2Minutes: 0
    property int cat3Minutes: 0
    property int cat4Minutes: 0
    property int cat5Minutes: 0
    property string cat0HoursLabel: "0:00"
    property string cat1HoursLabel: "0:00"
    property string cat2HoursLabel: "0:00"
    property string cat3HoursLabel: "0:00"
    property string cat4HoursLabel: "0:00"
    property string cat5HoursLabel: "0:00"
    // UI depends on  data
    property int cat0BarHeight: barMaxAvailableSpace*cat0Minutes/maxMinutes
    property int cat1BarHeight: barMaxAvailableSpace*cat1Minutes/maxMinutes
    property int cat2BarHeight: barMaxAvailableSpace*cat2Minutes/maxMinutes
    property int cat3BarHeight: barMaxAvailableSpace*cat3Minutes/maxMinutes
    property int cat4BarHeight: barMaxAvailableSpace*cat4Minutes/maxMinutes
    property int cat5BarHeight: barMaxAvailableSpace*cat5Minutes/maxMinutes
    layout: DockLayout {
    }
    // ...
}

To get more flexibility you should use arrays – but in this special case I could live with properties per bar.

Then for the bars:

    Container {
        id: chartContainer
        verticalAlignment: VerticalAlignment.Bottom
        translationY: -labelSize
        Container {
            id: barContainer
            layout: StackLayout {
                orientation: LayoutOrientation.LeftToRight
            }
            Container {
                layoutProperties: StackLayoutProperties {
                    spaceQuota: 0.2
                }
            }
            Container {
                id: barCat0
                background: Color.Yellow
                minHeight: cat0BarHeight
                layoutProperties: StackLayoutProperties {
                    spaceQuota: 1.0
                }
                verticalAlignment: VerticalAlignment.Bottom
            }
            Container {
                layoutProperties: StackLayoutProperties {
                    spaceQuota: 0.2
                }
            }
        // ...
        }
    // ...
}

again: to be more flexible define this from arrays using ComponentDefinitions. For my use-case it was ok to create statically.

Each bar itself will have the same width (spaceQuota 1.0) and between two bars there’s a small amount of space (spaceQuota 0.2). all bars live inside a DockLayout aligned to the bottom, the height is calculated from properties.

The information which Category belongs to Legal/Other and Paid/Unpaid is configured in Settings – Categories.

Here are two examples from Settings:

Working Time is classified as Leagl and also Paid Work:

Ch3

Travel Time is classified as Paid Work, but doesn’t count as Legal Work.

Ch4

Using bar charts users can easy compare the amount of tracked times. It’s also easy done for Developers using Custom Container Components.

More UX highlights:

Dynamic Pages

dynamic_bg

I already discussed dynamic Creation of Objects to speed up start time and to free memory as soon as possible.

Now let’s talk about dynamic Pages / Sheets where it depends from

  • data
  • settings
  • workflow and  status

how a Page will be layouted: which Containers, Fields or other Controls must be created, which ActionItems are available, where to change Visibility, Opacity or Icons and Colors ?

There’s an important rule you should always follow:

Spotlight On: Present relevant information only

spotlight

This can be easy explained taking a look at Categories and follow the impact of changing some properties.

Here are the Settings Category for

  • Working Time
  • Travel Time
  • Break / Rest

Swork_travel_break

Working Time:

  • Project: mandatory
  • Order: mandatory
  • Task: optional
  • Contact: mandatory

Travel Time:

  • Task: optional

Rest / Break

  • nothing

How does this look from Details Segment of Tracked Times Details:

DetailWork_travel_break

Only required or optional fields are visible to get User’s focus on the important things.

Please note the green Checkmark at Project and Order: this means both fields have a Validator attached and Validation Rules are checked and OK.

Container {
    id: projectEntryContainer
    visible: categoryMap.project
    TextField {
        id: projectText
        enabled: isEditable
        hintText: qsTr("Short project reference")
        validator: Validator {
            id: projectTextValidator
            mode: ValidationMode.Immediate
            errorMessage: qsTr("You must enter the Project Reference")
            onValidate: {
                if (! categoryMap.project || categoryMap.projectOptional || projectText.text.length > 0)
                    state = ValidationState.Valid;
                else
                    state = ValidationState.Invalid;
            }
        }
    }
}

The Container is only visible if Category requires a Project. TextField Validator checks if User has entered some Text or Project is optional.

Now let’s take a look at GPS Timestamps / GeoLocation.

Working Time:

  • requires GPS Stams at Start and Stop

TravelTime:

  • requires continuous GPS Tracking

Rest / Break:

  • requires NO GPS Tracking to guaranteee privacy

Now lets take a look at the Overview from Category Segment of Tracked Times Details:

OverviewWork_travel_break

Please note the different Icons for Geocoding / GPS Tracking: Working Time requires GPS Stamps at Start and Stop and displays a Marker with 2 flags. Travel Time requires continuous GPS Tracking and displays the earth. Rest/Break has no GPS stamps and gets another icon representing this state.

Contact is mandatory for Working Time, so there’s a Contact Icon, where TravelTime and Break have no Contact requested and th Icon is nearly invisible (Opacity 0.3). Contact Icon for Working Time is colored, so we know that there’s a Customer added.

You can see that you only have to set some properties or to change an Icon to provide an easy to understand UI where the important stuff is immediately recognized.

Dynamic TitleBars

Not only Pages can be dynamically – also TitleBars can reflect informations and dynamically change content.

If running TimeTracker Enterprise Edition your tracked data must be transmitted to server – this can be done manually or automatically. From Settings TimeTracker knows about the rules:

  • transmit Data always
  • transmit Data only if not Roaming
  • transmit Data only from WiFi

It’s the nature of a mobile app: you’re not always online, sometimes roaming and not always have access to WiFi. So transmitting data is a two-steps-operation:

  1. select data to be transmitted and put it in a Queue (can be done manually or automatically)
  2. from Queue send to server if all rules are valid – per ex. ‘not in roaming’

There’s an extra Tab (only visible if you’re running the Enterprise Edition) to see what’s inside the Queue and waiting. If nothing goes out and the Queue grows and grows users must inspect Settings from phone to find out the reason. To make this immediately visible I implemented a special TitleBar containing Icons reflecting the network state.

Here are some screenshots

WiFi available, Internet connection OK:

ServerTitlebarAll

NO WiFi, Internet connection OK:

ServerTitlebarNoWiFi

If no WiFi and Roaming the first Icon will change to:

signal_roaming

No Network, No Internet Connection:

ServerTitlebarNothing

Adding some icons to your TitleBar and connecting them to Signals if NetworkState changes tells your user the complete story why data wasn’t transmitted.

To provide such kind of dynamic TitleBars Cascades gives you the FreeFormTitleBar:

ComponentDefinition {
     id: serverTitlebarComponent
    TitleBar {
        id: serverTitlebar
        property alias titleText: titlebarLabel.text
        kind: TitleBarKind.FreeForm
        scrollBehavior: TitleBarScrollBehavior.Sticky
        kindProperties: FreeFormTitleBarKindProperties {
            Container {
                rightPadding: 10
                leftPadding: 20
                layout: StackLayout {
                    orientation: LayoutOrientation.LeftToRight
                }
                Label {
                    id: titlebarLabel
                    text: "Titlebar"
                    textStyle.base: SystemDefaults.TextStyles.TitleText
                    textStyle.color: Color.White
                    verticalAlignment: VerticalAlignment.Center
                    layoutProperties: StackLayoutProperties {
                        spaceQuota: 1
                    }
                }
                Container {
                    layout: StackLayout {
                        orientation: LayoutOrientation.LeftToRight
                    }
                    verticalAlignment: VerticalAlignment.Center
                    // images will be automatically updated from C++
                    // as soon as any events are fired
                    ImageView {
                        id: wifiSignalImage
                        objectName: "wifiSignalImage"
                        imageSource: "images/titlebar/no_signal.png"
                        verticalAlignment: VerticalAlignment.Center
                    }
                    ImageView {
                        id: onlineOfflineImage
                        objectName: "onlineOfflineImage"
                        imageSource: "images/titlebar/offline.png"
                        verticalAlignment: VerticalAlignment.Center
                    }
                }
            }
        }
    }
} // end serverTitlebarComponent

I defined the ServerTitleBar as a ComponentDefinition attached to my root object (TabbedPane). Read here about dynamic creation.

This ServerTitleBar is used from some of my Pages and as soon as these Pages are created dynamically I create and add the TitleBar. Then the Page becomes the owner and this TitleBar will be destroyed together with the Page.

Layout of this TitleBar is a simple StackLayout using a Label for the Title and two Icons. These Icons will change if NetworkState is changing. This stuff is done by C++ methods like this one:

void Rest::updateServerTitleBarNetstatus(int netstatusInfo)
{
    // there can be some pages using a ServerTitlebar
    QList<ImageView*> images = Application::instance()->scene()->findChildren<ImageView*>(
            "wifiSignalImage");
    if (images.size() > 0) {
        QString imageSource = QDir::currentPath() + "/app/native/assets/images/titlebar/";
        switch (netstatusInfo) {
            case NetstatusInfo::WifiOrBETTER:
                imageSource += "wifi.png";
                break;
            case NetstatusInfo::Cellular:
                if (roaming()) {
                    imageSource += "signal_roaming.png";
                } else {
                    imageSource += "signal.png";
                }
                break;
            case NetstatusInfo::Offline:
                imageSource += "no_signal.png";
                break;
            default:
                return;
        }
        for (int i = 0; i < images.size(); ++i) {
            ImageView* image = images.at(i);
            image->setImageSource(imageSource);
        }
    }
}

Creating the ServerTitleBar per ex. from ServerQueueListPage is done in onCreationCompleted{}:

onCreationCompleted: {
        // set parent to the Page where the TitleBar belongs to, so it will be destroyed with the Page
        serverQueueListPage.titleBar = rootPane.serverTitleBar(serverQueueListPage)
        serverQueueListPage.titleBar.titleText = qsTr("List of queued Data")
    }

this is the function from rootPane:

function serverTitleBar(parent) {
    return serverTitlebarComponent.createObject(parent)
}

As you can see the TitleBar was created from ComponentDefinition with ServerQueueListpage as parent.

I’m using such kind of TitleBars in some of my apps, sometimes also adding Login/Logoff Icons to visualize if User is logged in or not.

More UX Highlights:

Touch vs Keyboard

Please take a look at the workflow in ‘Strict’ Mode – explained in detail here.

All works well and looks great on Touch Device and I have tried to minimze the Touch Gestures / Taps you have to do while starting a new Tracking.

Same will also work on a Keyboard Device like Q10 or Q5 – demonstrated in detail for ‘Free’Mode here.

hmmm – there’s a keyboard integrated – can we do it even faster ? Yep !

Type’n’Go TimeTracker:

G → (Alt-n) → G → G → G

That’s all 🙂

Let’s take a look how this works:

At first there’s the HomeScreen:

Q10home_G

Typing ‘G‘ will do the same as Tapping on the Action ‘go‘ – if you don’t know which Key to type, tap on the overflow menu (three dots):

Q10home_Menu_G

Please notice the small shortcut listed in top-right corner. This is only visible on a keyboard device. Developing this is easy:

ActionItem {
    id: startStopAction
    title: trackedTimeContainer.running ? qsTr("STOP") : qsTr("START")
    ActionBar.placement: ActionBarPlacement.OnBar
    imageSource: trackedTimeContainer.running ? "asset:///images/stop.png" : "asset:///images/go.png"
    onTriggered: {
        if (trackedTimeContainer.running) {
            pushTimeStopper()
        } else {
            pushTimeStarter()
        }
    }
    shortcuts: [
        Shortcut {
            enabled: trackedTimeContainer.running
            key: qsTr("s")
        },
        Shortcut {
            enabled: !trackedTimeContainer.running
            key: qsTr("g")
        }
    ]
}

This Action is a Toggle between Start and Stop, so there are two different titles, Icons and also ShortCuts.

Important: please don’t only add your shortcut this way:

key: “s”

surround the Key with qsTr() – then you can translate the key into different languages. It’s not an easy task to find unique letters through the app – esp. because there are some default keys already used (and translated) by the System to Edit, Zoom, Delete etc. Take a look at the documentation.

Back to our workflow: ‘G’ opens the next Page where you can select the Category:

Q10 what

If the selected Category is OK we simply type again ‘G‘ to go on. To select another category without moving your fingers to the Icon and Tapping you can type ‘alt n‘ to get this:

Q10altn

The Selection Marker was moved from ‘Working Time’ to ‘Work Readyness’. Typing alt-n again and again the Marker will cycle through all Categories.

Then typing ‘G‘ you can Go On to the next Page – typing ‘U‘ you can cancel (Undo) the workflow.

Shortcuts for ‘G’ and ‘U’ are similar to the Action from HomeScreen:

ActionItem {
    title: qsTr("Cancel")
    ActionBar.placement: ActionBarPlacement.OnBar
    imageSource: "asset:///images/ic_cancel.png"
    onTriggered: {
        cancelAndBack()
    }
    shortcuts: [
        SystemShortcut {
            type: SystemShortcuts.Undo
        }
    ]
},
ActionItem {
    title: qsTr("Go On")
    imageSource: "asset:///images/ic_next.png"
    ActionBar.placement: ActionBarPlacement.OnBar
    onTriggered: {
        saveAndGoOn()
    }
    shortcuts: [
        Shortcut {
            key: qsTr("g")
        }
    ]
}

Only different: ‘U’ (Undo) is a predefined SystemShortcut, where ‘G’ is a custom Shortcut.

Categories are displayed using a ListView with a simple ArrayDataModel. Besides the Category Icon there’s a green Marker Icon, This Marker Icon is only visible if Category Id == selected Id.

Categories will be selected by Tapping on an ImageView and Going On with selected Category from ListView function onTriggered{}:

onTriggered: {
    newCategory = categoryDataModel.data(indexPath)
    saveAndGoOn()
}

Nothing special.

To enable Shortcuts we have to tell Cascades that the ListView should receive Keys (input.route) and to add shortcuts to the ListView itself:

ListView {
     id: categoryList
     inputRoute.primaryKeyTarget: true
     shortcuts: [
         Shortcut {
             key: qsTr("Alt + n")
             onTriggered: {
                 toggleCategory()
             }
         }
     ]
     function toggleCategory(){
         if(newCategory < 5){
             newCategory +=1
         } else {
             newCategory = 0
         }
     }
}

‘G’oing on opens the next Page with details.

Request Focus

There are no or up to three Input Fields on the next Page: Project, Order, Task. It depends from Settings – Categories which fields will be displayed.

Q10 details

To enter data on a Touch Device you simply Tap on the field and enter the data. On a keyboard device it would be nice to enter data immediately without tapping into a field. That’s the reason why we need requestFocus().

Hint: think carefully when to request the focus, because on a Touch device the virtual keyboard will come up and perhaps this is not what user expects. So I’m always testing if working on a Device with a physical keyboard:

bool ApplicationUI::isKeyboardDevice() {
    bb::device::HardwareInfo hardware;
    return hardware.isPhysicalKeyboardDevice();
}

and here’s the code from QML:

    function requestFocusFirstField(){
        if(!app.isKeyboardDevice()){
            return
        }
        if(projectContainer.visible){
            if(projectText.text.length == 0){
                projectText.requestFocus()
                return
            }
        }
        if(orderContainer.visible){
            if(orderText.text.length == 0){
                orderText.requestFocus()
                return
            }
        }
        if(taskContainer.visible){
            if(taskText.text.length == 0){
                taskText.requestFocus()
            }
        }
    }

I’m checking if the field is visible and also if there’s already some text typed.

If visible and field is empty User can start typing immediately.

TimeTracker Shortcuts

TimeTracker only uses less easy-to-remember Shortcuts:

  • G:
    • Go / Start / Go On
  • S:
    • Stop / Stop+Go
  • U:
    • Undo / Cancel
  • E:
    • Edit
  • alt N:
    • Next Category / Next Selection
  • alt shift R:
    • Refresh
  • 1,2,3,4:
    • Select Tab #1 … #4
  • +:
    • Add new Record

Hint: to type a ‘1’ on Q10 you have to type ‘alt w’. Adding a Shortcut’1′  to an Action doesn’t work, adding ‘alt w’ works, but then also ‘alt w’ is displayed as shortcut from menu. You can use a trick: define two shortcuts and the first one will be displayed:

shortcuts: [
    Shortcut {
        key: "1"
    },
    Shortcut {
        key: qsTr("Alt + w")
    }
]

Q10short1234

Business Users want to be productive

I really recommend every developer to think carefully about using Shortcuts to simplify workflows or speed things up: BlackBerry Business App Users want to be productive !

Orientation

BlackBerry 10 Touch-Only devices can be used in Portrait or Landscape, where the Q5 / Q10 have a square screen and don’t distinguish between Portrait and Landscape.

There’s not the one-and-only-way-to-go HowTo solve the Orientation challenge.

Sometimes it helps using a DockLayout or using a StackLayout where sizes aren’t defined by size but relative using spaceQuota.

Cascades documentation gives you many hints and informations HowTo do this. So I only want to show some examples what can be done.

Simple Layout to support all

Here’s a simple ListView:

Q10 what

Z30_what

Z10_landscape

On a Q10 we have 2 rows of 3 items,

on a Z30 / Z10 we have 3 rows of 2 items in Portrait and 2 rows of 3 items in Landscape.

My default is always for Touch Screens in Portrait and the ListView Layout is defined this way:

layout: GridListLayout {
columnCount: 2
cellAspectRatio: 360/300
}

It’s a GridListLayout with 2 Columns where inside a Column a ImageView and Label are placed.
To watch for Orientation changes I added an orientationHandler to the Page:

attachedObjects: [
    OrientationHandler {
        onOrientationAboutToChange: {
            categorySelectionPage.reLayout(orientation)
        }
    }
]

As soon as orientation changes a function reLayout() was called.

function reLayout(orientation){
    if (orientation == UIOrientation.Landscape) {
        categoryList.layout.columnCount = 3
        categoryList.layout.cellAspectRatio = 360/200
    } else {
        categoryList.layout.columnCount = 2
        categoryList.layout.cellAspectRatio = 360/300
    }
}

Important: as soon as the Page was created I check if the device is square and change the parameters so it fits on the small screen – also I’m checking if the user is already holding the device in Landscape.

onCreationCompleted: {
    if (OrientationSupport.orientation == UIOrientation.Landscape) {
        reLayout(OrientationSupport.orientation)
    } else if (app.isSquare()) {
        categoryList.layout.columnCount = 3
        categoryList.layout.cellAspectRatio = 360/360
    }
}

Complex Layout to support’em all

Let’s take a look at a more complex sample. Please understand that I cannot show you all the code behind the scenes. To learn HowTo do this with Cascades you can attend a Cascades Workshop by me 😉 … or I develop the complete App for you 🙂

Here’s the first Tab from Tracked Times Details on Q10 and also Z30 Portrait and Landscape. Do you find out all the differences ?

Q10cat

Z30catportrait

Z30catlandscape

There are some Quick-Access Icons to jump to Geodata, Contact, Task/Project/Order and so on.

Q10 shows small Icons for this because there’s not so much space.

Z30 Portrait shows large icons in the bottom part of the screen, where in Landscape only the small Icons are visible and some fields are displayed at the right site of the Screen.

This Details Page is a Page with a Segmented TitleBar: 1. Category, 2. Detail, 3. Time, 4. Geo.

To switch between the Segments it’s easy on a Q10: simply use the 1,2,3 or 4 as key.

On a Z30 in Portrait with 5″ Screen it’s a long way up to the Top of the Screen, so I added a small navigation row at the bottom to easy swap between Segments. Moving the Z30 into Landscape this row disappears because there’s not enough space and also the TitleBar isn’t so far away.

Please also notice: in Portrait the TitleBar Segments are Text-Only where in Landscape I added an Icon because there’s much more space available.

Hope you got some inspiration what you can to to make your Users happy.

Themes 10.2

OS 10.2 only supports two Themes: dark and light, where the default depends from technology: on an OLED Display you must use a dark theme because this is the best to consume as less battery as possible. So Z10 and Q5 are using the light theme and Z30 and Q10 are using the dark theme.

Which Theme to use is defined in bar-descriptor.xml and only with some tricks you can change this on runtime.

OS 10.3 will be much more flexible and also add some descent colors to customize the UI. I will blog later about TimeTracker 2.x for OS 10.3.x

Most screens in TimeTracker are running out-of-the-box on dark and light theme, sometimes I’m using different Icons or different colors for Label.

//
textStyle.color: Application.themeSupport.theme.colorTheme.style == VisualStyle.Dark ?
        Color.LightGray : Color.DarkGray
//

Here’s a list in dark and light:

Z30listdark

Z10listlight

Manually inserted Times are marked yellow on dark and magenta on light theme, colors of Header is LightGray on dark and DarkGray on light theme. Modified records are colored orange on both themes.

There’s a small marker icon (Moon) to mark an Item as ‘Overnight’: this Icon is different for both themes. I’m using static asset folders to store those Icons.