In fact, while porting gPodder from Harmattan to Sailfish, it reminded me of porting from Maemo 4 to Maemo 5 - same toolkit (then Gtk+, now Qt/QML), similar extensions (then Hildon, now Components) but different concepts (then normal menus in Maemo 4 vs. HildonAppMenu in Maemo 5, now toolbars and context menus in Harmattan vs. pulley menus in Silica).
Obviously, as gPodder is written in Python here, this applies mostly to PySide-based applications, but the real difference is in the QML (there's really only one short snippet in the Python code that decides which QML import path to use). The approach taken is somewhat simliar to what the Jolla guys have been presenting at FOSDEM 2013, but I don't recall everything and have tailored the approach to better fit my workflow.
Before we start, be sure to know that qt-components is in the Sailfish Emulator (once you install it via "zypper in qt-components" as root), so that could be a shortcut for quick porting exercises and as an interim solution.
For starters, I've cleaned up gPodder's QML UI to make better use of Qt Components (historically, gPodder's QML UI was started at a time where Harmattan Qt Components were not even out or announced, so wazd and me came up with our own style - yes, that's pre-feb11 even!). So, instead of custom transitions and state management, everything is a Page, and transitions happen via the PageStack (the Silica equivalents are also called Page and PageStack). So, assuming you only have pages and so on, you could theoretically already use this or that depending on which platform you are on. Unfortunately, QML doesn't have conditional imports or something like that, so we have to add some abstraction layer.
Here's the basic idea:
- Create a new "Components" set - I'm calling mine "org.gpodder.qmlui"
- You define which items your components have, and a common API
- You implement your components twice: Once for Harmattan, once for Sailfish
- At runtime (or even compile time if you would want that) you decide which set of components you use
- Your application GUI code only imports QtQuick, maybe QtMultimediaKit (if you need it - it's available on both platforms) and your custom components (org.gpodder.qmlui in my case)
My custom components at this time look like this:
- Harmattan implementation of org.gpodder.qmlui (as of gPodder 3.5.0)
- Sailfish implementation of org.gpodder.qmlui (as of gPodder 3.5.0)
Other wrappers just deal with API differences. For example, ScrollDecorator exists in both Harmattan and Sailfish, but in Harmattan the property is called "flickableItem" and on Sailfish it's "flickable". So my ScrollScroll (harmattan, sailfish - also, noticed a naming pattern there? ;) takes care of hiding the differences, and I just use "flickable" on a ScrollScroll, and it will do The Right Thing on the platform it's currently running on.
When the API is the same (e.g. for Button), you still need to create pass-through components because of the different import path: Button (harmattan, sailfish).
But there's also some more elaborate differences. For example, I want to have menus - a toolbar menu on Harmattan and a pulley menu on Sailfish. So I defined a very simple Action (I'd be surprised if such a component doesn't already exist, but I didn't find one when I wasn't looking (sic)) that basically has a text and some signal attached to it. In my application code, I define actions as a property on my PagePage (which is a Page with some special code to transform actions into whatever the platform representation is). See the main page actions for an example. The ActionMenu (harmattan, sailfish) then takes care of creating a ContextMenu (Harmattan) or PullDownMenu (Sailfish) and PagePage (harmattan, sailfish) takes care of creating a toolbar on Harmattan and forwarding a reference to the listview to which the PullDownMenu should be attached to (in Harmattan, I still need to pass the listview there, but it's not used).
The same customization also happens for the ListList (harmattan, sailfish), which is a ListView (Harmattan) or SilicaListView (Sailfish), but also takes care of displaying a header (which still needs to be styled correctly on Sailfish) if the platform requires it.
Once you've created your custom set of components, you have to create a qmldir file that lists the components available and in which version of the components they are available. Read up on Declarative Modules in the official documentation.
So there you have it. One QML codebase, two UX targets that are well-integrated. You can see the results in the just-released gPodder 3.5.0, which is more Harmattan-ish on the N9 than before (although some items have moved from the toolbar into the menu for simplicity reasons, and I actually think it's cleaner now) and also looks and feels quite native on Sailfish (it's not done yet, and I'm sure the UI will evolve and adapt once Silica gets more mature and we see more Sailfish apps).
What I'd like to see in the Sailfish SDK: Different Ambiance backgrounds (darker, brighter, different hues) so that developers like me can test if their apps look good atop more than just the nice blue default background.
As a last hint (I couldn't find that in the API docs, but in the component gallery): Sailfish Silica's Label Component has a "truncationMode" property. If you set it to "TruncationMode.Fade", you never want to see elided text again, because it looks so sexy! :)