[Merge] lp:~fboucault/camera-app/ui_rewrite into lp:camera-app

Olivier Tilloy olivier.tilloy at canonical.com
Thu Jun 26 09:32:53 UTC 2014


Posting a few minor comments, I haven’t reviewed all the code yet but that’s a start (and it looks good so far).

Diff comments:

> === modified file 'CMakeLists.txt'
> --- CMakeLists.txt	2014-05-06 18:41:31 +0000
> +++ CMakeLists.txt	2014-06-26 09:06:29 +0000
> @@ -79,6 +79,9 @@
>  
>  file(GLOB QML_JS_FILES *.qml *.js)
>  
> +# make the files visible on qtcreator
> +add_custom_target(QML_JS_TARGET ALL SOURCES ${QML_JS_FILES})
> +
>  install(FILES ${QML_JS_FILES}
>      DESTINATION ${CAMERA_APP_DIR}
>      )
> @@ -105,6 +108,9 @@
>  install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE}
>      DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
>      )
> +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE}
> +    DESTINATION /
> +    )
>  
>  add_subdirectory(CameraApp)
>  add_subdirectory(click)
> 
> === modified file 'CameraApp/CMakeLists.txt'
> --- CameraApp/CMakeLists.txt	2013-09-26 14:16:34 +0000
> +++ CameraApp/CMakeLists.txt	2014-06-26 09:06:29 +0000
> @@ -3,11 +3,13 @@
>  set(plugin_SRCS
>      components.cpp
>      advancedcamerasettings.cpp
> +    fileoperations.cpp
>      )
>  
>  set(plugin_HDRS
>      components.h
>      advancedcamerasettings.h
> +    fileoperations.h
>      )
>  
>  add_library(camera-qml SHARED ${plugin_SRCS} ${plugin_HDRS})
> 
> === modified file 'CameraApp/advancedcamerasettings.cpp'
> --- CameraApp/advancedcamerasettings.cpp	2013-06-21 10:22:23 +0000
> +++ CameraApp/advancedcamerasettings.cpp	2014-06-26 09:06:29 +0000
> @@ -24,6 +24,7 @@
>  #include <QtMultimedia/QCameraControl>
>  #include <QtMultimedia/QMediaService>
>  #include <QtMultimedia/QVideoDeviceSelectorControl>
> +#include <QtMultimedia/QCameraFlashControl>
>  
>  AdvancedCameraSettings::AdvancedCameraSettings(QObject *parent) :
>      QObject(parent),
> @@ -113,13 +114,25 @@
>  
>      QCameraControl *camControl = qobject_cast<QCameraControl*>(control);
>      if (camControl == 0) {
> -        qWarning() << "No viewfinder settings support";
> +        qWarning() << "No camera control support";
>          return 0;
>      }
>  
>      return camControl;
>  }
>  
> +QCameraFlashControl *AdvancedCameraSettings::flashControlFromCamera(QCamera *camera) const
> +{
> +    QMediaControl *control = mediaControlFromCamera(camera, QCameraFlashControl_iid);
> +    QCameraFlashControl *flashControl = qobject_cast<QCameraFlashControl*>(control);
> +
> +    if (flashControl == 0) {
> +        qWarning() << "No flash control support";
> +    }
> +
> +    return flashControl;
> +}
> +
>  QObject* AdvancedCameraSettings::camera() const
>  {
>      return m_cameraObject;
> @@ -142,25 +155,15 @@
>          m_camera = camera;
>          if (m_camera != 0) {
>              this->connect(m_camera, SIGNAL(stateChanged(QCamera::State)),
> -                          SIGNAL(resolutionChanged()));
> -        }
> -
> -        QVideoDeviceSelectorControl* selector = selectorFromCamera(m_camera);
> -        m_deviceSelector = selector;
> -        if (selector) {
> -            m_deviceSelector->setSelectedDevice(m_activeCameraIndex);
> -
> -            QCameraViewfinderSettingsControl* viewfinder = viewfinderFromCamera(m_camera);
> -            if (viewfinder) {
> -                m_viewFinderControl = viewfinder;
> -                resolutionChanged();
> +                          SLOT(onCameraStateChanged()));
> +            if (m_camera->state() == QCamera::LoadedState || m_camera->state() == QCamera::ActiveState) {
> +                readCapabilities();
>              }

This code is duplicated, you could probably call onCameraStateChanged() instead.

>  
> -            QCameraControl* cameraControl = camcontrolFromCamera(m_camera);
> -            if (cameraControl) {
> -                QObject::connect(cameraControl,
> -                                 SIGNAL(captureModeChanged(QCamera::CaptureModes)),
> -                                 this, SIGNAL(resolutionChanged()));
> +            QVideoDeviceSelectorControl* selector = selectorFromCamera(m_camera);
> +            m_deviceSelector = selector;
> +            if (selector) {
> +                m_deviceSelector->setSelectedDevice(m_activeCameraIndex);
>              }
>          }
>  
> @@ -168,6 +171,29 @@
>      }
>  }
>  
> +void AdvancedCameraSettings::readCapabilities()
> +{
> +    m_viewFinderControl = viewfinderFromCamera(m_camera);
> +    m_cameraControl = camcontrolFromCamera(m_camera);
> +    if (m_cameraControl) {
> +        QObject::connect(m_cameraControl,
> +                         SIGNAL(captureModeChanged(QCamera::CaptureModes)),
> +                         this, SIGNAL(resolutionChanged()));
> +    }
> +
> +    m_cameraFlashControl = flashControlFromCamera(m_camera);
> +
> +    Q_EMIT resolutionChanged();
> +    Q_EMIT hasFlashChanged();
> +}
> +
> +void AdvancedCameraSettings::onCameraStateChanged()
> +{
> +    if (m_camera->state() == QCamera::LoadedState || m_camera->state() == QCamera::ActiveState) {
> +        readCapabilities();
> +    }
> +}
> +
>  void AdvancedCameraSettings::setActiveCameraIndex(int index)
>  {
>      if (index != m_activeCameraIndex) {
> @@ -177,6 +203,7 @@
>          }
>          Q_EMIT activeCameraIndexChanged();
>          Q_EMIT resolutionChanged();
> +        Q_EMIT hasFlashChanged();
>      }
>  }
>  
> @@ -191,3 +218,14 @@
>  
>      return QSize();
>  }
> +
> +bool AdvancedCameraSettings::hasFlash() const
> +{
> +    if (m_cameraFlashControl) {
> +        return m_cameraFlashControl->isFlashModeSupported(QCameraExposure::FlashAuto)
> +            && m_cameraFlashControl->isFlashModeSupported(QCameraExposure::FlashOff)
> +            && m_cameraFlashControl->isFlashModeSupported(QCameraExposure::FlashOn);
> +    } else {
> +        return false;
> +    }
> +}
> 
> === modified file 'CameraApp/advancedcamerasettings.h'
> --- CameraApp/advancedcamerasettings.h	2013-06-21 10:22:23 +0000
> +++ CameraApp/advancedcamerasettings.h	2014-06-26 09:06:29 +0000
> @@ -27,6 +27,7 @@
>  #include <QtMultimedia/QMediaControl>
>  
>  class QCameraControl;
> +class QCameraFlashControl;
>  
>  class AdvancedCameraSettings : public QObject
>  {
> @@ -35,6 +36,7 @@
>      Q_PROPERTY (int activeCameraIndex READ activeCameraIndex WRITE setActiveCameraIndex
>                  NOTIFY activeCameraIndexChanged)
>      Q_PROPERTY (QSize resolution READ resolution NOTIFY resolutionChanged)
> +    Q_PROPERTY (bool hasFlash READ hasFlash NOTIFY hasFlashChanged)
>  
>  public:
>      explicit AdvancedCameraSettings(QObject *parent = 0);
> @@ -43,16 +45,23 @@
>      void setCamera(QObject* camera);
>      void setActiveCameraIndex(int index);
>      QSize resolution() const;
> +    bool hasFlash() const;
> +    void readCapabilities();
>  
>  Q_SIGNALS:
>      void cameraChanged();
>      void activeCameraIndexChanged();
>      void resolutionChanged();
> +    void hasFlashChanged();
> +
> +private Q_SLOTS:
> +    void onCameraStateChanged();
>  
>  private:
>      QVideoDeviceSelectorControl* selectorFromCamera(QCamera *camera) const;
>      QCameraViewfinderSettingsControl* viewfinderFromCamera(QCamera *camera) const;
>      QCameraControl *camcontrolFromCamera(QCamera *camera) const;
> +    QCameraFlashControl* flashControlFromCamera(QCamera* camera) const;
>      QCamera* cameraFromCameraObject(QObject* cameraObject) const;
>      QMediaControl* mediaControlFromCamera(QCamera *camera, const char* iid) const;
>  
> @@ -61,6 +70,9 @@
>      QVideoDeviceSelectorControl* m_deviceSelector;
>      int m_activeCameraIndex;
>      QCameraViewfinderSettingsControl* m_viewFinderControl;
> +    QCameraControl* m_cameraControl;
> +    QCameraFlashControl* m_cameraFlashControl;
> +
>  };
>  
>  #endif // ADVANCEDCAMERASETTINGS_H
> 
> === modified file 'CameraApp/components.cpp'
> --- CameraApp/components.cpp	2012-11-12 09:56:58 +0000
> +++ CameraApp/components.cpp	2014-06-26 09:06:29 +0000
> @@ -1,8 +1,9 @@
>  /*
> - * Copyright (C) 2012 Canonical, Ltd.
> + * Copyright (C) 2014 Canonical, Ltd.
>   *
>   * Authors:
>   *  Ugo Riboni <ugo.riboni at canonical.com>
> + *  Florian Boucault <florian.boucault at canonical.com>
>   *
>   * This program is free software; you can redistribute it and/or modify
>   * it under the terms of the GNU General Public License as published by
> @@ -20,10 +21,15 @@
>  #include <QtQuick>
>  #include "components.h"
>  #include "advancedcamerasettings.h"
> +#include "fileoperations.h"
>  
>  void Components::registerTypes(const char *uri)
>  {
> +   Q_ASSERT(uri == QLatin1String("CameraApp"));
> +
> +    // @uri CameraApp
>      qmlRegisterType<AdvancedCameraSettings>(uri, 0, 1, "AdvancedCameraSettings");
> +    qmlRegisterType<FileOperations>(uri, 0, 1, "FileOperations");
>  }
>  
>  void Components::initializeEngine(QQmlEngine *engine, const char *uri)
> 
> === added file 'CameraApp/fileoperations.cpp'
> --- CameraApp/fileoperations.cpp	1970-01-01 00:00:00 +0000
> +++ CameraApp/fileoperations.cpp	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,28 @@
> +/*
> + * Copyright (C) 2014 Canonical, Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "fileoperations.h"
> +#include <QtCore/QFile>
> +
> +FileOperations::FileOperations(QObject *parent) :
> +    QObject(parent)
> +{
> +}
> +
> +bool FileOperations::remove(const QString & fileName) const
> +{
> +    return QFile::remove(fileName);
> +}
> 
> === added file 'CameraApp/fileoperations.h'
> --- CameraApp/fileoperations.h	1970-01-01 00:00:00 +0000
> +++ CameraApp/fileoperations.h	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,31 @@
> +/*
> + * Copyright (C) 2014 Canonical, Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef FILEOPERATIONS_H
> +#define FILEOPERATIONS_H
> +
> +#include <QtCore/QObject>
> +
> +class FileOperations : public QObject
> +{
> +    Q_OBJECT
> +
> +public:
> +    explicit FileOperations(QObject *parent = 0);
> +    Q_INVOKABLE bool remove(const QString & fileName) const;
> +};
> +
> +#endif // FILEOPERATIONS_H
> 
> === modified file 'CameraApp/qmldir'
> --- CameraApp/qmldir	2012-09-26 11:22:50 +0000
> +++ CameraApp/qmldir	2014-06-26 09:06:29 +0000
> @@ -1,1 +1,2 @@
> +module CameraApp
>  plugin camera-qml
> 
> === added file 'CircleButton.qml'
> --- CircleButton.qml	1970-01-01 00:00:00 +0000
> +++ CircleButton.qml	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,77 @@
> +/*
> + * Copyright 2014 Canonical Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +import QtQuick 2.2
> +import QtQuick.Window 2.0
> +import Ubuntu.Components 1.0
> +
> +AbstractButton {
> +    id: button
> +
> +    property alias iconName: icon.name
> +    property bool on: true
> +    property string label: ""
> +
> +    width: units.gu(5)
> +    height: width
> +
> +    Image {
> +        anchors.fill: parent
> +        source: "assets/ubuntu_shape.svg"

Shouldn’t that asset be exported to png?

> +        opacity: button.pressed ? 0.7 : 0.3
> +        sourceSize.width: width
> +        sourceSize.height: height
> +    }
> +
> +    Icon {
> +        id: icon
> +        anchors {
> +            fill: parent
> +            margins: units.gu(1)
> +        }
> +        color: "white"
> +        opacity: button.on ? 1.0 : 0.5
> +        visible: label === ""
> +        rotation: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
> +        Behavior on rotation {
> +            RotationAnimator {
> +                duration: UbuntuAnimation.BriskDuration
> +                easing: UbuntuAnimation.StandardEasing
> +                direction: RotationAnimator.Shortest
> +            }
> +        }
> +    }
> +
> +    Label {
> +        anchors {
> +            centerIn: parent
> +        }
> +        font.weight: Font.Light
> +        fontSize: "small"
> +        color: "white"
> +        text: label
> +        opacity: button.on ? 1.0 : 0.5
> +        visible: label !== ""
> +        rotation: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
> +        Behavior on rotation {
> +            RotationAnimator {
> +                duration: UbuntuAnimation.BriskDuration
> +                easing: UbuntuAnimation.StandardEasing
> +                direction: RotationAnimator.Shortest
> +            }
> +        }
> +    }
> +}
> 
> === removed file 'CrossFadingButton.qml'
> --- CrossFadingButton.qml	2013-02-07 13:13:23 +0000
> +++ CrossFadingButton.qml	1970-01-01 00:00:00 +0000
> @@ -1,60 +0,0 @@
> -/*
> - * Copyright (C) 2012 Canonical, Ltd.
> - *
> - * This program is free software; you can redistribute it and/or modify
> - * it under the terms of the GNU General Public License as published by
> - * the Free Software Foundation; version 3.
> - *
> - * This program is distributed in the hope that it will be useful,
> - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> - * GNU General Public License for more details.
> - *
> - * You should have received a copy of the GNU General Public License
> - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> - */
> -
> -import QtQuick 2.0
> -import Ubuntu.Components 0.1
> -import "constants.js" as Const
> -
> -AbstractButton {
> -    id: button
> -    property string iconSource
> -
> -    property Image __active: icon1
> -    property Image __inactive: icon2
> -
> -    onIconSourceChanged: {
> -        if (__active && __inactive) {
> -            __inactive.source = iconSource
> -            __active.opacity = 0.0
> -            __inactive.opacity = 1.0
> -            var swap = __active
> -            __active = __inactive
> -            __inactive = swap
> -        } else icon1.source = iconSource
> -    }
> -
> -    Image {
> -        id: icon1
> -        anchors.fill: parent
> -        Behavior on opacity {
> -            NumberAnimation {
> -                duration: Const.iconFadeDuration; easing.type: Easing.InOutQuad
> -            }
> -        }
> -    }
> -
> -    Image {
> -        id: icon2
> -        anchors.fill: parent
> -        opacity: 0.0
> -        Behavior on opacity {
> -            NumberAnimation {
> -                duration: Const.iconFadeDuration; easing.type: Easing.InOutQuad
> -            }
> -        }
> -    }
> -}
> -
> 
> === removed file 'DeviceOrientation.qml'
> --- DeviceOrientation.qml	2013-06-10 12:38:15 +0000
> +++ DeviceOrientation.qml	1970-01-01 00:00:00 +0000
> @@ -1,37 +0,0 @@
> -/*
> - * Copyright 2013 Canonical Ltd.
> - *
> - * This program is free software; you can redistribute it and/or modify
> - * it under the terms of the GNU General Public License as published by
> - * the Free Software Foundation; version 3.
> - *
> - * This program is distributed in the hope that it will be useful,
> - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> - * GNU General Public License for more details.
> - *
> - * You should have received a copy of the GNU General Public License
> - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> - */
> -
> -import QtQuick 2.0
> -import QtQuick.Window 2.0
> -
> -// We must use Item element because Screen component does not works with QtObject
> -Item {
> -    property string naturalOrientation: Screen.primaryOrientation == Qt.LandscapeOrientation ? "landscape" : "portrait"
> -
> -    /* Is the device currently rotated to be in lanscape orientation ? */
> -    property bool isLandscape: Screen.orientation == Qt.LandscapeOrientation ||
> -                               Screen.orientation == Qt.InvertedLandscapeOrientation
> -
> -    /* Is the device currently rotated upside down ? */
> -    property bool isInverted: Screen.orientation == Qt.InvertedLandscapeOrientation ||
> -                              Screen.orientation == Qt.InvertedPortraitOrientation
> -
> -    /* The rotation angle in 90 degrees increments with respect to the device being in its
> -       default position */
> -    property int rotationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
> -
> -    visible: false
> -}
> 
> === removed file 'FadingButton.qml'
> --- FadingButton.qml	2013-02-07 13:13:23 +0000
> +++ FadingButton.qml	1970-01-01 00:00:00 +0000
> @@ -1,72 +0,0 @@
> -/*
> - * Copyright (C) 2012 Canonical, Ltd.
> - *
> - * This program is free software; you can redistribute it and/or modify
> - * it under the terms of the GNU General Public License as published by
> - * the Free Software Foundation; version 3.
> - *
> - * This program is distributed in the hope that it will be useful,
> - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> - * GNU General Public License for more details.
> - *
> - * You should have received a copy of the GNU General Public License
> - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> - */
> -
> -import QtQuick 2.0
> -import Ubuntu.Components 0.1
> -import "constants.js" as Const
> -
> -AbstractButton {
> -    id: button
> -    property string iconSource
> -
> -    property Image __active: icon1
> -    property Image __inactive: icon2
> -
> -    onIconSourceChanged: {
> -        if (__active && __inactive) {
> -            __inactive.source = iconSource
> -            __active.opacity = 0.0
> -        } else icon1.source = iconSource
> -    }
> -
> -    Image {
> -        id: icon1
> -        anchors.fill: parent
> -        Behavior on opacity {
> -            NumberAnimation {
> -                duration: Const.iconFadeDuration; easing.type: Easing.InOutQuad
> -            }
> -        }
> -    }
> -
> -    Image {
> -        id: icon2
> -        anchors.fill: parent
> -        opacity: 0.0
> -        Behavior on opacity {
> -            NumberAnimation {
> -                duration: Const.iconFadeDuration; easing.type: Easing.InOutQuad
> -            }
> -        }
> -    }
> -
> -    Connections {
> -        target: __active
> -        onOpacityChanged: if (__active.opacity == 0.0) __inactive.opacity = 1.0
> -    }
> -
> -    Connections {
> -        target: __inactive
> -        onOpacityChanged: {
> -            if (__inactive.opacity == 1.0) {
> -                var swap = __active
> -                __active = __inactive
> -                __inactive = swap
> -            }
> -        }
> -    }
> -}
> -
> 
> === removed file 'FlashButton.qml'
> --- FlashButton.qml	2013-02-07 13:13:23 +0000
> +++ FlashButton.qml	1970-01-01 00:00:00 +0000
> @@ -1,82 +0,0 @@
> -/*
> - * Copyright (C) 2012 Canonical, Ltd.
> - *
> - * This program is free software; you can redistribute it and/or modify
> - * it under the terms of the GNU General Public License as published by
> - * the Free Software Foundation; version 3.
> - *
> - * This program is distributed in the hope that it will be useful,
> - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> - * GNU General Public License for more details.
> - *
> - * You should have received a copy of the GNU General Public License
> - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> - */
> -
> -import QtQuick 2.0
> -import "constants.js" as Const
> -
> -Item {
> -    id: button
> -
> -    property bool flashAllowed: true
> -    property bool torchMode: false
> -    property string flashState: "off"
> -    signal clicked()
> -
> -    CrossFadingButton {
> -        id: flash
> -        anchors.fill: parent
> -        iconSource: (flashState == "off") ? "assets/flash_off.png" :
> -                    ((flashState == "on") ? "assets/flash_on.png" : "assets/flash_auto.png")
> -        onClicked: button.clicked()
> -        enabled: !torchMode
> -    }
> -
> -    CrossFadingButton {
> -        id: torch
> -        anchors.fill: parent
> -        iconSource: (flashState == "on") ? "assets/torch_on.png" : "assets/torch_off.png"
> -        enabled: torchMode
> -        onClicked: button.clicked()
> -    }
> -
> -    states: [
> -        State { name: "flash"; when: !torchMode
> -            PropertyChanges { target: flash; opacity: 1.0 }
> -            PropertyChanges { target: torch; opacity: 0.0 }
> -        },
> -        State { name: "torch"; when: torchMode
> -            PropertyChanges { target: flash; opacity: 0.0 }
> -            PropertyChanges { target: torch; opacity: 1.0 }
> -        }
> -    ]
> -
> -    transitions: [
> -        Transition { from: "flash"; to: "torch";
> -            SequentialAnimation {
> -                NumberAnimation {
> -                    target: flash; property: "opacity";
> -                    duration: Const.iconFadeDuration; easing.type: Easing.InOutQuad
> -                }
> -                NumberAnimation {
> -                    target: torch; property: "opacity";
> -                    duration: Const.iconFadeDuration; easing.type: Easing.InOutQuad
> -                }
> -            }
> -        },
> -        Transition { from: "torch"; to: "flash";
> -            SequentialAnimation {
> -                NumberAnimation {
> -                    target: torch; property: "opacity";
> -                    duration: Const.iconFadeDuration; easing.type: Easing.InOutQuad
> -                }
> -                NumberAnimation {
> -                    target: flash; property: "opacity";
> -                    duration: Const.iconFadeDuration; easing.type: Easing.InOutQuad
> -                }
> -            }
> -        }
> -    ]
> -}
> 
> === modified file 'FocusRing.qml'
> --- FocusRing.qml	2013-04-12 15:19:35 +0000
> +++ FocusRing.qml	2014-06-26 09:06:29 +0000
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright (C) 2012 Canonical, Ltd.
> + * Copyright (C) 2014 Canonical, Ltd.

Picking nits: that should probably be (C) 2012-2014 here, as we’re not giving up on copyright for the past two years, are we?

>   *
>   * This program is free software; you can redistribute it and/or modify
>   * it under the terms of the GNU General Public License as published by
> @@ -15,28 +15,39 @@
>   */
>  
>  import QtQuick 2.0
> -import Ubuntu.Components 0.1
> +import Ubuntu.Components 1.0
>  
>  Image {
> -    property var center
> +    id: focusRing
> +
> +    property point center
> +    function show() {
> +        hideTimer.restart();
> +        rotationAnimation.restart();
> +        opacity = 1.0;
> +    }
> +
> +    x: center.x - width / 2.0
> +    y: center.y - height / 2.0
> +    width: units.gu(11)
> +    height: units.gu(11)
>      source: "assets/focus_ring.png"
>  
> -    Behavior on opacity { NumberAnimation { duration: 500 } }
> -    onCenterChanged: {
> -        x = center.x - focusRing.width * 0.5
> -        y = center.y - focusRing.height * 0.5
> -        opacity = 1.0
> -        restartTimeout()
> -    }
> -
> -    function restartTimeout()
> -    {
> -        focusRingTimeout.restart()
> -    }
> +    opacity: 0.0
> +    Behavior on opacity { UbuntuNumberAnimation {} }
>  
>      Timer {
> -        id: focusRingTimeout
> -        interval: 2000
> -        onTriggered: focusRing.opacity = 0.0
> +        id: hideTimer
> +        interval: 1000
> +        onTriggered: focusRing.opacity = 0.0;
> +    }
> +
> +    UbuntuNumberAnimation {
> +        id: rotationAnimation
> +        target: focusRing
> +        property: "rotation"
> +        from: 0
> +        to: 90
> +        duration: UbuntuAnimation.SleepyDuration
>      }

Does this use a RotationAnimator under the hood? If not, would it be beneficial to refactor this code to use one?

>  }
> 
> === added file 'GalleryView.qml'
> --- GalleryView.qml	1970-01-01 00:00:00 +0000
> +++ GalleryView.qml	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,125 @@
> +/*
> + * Copyright 2014 Canonical Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +import QtQuick 2.2
> +import Ubuntu.Components 1.0
> +import Qt.labs.folderlistmodel 2.1
> +
> +Item {
> +    id: galleryView
> +
> +    signal exit
> +    property bool inView
> +    property Item currentView: state == "GRID" ? photogridView : slideshowView
> +    property var model: FolderListModel {
> +        folder: application.mediaLocation
> +        nameFilters: [ "*.png", "*.jpg", "*.jpeg", "*.PNG", "*.JPG", "*.JPEG" ]
> +        showOnlyReadable: true
> +        sortField: FolderListModel.LastModified
> +        sortReversed: true
> +        showDirs: false
> +    }
> +    property bool gridMode: false
> +
> +    function showLastPhotoTaken() {
> +        galleryView.gridMode = false;
> +        slideshowView.showLastPhotoTaken();
> +    }
> +
> +    onExit: {
> +        slideshowView.exit();
> +        photogridView.exit();
> +    }
> +
> +    OrientationHelper {
> +        visible: inView
> +
> +        SlideshowView {
> +            id: slideshowView
> +            anchors.fill: parent
> +            model: galleryView.model
> +            visible: opacity != 0.0
> +            onToggleHeader: header.toggle();
> +        }
> +
> +        PhotogridView {
> +            id: photogridView
> +            anchors.fill: parent
> +            headerHeight: header.height
> +            model: galleryView.model
> +            visible: opacity != 0.0
> +            onPhotoClicked: {
> +                slideshowView.showPhotoAtIndex(index);
> +                galleryView.gridMode = false;
> +            }
> +        }
> +
> +        GalleryViewHeader {
> +            id: header
> +            onExit: galleryView.exit()
> +            actions: currentView.actions
> +            onToggleViews: {
> +                if (!galleryView.gridMode) {
> +                    // position grid view so that the current photo in slideshow view is visible
> +                    photogridView.showPhotoAtIndex(slideshowView.currentIndex);
> +                }
> +
> +                galleryView.gridMode = !galleryView.gridMode
> +            }
> +        }
> +    }
> +
> +    onInViewChanged: if (inView) {
> +                         header.show();
> +                     }
> +
> +    state: galleryView.gridMode ? "GRID" : "SLIDESHOW"
> +    states: [
> +        State {
> +            name: "SLIDESHOW"
> +            PropertyChanges {
> +                target: slideshowView
> +                scale: 1.0
> +                opacity: 1.0
> +            }
> +            PropertyChanges {
> +                target: photogridView
> +                scale: 1.4
> +                opacity: 0.0
> +            }
> +        },
> +        State {
> +            name: "GRID"
> +            PropertyChanges {
> +                target: slideshowView
> +                scale: 1.4
> +                opacity: 0.0
> +            }
> +            PropertyChanges {
> +                target: photogridView
> +                scale: 1.0
> +                opacity: 1.0
> +            }
> +        }
> +    ]
> +
> +    transitions: [
> +        Transition {
> +            to: "*"
> +            UbuntuNumberAnimation { properties: "scale,opacity"; duration: UbuntuAnimation.SnapDuration }
> +        }
> +    ]
> +}
> 
> === added file 'GalleryViewHeader.qml'
> --- GalleryViewHeader.qml	1970-01-01 00:00:00 +0000
> +++ GalleryViewHeader.qml	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,178 @@
> +/*
> + * Copyright 2014 Canonical Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +import QtQuick 2.2
> +import Ubuntu.Components 1.0
> +import QtQuick.Layouts 1.1
> +
> +Item {
> +    id: header
> +    anchors {
> +        left: parent.left
> +        right: parent.right
> +    }
> +    y: shown ? 0 : -height
> +    Behavior on y { UbuntuNumberAnimation {} }
> +    opacity: shown ? 1.0 : 0.0
> +    Behavior on opacity { UbuntuNumberAnimation {} }
> +
> +    height: units.gu(7)
> +
> +    property bool shown: true
> +    property alias actions: actionsDrawer.actions
> +    signal exit
> +    signal toggleViews
> +
> +    function show() {
> +        shown = true;
> +    }
> +
> +    function toggle() {
> +        shown = !shown;
> +    }
> +
> +    Rectangle {
> +        anchors.fill: parent
> +        color: "black"
> +        opacity: 0.6
> +    }
> +
> +    RowLayout {
> +        anchors.fill: parent
> +        spacing: 0
> +
> +        IconButton {
> +            anchors {
> +                top: parent.top
> +                bottom: parent.bottom
> +            }
> +            width: units.gu(8)
> +            iconHeight: units.gu(3)
> +            iconWidth: iconHeight
> +            iconName: "back"
> +            iconColor: Theme.palette.normal.foregroundText
> +            onClicked: header.exit()
> +        }
> +
> +        Label {
> +            text: i18n.tr("Photo Roll")
> +            fontSize: "x-large"
> +            color: Theme.palette.normal.foregroundText
> +            Layout.fillWidth: true
> +        }
> +
> +        ImageButton {
> +            anchors {
> +                top: parent.top
> +                bottom: parent.bottom
> +            }
> +            width: units.gu(6)
> +            iconSource: "assets/gridview.png"
> +            onClicked: header.toggleViews()
> +            //            IconButton {
> +            //                iconName: "view-grid-symbolic"
> +        }
> +
> +        ImageButton {
> +            anchors {
> +                top: parent.top
> +                bottom: parent.bottom
> +            }
> +            width: units.gu(6)
> +            iconSource: "assets/options.png"
> +            visible: actionsDrawer.actions.length > 0
> +            //            IconButton {
> +            //                iconName: "contextual-menu"
> +            onClicked: actionsDrawer.opened = !actionsDrawer.opened
> +        }
> +    }
> +
> +    Item {
> +        id: actionsDrawer
> +
> +        anchors {
> +            top: parent.bottom
> +            right: parent.right
> +        }
> +        width: units.gu(20)
> +        height: childrenRect.height
> +        clip: actionsColumn.y != 0
> +
> +        function close() {
> +            opened = false;
> +        }
> +
> +        property bool opened: false
> +        property list<Action> actions
> +
> +        InverseMouseArea {
> +            onPressed: actionsDrawer.close();
> +            enabled: actionsDrawer.opened
> +        }
> +
> +        Column {
> +            id: actionsColumn
> +            anchors {
> +                left: parent.left
> +                right: parent.right
> +            }
> +            y: actionsDrawer.opened ? 0 : -height
> +            Behavior on y { UbuntuNumberAnimation {} }
> +
> +            Repeater {
> +                model: actionsDrawer.actions
> +                delegate: AbstractButton {
> +                    anchors {
> +                        left: actionsColumn.left
> +                        right: actionsColumn.right
> +                    }
> +                    height: units.gu(6)
> +
> +                    action: modelData
> +                    onClicked: actionsDrawer.close()
> +
> +                    Rectangle {
> +                        anchors.fill: parent
> +                        color: Qt.rgba(0.0, 0.0, 0.0, 0.6)
> +                    }
> +
> +                    Label {
> +                        id: label
> +                        anchors {
> +                            left: parent.left
> +                            leftMargin: units.gu(2)
> +                            verticalCenter: parent.verticalCenter
> +                        }
> +                        text: model.text
> +                        color: Theme.palette.normal.foregroundText
> +                    }
> +
> +                    Icon {
> +                        anchors {
> +                            right: parent.right
> +                            rightMargin: units.gu(2)
> +                            verticalCenter: parent.verticalCenter
> +                        }
> +                        width: height
> +                        height: label.paintedHeight
> +                        color: Theme.palette.normal.foregroundText
> +                        name: model.iconName
> +                    }
> +                }
> +            }
> +        }
> +    }
> +}
> 
> === added file 'IconButton.qml'
> --- IconButton.qml	1970-01-01 00:00:00 +0000
> +++ IconButton.qml	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,37 @@
> +/*
> + * Copyright 2014 Canonical Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +import QtQuick 2.0
> +import Ubuntu.Components 1.0
> +
> +AbstractButton {
> +    property alias iconWidth: icon.width
> +    property alias iconHeight: icon.height
> +    property alias iconName: icon.name
> +    property alias iconColor: icon.color
> +
> +    width: units.gu(4)
> +    height: units.gu(4)
> +
> +    Icon {
> +        id: icon
> +        anchors.centerIn: parent
> +        width: parent.width
> +        height: parent.height
> +        color: "white"
> +    }
> +}
> +
> 
> === renamed file 'CameraToolbarButton.qml' => 'ImageButton.qml'
> --- CameraToolbarButton.qml	2013-06-20 05:51:52 +0000
> +++ ImageButton.qml	2014-06-26 09:06:29 +0000
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright 2012 Canonical Ltd.
> + * Copyright 2014 Canonical Ltd.
>   *
>   * This program is free software; you can redistribute it and/or modify
>   * it under the terms of the GNU General Public License as published by
> @@ -15,18 +15,19 @@
>   */
>  
>  import QtQuick 2.0
> -import Ubuntu.Components 0.1
> +import Ubuntu.Components 1.0
>  
>  AbstractButton {
>      property alias iconWidth: icon.width
>      property alias iconHeight: icon.height
>      property alias iconSource: icon.source
>  
> -    width: icon.paintedWidth
> -    height: icon.paintedHeight
> +    width: icon.width
> +    height: icon.height
>  
>      Image {
>          id: icon
> +        anchors.centerIn: parent
>      }
>  }
>  
> 
> === added file 'PhotogridView.qml'
> --- PhotogridView.qml	1970-01-01 00:00:00 +0000
> +++ PhotogridView.qml	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,98 @@
> +/*
> + * Copyright 2014 Canonical Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +import QtQuick 2.2
> +import Ubuntu.Components 1.0
> +
> +Item {
> +    id: photogridView
> +
> +    property int itemsPerRow: 3
> +    property var model
> +    signal photoClicked(int index)
> +    property real headerHeight
> +    property list<Action> actions
> +
> +    function showPhotoAtIndex(index) {
> +        gridView.positionViewAtIndex(index, GridView.Center);
> +    }
> +
> +    function exit() {
> +    }
> +
> +    GridView {
> +        id: gridView
> +        anchors.fill: parent
> +        // FIXME: prevent the header from overlapping the beginning of the grid
> +        // when Qt 5.3 is landed, use property 'displayMarginBeginning' instead
> +        // cf. http://qt-project.org/doc/qt-5/qml-qtquick-gridview.html#displayMarginBeginning-prop
> +        header: Item {
> +            width: gridView.width
> +            height: headerHeight
> +        }
> +        
> +        Component.onCompleted: {
> +            // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
> +            // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
> +            var scaleFactor = units.gridUnit / 8;
> +            maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
> +            flickDeceleration = flickDeceleration * scaleFactor;
> +        }
> +
> +        cellWidth: width / photogridView.itemsPerRow
> +        cellHeight: cellWidth
> +
> +        model: photogridView.model
> +        delegate: Item {
> +            id: cellDelegate
> +            
> +            width: GridView.view.cellWidth
> +            height: GridView.view.cellHeight
> +
> +            Image {
> +                id: thumbnail
> +                property real margin: units.dp(2)
> +                anchors {
> +                    top: parent.top
> +                    topMargin: index < photogridView.itemsPerRow ? 0 : margin/2
> +                    bottom: parent.bottom
> +                    bottomMargin: margin/2
> +                    left: parent.left
> +                    leftMargin: index % photogridView.itemsPerRow == 0 ? 0 : margin/2
> +                    right: parent.right
> +                    rightMargin: index % photogridView.itemsPerRow == photogridView.itemsPerRow - 1 ? 0 : margin/2
> +                }
> +                
> +                asynchronous: true
> +                cache: false
> +                // FIXME: should use the thumbnailer instead of loading the full image and downscaling on the fly
> +                source: fileURL
> +                sourceSize {
> +                    width: width
> +                    height: height
> +                }
> +                fillMode: Image.PreserveAspectCrop
> +                opacity: status == Image.Ready ? 1.0 : 0.0
> +                Behavior on opacity { UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration} }
> +            }
> +
> +            MouseArea {
> +                anchors.fill: parent
> +                onClicked: photogridView.photoClicked(index)
> +            }
> +        }
> +    }
> +}
> 
> === modified file 'ShootButton.qml'
> --- ShootButton.qml	2013-06-20 05:51:52 +0000
> +++ ShootButton.qml	2014-06-26 09:06:29 +0000
> @@ -15,61 +15,77 @@
>   */
>  
>  import QtQuick 2.0
> -import Ubuntu.Components 0.1
> -
> -CameraToolbarButton {
> -    id: button
> -
> -    states: [
> -        State { name: "camera"
> -            PropertyChanges { target: button; iconSource: "assets/shoot.png" }
> -            PropertyChanges { target: recordOn; opacity: 0.0 }
> -            PropertyChanges { target: pulseAnimation; running: false }
> -        },
> -        State { name: "record_off"
> -            PropertyChanges { target: button; iconSource: "assets/record_off.png" }
> -            PropertyChanges { target: recordOn; opacity: 0.0 }
> -            PropertyChanges { target: pulseAnimation; running: false }
> -        },
> -        State { name: "record_on"
> -            PropertyChanges { target: button; iconSource: "assets/record_off.png" }
> -            PropertyChanges { target: recordOn; opacity: 1.0 }
> -            PropertyChanges { target: pulseAnimation; running: true }
> -        }
> -    ]
> -
> -    property int pulsePeriod: 750
> -
> -    Image {
> -        id: recordOn
> -        anchors.fill: parent
> -        source: "assets/record_on.png"
> -        Behavior on opacity { NumberAnimation { duration: pulsePeriod } }
> -    }
> -
> -    Image {
> -        id: pulse
> -        anchors.fill: parent
> -        source: "assets/record_on_pulse.png"
> -        opacity: 1.0
> -        visible: button.state != "camera"
> -
> -        SequentialAnimation on opacity  {
> -            id: pulseAnimation
> -            loops: Animation.Infinite
> -            alwaysRunToEnd: true
> -            running: false
> -
> -            PropertyAnimation {
> -                from: 1.0
> -                to: 0.0
> -                duration: pulsePeriod
> -            }
> -            PropertyAnimation {
> -                from: 0.0
> -                to: 1.0
> -                duration: pulsePeriod
> -            }
> -        }
> -    }
> +import Ubuntu.Components 1.0
> +
> +Item {
> +    id: shootButton
> +
> +    signal clicked()
> +
> +    width: icon.width
> +    height: icon.height
> +    opacity: enabled ? 1.0 : 0.5
> +
> +    MouseArea {
> +        anchors.fill: parent
> +        onClicked: shootButton.clicked()
> +    }
> +
> +    Image {
> +        id: icon
> +        anchors.centerIn: parent
> +        source: "assets/shutter_stills.png"
> +    }
> +//    states: [
> +//        State { name: "camera"
> +//            PropertyChanges { target: shootButton; iconSource: "assets/shoot.png" }
> +//            PropertyChanges { target: recordOn; opacity: 0.0 }
> +//            PropertyChanges { target: pulseAnimation; running: false }
> +//        },
> +//        State { name: "record_off"
> +//            PropertyChanges { target: shootButton; iconSource: "assets/record_off.png" }
> +//            PropertyChanges { target: recordOn; opacity: 0.0 }
> +//            PropertyChanges { target: pulseAnimation; running: false }
> +//        },
> +//        State { name: "record_on"
> +//            PropertyChanges { target: shootButton; iconSource: "assets/record_off.png" }
> +//            PropertyChanges { target: recordOn; opacity: 1.0 }
> +//            PropertyChanges { target: pulseAnimation; running: true }
> +//        }
> +//    ]
> +
> +//    property int pulsePeriod: 750
> +
> +//    Image {
> +//        id: recordOn
> +//        anchors.fill: parent
> +//        source: "assets/record_on.png"
> +//        Behavior on opacity { NumberAnimation { duration: pulsePeriod } }
> +//    }
> +
> +//    Image {
> +//        id: pulse
> +//        anchors.fill: parent
> +//        source: "assets/record_on_pulse.png"
> +//        opacity: 1.0
> +//        visible: shootButton.state != "camera"
> +
> +//        SequentialAnimation on opacity  {
> +//            id: pulseAnimation
> +//            loops: Animation.Infinite
> +//            alwaysRunToEnd: true
> +//            running: false
> +
> +//            PropertyAnimation {
> +//                from: 1.0
> +//                to: 0.0
> +//                duration: pulsePeriod
> +//            }
> +//            PropertyAnimation {
> +//                from: 0.0
> +//                to: 1.0
> +//                duration: pulsePeriod
> +//            }
> +//        }
> +//    }
>  }
> 
> === added file 'SlideshowView.qml'
> --- SlideshowView.qml	1970-01-01 00:00:00 +0000
> +++ SlideshowView.qml	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,244 @@
> +/*
> + * Copyright 2014 Canonical Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +import QtQuick 2.2
> +import Ubuntu.Components 1.0
> +import Ubuntu.Components.ListItems 1.0 as ListItems
> +import Ubuntu.Components.Popups 1.0
> +import Ubuntu.Content 0.1
> +import CameraApp 0.1
> +
> +Item {
> +    id: slideshowView
> +
> +    property var model
> +    property int currentIndex: listView.currentIndex
> +    property string currentFilePath: {
> +        var filePath = slideshowView.model.get(slideshowView.currentIndex, "filePath")
> +        if (filePath) {
> +            return filePath;
> +        } else {
> +            return "";
> +        }
> +    }
> +
> +    signal toggleHeader
> +    property list<Action> actions: [
> +                Action {
> +                    text: i18n.tr("Share")
> +                    iconName: "share"
> +                    onTriggered: PopupUtils.open(sharePopoverComponent)
> +                },
> +                Action {
> +                    text: i18n.tr("Delete")
> +                    iconName: "delete"
> +                    onTriggered: PopupUtils.open(deleteDialogComponent)
> +                }
> +            ]
> +
> +    function showPhotoAtIndex(index) {
> +        listView.positionViewAtIndex(index, ListView.Contain);
> +    }
> +
> +    function showLastPhotoTaken() {
> +        listView.positionViewAtBeginning();
> +    }
> +
> +    function exit() {
> +        if (listView.currentItem) {
> +            listView.currentItem.zoomOut();
> +        }
> +    }
> +
> +    ListView {
> +        id: listView
> +        Component.onCompleted: {
> +            // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
> +            // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
> +            var scaleFactor = units.gridUnit / 8;
> +            maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
> +            flickDeceleration = flickDeceleration * scaleFactor;
> +        }
> +
> +        anchors.fill: parent
> +        model: slideshowView.model
> +        orientation: ListView.Horizontal
> +        boundsBehavior: Flickable.StopAtBounds
> +        cacheBuffer: width
> +        highlightRangeMode: ListView.StrictlyEnforceRange
> +        spacing: units.gu(1)
> +        property real maxDimension: Math.max(width, height)
> +
> +        delegate: Item {
> +            function zoomIn(centerX, centerY) {
> +                flickable.scaleCenterX = centerX / flickable.width;
> +                flickable.scaleCenterY = centerY / flickable.height;
> +                flickable.sizeScale = 3.0;
> +            }
> +
> +            function zoomOut() {
> +                if (flickable.sizeScale != 1.0) {
> +                    flickable.scaleCenterX = flickable.contentX / flickable.width / (flickable.sizeScale - 1);
> +                    flickable.scaleCenterY = flickable.contentY / flickable.height / (flickable.sizeScale - 1);
> +                    flickable.sizeScale = 1.0;
> +                }
> +            }
> +
> +            width: ListView.view.width
> +            height: ListView.view.height
> +
> +            ActivityIndicator {
> +                anchors.centerIn: parent
> +                visible: running
> +                running: image.status != Image.Ready
> +            }
> +
> +            Flickable {
> +                id: flickable
> +                anchors.fill: parent
> +                contentWidth: media.width
> +                contentHeight: media.height
> +                contentX: (sizeScale - 1) * scaleCenterX * width
> +                contentY: (sizeScale - 1) * scaleCenterY * height
> +
> +                property real sizeScale: 1.0
> +                property real scaleCenterX: 0.0
> +                property real scaleCenterY: 0.0
> +                Behavior on sizeScale { UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration} }
> +
> +                Item {
> +                    id: media
> +
> +                    width: flickable.width * flickable.sizeScale
> +                    height: flickable.height * flickable.sizeScale
> +
> +                    Image {
> +                        id: image
> +                        anchors.fill: parent
> +                        asynchronous: true
> +                        cache: false
> +                        // FIXME: should use the thumbnailer instead of loading the full image and downscaling on the fly
> +                        source: fileURL
> +                        sourceSize {
> +                            width: listView.maxDimension
> +                            height: listView.maxDimension
> +                        }
> +                        fillMode: Image.PreserveAspectFit
> +                        opacity: status == Image.Ready ? 1.0 : 0.0
> +                        Behavior on opacity { UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration} }
> +
> +                    }
> +
> +                    Image {
> +                        id: highResolutionImage
> +                        anchors.fill: parent
> +                        asynchronous: true
> +                        cache: false
> +                        source: flickable.sizeScale > 1.0 ? fileURL : ""
> +                        sourceSize {
> +                            width: width
> +                            height: height
> +                        }
> +                        fillMode: Image.PreserveAspectFit
> +                    }
> +                }
> +
> +                MouseArea {
> +                    anchors.fill: parent
> +                    onClicked: {
> +                        slideshowView.toggleHeader();
> +                        mouse.accepted = false;
> +                    }
> +                    onDoubleClicked: {
> +                        if (flickable.sizeScale == 1.0) {
> +                            zoomIn(mouse.x, mouse.y);
> +                        } else {
> +                            zoomOut();
> +                        }
> +                    }
> +                }
> +            }
> +        }
> +    }
> +
> +
> +    Component {
> +        id: sharePopoverComponent
> +
> +        PopupBase {
> +            id: sharePopover
> +
> +            fadingAnimation: UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
> +
> +            // FIXME: ContentPeerPicker should either have a background or not, not half of one
> +            Rectangle {
> +                anchors.fill: parent
> +                color: Theme.palette.normal.overlay
> +            }
> +
> +            ContentItem {
> +                id: contentItem
> +                url: slideshowView.currentFilePath
> +            }
> +
> +            ContentPeerPicker {
> +                // FIXME: ContentPeerPicker should define an implicit size and not refer to its parent
> +                // FIXME: ContentPeerPicker should not be visible: false by default
> +                visible: true
> +                contentType: ContentType.Pictures
> +                handler: ContentHandler.Share
> +
> +                onPeerSelected: {
> +                    var transfer = peer.request();
> +                    if (transfer.state === ContentTransfer.InProgress) {
> +                        transfer.items = [ contentItem ];
> +                        transfer.state = ContentTransfer.Charged;
> +                    }
> +                    PopupUtils.close(sharePopover);
> +                }
> +                onCancelPressed: PopupUtils.close(sharePopover);
> +            }
> +        }
> +    }
> +
> +    Component {
> +        id: deleteDialogComponent
> +
> +        Dialog {
> +            id: deleteDialog
> +
> +            title: i18n.tr("Delete media?")
> +
> +            FileOperations {
> +                id: fileOperations
> +            }
> +
> +            Button {
> +                text: i18n.tr("Cancel")
> +                color: UbuntuColors.warmGrey
> +                onClicked: PopupUtils.close(deleteDialog)
> +            }
> +            Button {
> +                text: i18n.tr("Delete")
> +                color: UbuntuColors.orange
> +                onClicked: {
> +                    fileOperations.remove(slideshowView.currentFilePath);
> +                    PopupUtils.close(deleteDialog);
> +                }
> +            }
> +        }
> +    }
> +}
> 
> === modified file 'Snapshot.qml'
> --- Snapshot.qml	2013-02-19 09:06:08 +0000
> +++ Snapshot.qml	2014-06-26 09:06:29 +0000
> @@ -14,8 +14,8 @@
>   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
>   */
>  
> -import QtQuick 2.0
> -import Ubuntu.Components 0.1
> +import QtQuick 2.2
> +import Ubuntu.Components 1.0
>  
>  Item {
>      id: snapshotRoot
> @@ -25,12 +25,18 @@
>      property ViewFinderGeometry geometry
>      property bool deviceDefaultIsPortrait: true
>  
> +    function startOutAnimation() {
> +        shoot.restart()
> +    }
> +
>      Item {
>          id: container
> -        anchors.left: parent.left
> -        anchors.right: parent.right
> -        height:parent.height
> -        y: 0
> +        anchors {
> +            top: parent.top
> +            bottom: parent.bottom
> +        }
> +        width: parent.width
> +        visible: false
>  
>          Image {
>              id: snapshot
> @@ -38,33 +44,34 @@
>              rotation: snapshotRoot.orientation * -1
>  
>              asynchronous: true
> -            opacity: 0.0
>              fillMode: Image.PreserveAspectFit
>              smooth: false
> -            width: deviceDefaultIsPortrait ? geometry.height :  geometry.width
> +            width: deviceDefaultIsPortrait ? geometry.height : geometry.width
>              height: deviceDefaultIsPortrait ? geometry.width : geometry.height
>              sourceSize.width: width
>              sourceSize.height: height
> -
> -            onStatusChanged: if (status == Image.Ready) shoot.restart()
> +        }
> +
> +        Image {
> +            id: shadow
> +
> +            property bool rotated: (snapshot.rotation % 180) != 0
> +            height: rotated ? snapshot.width : snapshot.height
> +            width: units.gu(2)
> +            x: (container.width - (rotated ? snapshot.height : snapshot.width)) / 2 - width
> +            source: "assets/shadow.png"
> +            fillMode: Image.Stretch
>          }
>      }
>  
>      SequentialAnimation {
>          id: shoot
> -        PropertyAction { target: snapshot; property: "opacity"; value: 1.0 }
> -        ParallelAnimation {
> -            NumberAnimation { target: container; property: "y";
> -                              to: container.parent.height; duration: 500; easing.type: Easing.InCubic }
> -            SequentialAnimation {
> -                PauseAnimation { duration: 0 }
> -                NumberAnimation { target: snapshot; property: "opacity";
> -                                  to: 0.0; duration: 500; easing.type: Easing.InCubic }
> -            }
> -        }
>  
> -        PropertyAction { target: snapshot; property: "opacity"; value: 0.0 }
> +        PropertyAction { target: container; property: "visible"; value: true }
> +        PauseAnimation { duration: 150 }
> +        XAnimator { target: container; to: container.width + shadow.width; duration: UbuntuAnimation.BriskDuration; easing: UbuntuAnimation.StandardEasing}
>          PropertyAction { target: snapshot; property: "source"; value: ""}
> -        PropertyAction { target: container; property: "y"; value: 0 }
> +        PropertyAction { target: container; property: "visible"; value: false }
> +        PropertyAction { target: container; property: "x"; value: 0 }
>      }
>  }
> 
> === modified file 'StopWatch.qml'
> --- StopWatch.qml	2013-07-30 09:36:01 +0000
> +++ StopWatch.qml	2014-06-26 09:06:29 +0000
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright 2012 Canonical Ltd.
> + * Copyright 2014 Canonical Ltd.
>   *
>   * This program is free software; you can redistribute it and/or modify
>   * it under the terms of the GNU General Public License as published by
> @@ -15,7 +15,7 @@
>   */
>  
>  import QtQuick 2.0
> -import Ubuntu.Components 0.1
> +import Ubuntu.Components 1.0
>  
>  Item {
>      property int time: 0
> 
> === modified file 'ThinSliderStyle.qml'
> --- ThinSliderStyle.qml	2013-06-27 15:22:59 +0000
> +++ ThinSliderStyle.qml	2014-06-26 09:06:29 +0000
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright 2012 Canonical Ltd.
> + * Copyright 2014 Canonical Ltd.
>   *
>   * This program is free software; you can redistribute it and/or modify
>   * it under the terms of the GNU General Public License as published by
> @@ -15,7 +15,7 @@
>   */
>  
>  import QtQuick 2.0
> -import Ubuntu.Components 0.1
> +import Ubuntu.Components 1.0
>  
>  /*
>    This delegate is styled using the following properties:
> @@ -45,6 +45,9 @@
>      property string backgroundImage: "assets/zoom_bar at 18.png"
>      property string thumbImage: "assets/zoom_point at 18.png"
>  
> +    implicitHeight: thumbShape.height + 2.0 * thumbSpacing + units.gu(2)
> +    implicitWidth: backgroundShape.width
> +
>      Image {
>          id: backgroundShape
>          anchors {
> @@ -67,12 +70,4 @@
>          anchors.verticalCenter: backgroundShape.verticalCenter
>          source: thumbImage
>      }
> -
> -    // set styledItem's implicitHeight to the thumbShape's height
> -    // this can also control the default sensing area
> -    Binding {
> -        target: styledItem
> -        property: "implicitHeight"
> -        value: thumbShape.height + 2.0 * thumbSpacing
> -    }
>  }
> 
> === modified file 'Toolbar.qml'
> --- Toolbar.qml	2014-02-14 18:09:17 +0000
> +++ Toolbar.qml	2014-06-26 09:06:29 +0000
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright 2012 Canonical Ltd.
> + * Copyright 2014 Canonical Ltd.
>   *
>   * This program is free software; you can redistribute it and/or modify
>   * it under the terms of the GNU General Public License as published by
> @@ -16,55 +16,11 @@
>  
>  import QtQuick 2.0
>  import QtMultimedia 5.0
> -import Ubuntu.Components 0.1
> +import Ubuntu.Components 1.0
>  
>  Item {
>      id: toolbar
>  
> -    property Camera camera
> -    property int iconsRotation
> -
> -    signal recordClicked()
> -    signal zoomClicked()
> -
> -    Behavior on opacity { NumberAnimation { duration: 500 } }
> -
> -    height: middle.height
> -    property int iconWidth: units.gu(6)
> -    property int iconHeight: units.gu(5)
> -    property bool canCapture
> -
> -    function shoot() {
> -        var orientation = 90
> -        if (device.isLandscape) {
> -            if (device.naturalOrientation === "portrait") {
> -                orientation = 180
> -            } else {
> -                orientation = 0
> -            }
> -        }
> -        if (device.isInverted)
> -            orientation += 180
> -
> -        if (camera.captureMode == Camera.CaptureVideo) {
> -            if (camera.videoRecorder.recorderState == CameraRecorder.StoppedState) {
> -                camera.videoRecorder.setMetadata("Orientation", orientation)
> -                camera.videoRecorder.record()
> -            } else {
> -                camera.videoRecorder.stop()
> -                // TODO: there's no event to tell us that the video has been successfully recorder or failed,
> -                // and no preview to slide off anyway. Figure out what to do in this case.
> -            }
> -        } else {
> -            camera.imageCapture.setMetadata("Orientation", orientation)
> -            camera.imageCapture.capture()
> -        }
> -    }
> -
> -    function switchCamera() {
> -        camera.advanced.activeCameraIndex = (camera.advanced.activeCameraIndex === 0) ? 1 : 0
> -    }
> -
>      function switchFlashMode() {
>          if (flashButton.torchMode) {
>              camera.flash.mode = (flashButton.flashState == "on") ?
> @@ -75,83 +31,64 @@
>          }
>      }
>  
> -    function changeRecordMode() {
> -        if (camera.captureMode == Camera.CaptureVideo) camera.videoRecorder.stop()
> -        camera.captureMode = (camera.captureMode == Camera.CaptureVideo) ? Camera.CaptureStillImage : Camera.CaptureVideo
> -    }
> -
> -
> -    BorderImage {
> -        id: leftBackground
> +    FlashButton {
> +        id: flashButton
> +        anchors.verticalCenter: parent.verticalCenter
>          anchors.left: parent.left
> -        anchors.top: parent.top
> -        anchors.bottom: parent.bottom
> -        anchors.right: middle.left
> -        anchors.topMargin: units.dp(2)
> -        anchors.bottomMargin: units.dp(2)
> -        source: "assets/toolbar-left.sci"
> -
> -        property int iconSpacing: (width - toolbar.iconWidth * children.length) / 3
> -
> -        FlashButton {
> -            id: flashButton
> -            anchors.verticalCenter: parent.verticalCenter
> -            anchors.left: parent.left
> -            anchors.leftMargin: parent.iconSpacing
> -
> -            height: toolbar.iconHeight
> -            width: toolbar.iconWidth
> -            visible: !application.desktopMode
> -            enabled: toolbar.opacity > 0.0
> -            rotation: iconsRotation
> -
> -            Connections {
> -                target: camera.advanced
> -                onActiveCameraIndexChanged: {
> -                    if (camera.advanced.activeCameraIndex == 1) {
> -                        camera.flash.mode = Camera.FlashOff;
> -                        flashButton.previousFlashMode = Camera.FlashOff;
> -                    }
> +        anchors.leftMargin: parent.iconSpacing
> +
> +        height: toolbar.iconHeight
> +        width: toolbar.iconWidth
> +        visible: !application.desktopMode
> +        enabled: toolbar.opacity > 0.0
> +        rotation: iconsRotation
> +
> +        Connections {
> +            target: camera.advanced
> +            onActiveCameraIndexChanged: {
> +                if (camera.advanced.activeCameraIndex == 1) {
> +                    camera.flash.mode = Camera.FlashOff;
> +                    flashButton.previousFlashMode = Camera.FlashOff;
>                  }
>              }
> -
> -            torchMode: camera.captureMode == Camera.CaptureVideo
> -            flashState: { switch (camera.flash.mode) {
> -                case Camera.FlashAuto: return "auto";
> -                case Camera.FlashOn:
> -                case Camera.FlashVideoLight: return "on";
> -                case Camera.FlashOff:
> -                default: return "off"
> -            }}
> -
> -            onClicked: toolbar.switchFlashMode()
> -
> -            property variant previousFlashMode: Camera.FlashOff
> -
> -            onTorchModeChanged: {
> -                var previous = camera.flash.mode;
> -                camera.flash.mode = previousFlashMode;
> -                previousFlashMode = previous;
> -            }
> -        }
> -
> -        FadingButton {
> -            id: recordModeButton
> -            objectName: "recordModeButton"
> -            anchors.verticalCenter: parent.verticalCenter
> -            anchors.left: flashButton.right
> -            anchors.leftMargin: parent.iconSpacing
> -            rotation: iconsRotation
> -
> -            // Disabled the video recording button for V1.0 since the feature is broken, leave it enabled for desktopMode
> -            enabled: application.desktopMode
> -            opacity: 0.5
> -
> -            width: toolbar.iconWidth
> -            height: toolbar.iconHeight
> -            iconSource: camera.captureMode == Camera.CaptureVideo ? "assets/record_picture.png" : "assets/record_video.png"
> -            onClicked: toolbar.changeRecordMode()
> -        }
> +        }
> +
> +        torchMode: camera.captureMode == Camera.CaptureVideo
> +        flashState: { switch (camera.flash.mode) {
> +            case Camera.FlashAuto: return "auto";
> +            case Camera.FlashOn:
> +            case Camera.FlashVideoLight: return "on";
> +            case Camera.FlashOff:
> +            default: return "off"
> +        }}
> +
> +        onClicked: toolbar.switchFlashMode()
> +
> +        property variant previousFlashMode: Camera.FlashOff
> +
> +        onTorchModeChanged: {
> +            var previous = camera.flash.mode;
> +            camera.flash.mode = previousFlashMode;
> +            previousFlashMode = previous;
> +        }
> +    }
> +
> +    FadingButton {
> +        id: recordModeButton
> +        objectName: "recordModeButton"
> +        anchors.verticalCenter: parent.verticalCenter
> +        anchors.left: flashButton.right
> +        anchors.leftMargin: parent.iconSpacing
> +        rotation: iconsRotation
> +
> +        // Disabled the video recording button for V1.0 since the feature is broken, leave it enabled for desktopMode
> +        enabled: application.desktopMode
> +        opacity: 0.5
> +
> +        width: toolbar.iconWidth
> +        height: toolbar.iconHeight
> +        iconSource: camera.captureMode == Camera.CaptureVideo ? "assets/record_picture.png" : "assets/record_video.png"
> +        onClicked: toolbar.changeRecordMode()
>      }
>  
>      BorderImage {
> @@ -177,49 +114,4 @@
>          }
>      }
>  
> -    BorderImage {
> -        id: rightBackground
> -        anchors.right: parent.right
> -        anchors.top: parent.top
> -        anchors.bottom: parent.bottom
> -        anchors.left: middle.right
> -        anchors.topMargin: units.dp(2)
> -        anchors.bottomMargin: units.dp(2)
> -        source: "assets/toolbar-right.sci"
> -
> -        property int iconSpacing: (width - toolbar.iconWidth * children.length) / 3
> -
> -        CameraToolbarButton {
> -            id: swapButton
> -            objectName: "swapButton"
> -            anchors.verticalCenter: parent.verticalCenter
> -            anchors.right: galleryButton.left
> -            anchors.rightMargin: parent.iconSpacing
> -            rotation: iconsRotation
> -            visible: !application.desktopMode
> -            enabled: toolbar.opacity > 0.0
> -            iconWidth: toolbar.iconWidth
> -            iconHeight: toolbar.iconHeight
> -            iconSource: "assets/swap_camera.png"
> -
> -            onClicked: toolbar.switchCamera()
> -        }
> -
> -        CameraToolbarButton {
> -            id: galleryButton
> -            objectName: "galleryButton"
> -            anchors.verticalCenter: parent.verticalCenter
> -            anchors.right: parent.right
> -            anchors.rightMargin: parent.iconSpacing
> -            rotation: iconsRotation
> -            visible: !application.desktopMode
> -            enabled: toolbar.opacity > 0.0
> -
> -            iconWidth: toolbar.iconWidth
> -            iconHeight: toolbar.iconHeight
> -            iconSource: "assets/gallery.png"
> -
> -            onClicked: Qt.openUrlExternally("appid://com.ubuntu.gallery/gallery/current-user-version")
> -        }
> -    }
>  }
> 
> === added file 'ViewFinderOverlay.qml'
> --- ViewFinderOverlay.qml	1970-01-01 00:00:00 +0000
> +++ ViewFinderOverlay.qml	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,554 @@
> +/*
> + * Copyright 2014 Canonical Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +import QtQuick 2.2
> +import QtQuick.Window 2.0
> +import Ubuntu.Components 1.0
> +import QtMultimedia 5.0
> +import CameraApp 0.1
> +
> +Item {
> +    id: viewFinderOverlay
> +
> +    property Camera camera
> +    property bool touchAcquired: bottomEdge.pressed || zoomPinchArea.active
> +    property real revealProgress: bottomEdge.progress
> +
> +    function showFocusRing(x, y) {
> +        focusRing.center = Qt.point(x, y);
> +        focusRing.show();
> +    }
> +
> +    QtObject {
> +        id: settings
> +
> +        property int flashMode: Camera.FlashAuto
> +        property bool gpsEnabled: false
> +        property bool hdrEnabled: false
> +    }
> +
> +    Binding {
> +        target: camera.flash
> +        property: "mode"
> +        value: settings.flashMode
> +    }
> +
> +    Connections {
> +        target: camera.imageCapture
> +        onReadyChanged: {
> +            if (camera.imageCapture.ready) {
> +                // FIXME: this is a workaround: simply setting
> +                // camera.flash.mode to the settings value does not have any effect
> +                camera.flash.mode = Camera.FlashOff;
> +                camera.flash.mode = settings.flashMode;
> +            }
> +        }
> +    }
> +
> +    Panel {
> +        id: bottomEdge
> +        anchors {
> +            right: parent.right
> +            left: parent.left
> +            bottom: parent.bottom
> +        }
> +        height: units.gu(9)
> +        onOpenedChanged: optionValueSelector.hide()
> +
> +        property real progress: (bottomEdge.height - bottomEdge.position) / bottomEdge.height
> +        property list<ListModel> options: [
> +            ListModel {
> +                id: gpsOptionsModel
> +
> +                property string settingsProperty: "gpsEnabled"
> +                property string icon: "location"
> +                property string label: ""
> +                property bool isToggle: true
> +                property int selectedIndex: bottomEdge.indexForValue(gpsOptionsModel, settings.gpsEnabled)
> +                property bool available: true
> +
> +                ListElement {
> +                    icon: ""
> +                    label: "On"
> +                    value: true
> +                }
> +                ListElement {
> +                    icon: ""
> +                    label: "Off"
> +                    value: false
> +                }
> +            },
> +            ListModel {
> +                id: flashOptionsModel
> +
> +                property string settingsProperty: "flashMode"
> +                property string icon: ""
> +                property string label: ""
> +                property bool isToggle: false
> +                property int selectedIndex: bottomEdge.indexForValue(flashOptionsModel, settings.flashMode)
> +                property bool available: camera.advanced.hasFlash
> +
> +                ListElement {
> +                    icon: "flash-on"
> +                    label: "On"
> +                    value: Camera.FlashOn
> +                }
> +                ListElement {
> +                    icon: "flash-auto"
> +                    label: "Auto"
> +                    value: Camera.FlashAuto
> +                }
> +                ListElement {
> +                    icon: "flash-off"
> +                    label: "Off"
> +                    value: Camera.FlashOff
> +                }
> +            },
> +            ListModel {
> +                id: hdrOptionsModel
> +
> +                property string settingsProperty: "hdrEnabled"
> +                property string icon: "import-image"
> +                property string label: "HDR"
> +                property bool isToggle: true
> +                property int selectedIndex: bottomEdge.indexForValue(hdrOptionsModel, settings.hdrEnabled)
> +                property bool available: true
> +
> +                ListElement {
> +                    icon: ""
> +                    label: "On"
> +                    value: true
> +                }
> +                ListElement {
> +                    icon: ""
> +                    label: "Off"
> +                    value: false
> +                }
> +            }
> +        ]
> +
> +        function indexForValue(model, value) {
> +            var i;
> +            var element;
> +            for (i=0; i<model.count; i++) {
> +                element = model.get(i);
> +                if (element.value === value) {
> +                    return i;
> +                }
> +            }
> +
> +            return -1;
> +        }
> +
> +        Item {
> +            anchors {
> +                horizontalCenter: parent.horizontalCenter
> +                bottom: parent.top
> +            }
> +            width: indicators.width + units.gu(2)
> +            height: units.gu(3)
> +            opacity: bottomEdge.pressed || bottomEdge.opened ? 0.0 : 1.0
> +            Behavior on opacity { UbuntuNumberAnimation {} }
> +
> +            Image {
> +                anchors {
> +                    left: parent.left
> +                    right: parent.right
> +                    top: parent.top
> +                }
> +                height: parent.height * 2
> +                opacity: 0.3
> +                source: "assets/ubuntu_shape.svg"
> +                sourceSize.width: width
> +                sourceSize.height: height
> +                cache: false
> +                visible: indicators.visibleChildren.length > 1
> +            }
> +
> +            Row {
> +                id: indicators
> +
> +                anchors {
> +                    top: parent.top
> +                    bottom: parent.bottom
> +                    horizontalCenter: parent.horizontalCenter
> +                }
> +                spacing: units.gu(1)
> +
> +                Repeater {
> +                    model: bottomEdge.options
> +                    delegate: Icon {
> +                        anchors {
> +                            top: parent.top
> +                            topMargin: units.gu(0.5)
> +                            bottom: parent.bottom
> +                            bottomMargin: units.gu(0.5)
> +                        }
> +                        width: units.gu(2)
> +                        color: "white"
> +                        opacity: 0.5
> +                        name: modelData.isToggle ? modelData.icon : modelData.get(model.selectedIndex).icon
> +                        visible: modelData.available ? (modelData.isToggle ? modelData.get(model.selectedIndex).value : true) : false
> +                    }
> +                }
> +            }
> +        }
> +    }
> +
> +    Item {
> +        id: controls
> +
> +        anchors {
> +            left: parent.left
> +            right: parent.right
> +        }
> +        height: parent.height
> +        y: bottomEdge.position - bottomEdge.height
> +        opacity: 1 - bottomEdge.progress
> +        visible: opacity != 0.0
> +        enabled: visible
> +
> +        function shoot() {
> +            camera.captureInProgress = true;
> +            shootFeedback.start();
> +
> +            var orientation = Screen.angleBetween(Screen.orientation, Screen.primaryOrientation);
> +            if (Screen.primaryOrientation == Qt.PortraitOrientation) {
> +                orientation += 90;
> +            }
> +
> +            if (camera.captureMode == Camera.CaptureVideo) {
> +                if (camera.videoRecorder.recorderState == CameraRecorder.StoppedState) {
> +                    camera.videoRecorder.setMetadata("Orientation", orientation)
> +                    camera.videoRecorder.record()
> +                } else {
> +                    camera.videoRecorder.stop()
> +                    // TODO: there's no event to tell us that the video has been successfully recorder or failed,
> +                    // and no preview to slide off anyway. Figure out what to do in this case.
> +                }
> +            } else {
> +                camera.imageCapture.setMetadata("Orientation", orientation)
> +                camera.imageCapture.captureToLocation(application.mediaLocation)
> +            }
> +        }
> +
> +        function completeCapture() {
> +            print("COMPLETE CAPTURE")
> +            viewFinderOverlay.visible = true;
> +            snapshot.startOutAnimation();
> +            camera.captureInProgress = false;
> +        }
> +
> +        function switchCamera() {
> +            camera.switchInProgress = true;
> +            //                viewFinderGrab.sourceItem = viewFinder;
> +            viewFinderGrab.x = viewFinder.x;
> +            viewFinderGrab.y = viewFinder.y;
> +            viewFinderGrab.width = viewFinder.width;
> +            viewFinderGrab.height = viewFinder.height;
> +            viewFinderGrab.visible = true;
> +            viewFinderGrab.scheduleUpdate();
> +        }
> +
> +        function completeSwitch() {
> +            print("COMPLETE SWITCH")
> +            viewFinderSwitcherAnimation.restart();
> +            camera.switchInProgress = false;
> +        }
> +
> +        function changeRecordMode() {
> +            if (camera.captureMode == Camera.CaptureVideo) camera.videoRecorder.stop()
> +            camera.captureMode = (camera.captureMode == Camera.CaptureVideo) ? Camera.CaptureStillImage : Camera.CaptureVideo
> +        }
> +
> +        Connections {
> +            target: camera.imageCapture
> +            onReadyChanged: {
> +                print("READY", camera.imageCapture.ready)
> +                if (camera.imageCapture.ready) {
> +                    if (camera.captureInProgress) {
> +                        controls.completeCapture();
> +                    } else if (camera.switchInProgress) {
> +                        controls.completeSwitch();
> +                    }
> +                }
> +            }
> +        }
> +
> +        CircleButton {
> +            id: recordModeButton
> +            objectName: "recordModeButton"
> +
> +            anchors {
> +                right: shootButton.left
> +                rightMargin: units.gu(7.5)
> +                bottom: parent.bottom
> +                bottomMargin: units.gu(6)
> +            }
> +
> +            iconName: "camcorder"
> +            onClicked: controls.changeRecordMode()
> +        }
> +
> +        ShootButton {
> +            id: shootButton
> +
> +            anchors {
> +                bottom: parent.bottom
> +                // account for the bottom shadow in the asset
> +                bottomMargin: units.gu(5) - units.dp(6)
> +                horizontalCenter: parent.horizontalCenter
> +            }
> +
> +            enabled: camera.imageCapture.ready
> +            onClicked: controls.shoot()
> +            rotation: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
> +            Behavior on rotation {
> +                RotationAnimator {
> +                    duration: UbuntuAnimation.BriskDuration
> +                    easing: UbuntuAnimation.StandardEasing
> +                    direction: RotationAnimator.Shortest
> +                }
> +            }
> +        }
> +
> +        CircleButton {
> +            id: swapButton
> +            objectName: "swapButton"
> +
> +            anchors {
> +                left: shootButton.right
> +                leftMargin: units.gu(7.5)
> +                bottom: parent.bottom
> +                bottomMargin: units.gu(6)
> +            }
> +
> +            iconName: "camera-flip"
> +            onClicked: controls.switchCamera()
> +        }
> +
> +
> +        PinchArea {
> +            id: zoomPinchArea
> +            anchors {
> +                top: parent.top
> +                bottom: shootButton.top
> +                bottomMargin: units.gu(1)
> +                left: parent.left
> +                right: parent.right
> +            }
> +
> +            property real initialZoom
> +            property real minimumScale: 0.3
> +            property real maximumScale: 3.0
> +            property bool active: false
> +
> +            onPinchStarted: {
> +                active = true;
> +                initialZoom = zoomControl.value;
> +                zoomControl.show();
> +            }
> +            onPinchUpdated: {
> +                zoomControl.show();
> +                var scaleFactor = MathUtils.projectValue(pinch.scale, 1.0, maximumScale, 0.0, zoomControl.maximumValue);
> +                zoomControl.value = MathUtils.clamp(initialZoom + scaleFactor, zoomControl.minimumValue, zoomControl.maximumValue);
> +            }
> +            onPinchFinished: {
> +                active = false;
> +            }
> +
> +
> +            MouseArea {
> +                id: manualFocusMouseArea
> +                anchors.fill: parent
> +                onClicked: {
> +                    camera.manualFocus(mouse.x, mouse.y);
> +                    mouse.accepted = false;
> +                }
> +                // FIXME: calling 'isFocusPointModeSupported' fails with
> +                // "Error: Unknown method parameter type: QDeclarativeCamera::FocusPointMode"
> +                //enabled: camera.focus.isFocusPointModeSupported(Camera.FocusPointCustom)
> +                enabled: !application.desktopMode
> +            }
> +        }
> +
> +        ZoomControl {
> +            id: zoomControl
> +
> +            anchors {
> +                bottom: shootButton.top
> +                bottomMargin: units.gu(2)
> +                left: parent.left
> +                right: parent.right
> +                leftMargin: recordModeButton.x
> +                rightMargin: parent.width - (swapButton.x + swapButton.width)
> +            }
> +            maximumValue: camera.maximumZoom
> +
> +            Binding { target: camera; property: "currentZoom"; value: zoomControl.value }
> +        }
> +
> +        FocusRing {
> +            id: focusRing
> +        }
> +    }
> +
> +    Item {
> +        id: options
> +
> +        anchors {
> +            left: parent.left
> +            right: parent.right
> +            top: controls.bottom
> +        }
> +        height: optionsGrid.height
> +
> +        Grid {
> +            id: optionsGrid
> +            anchors {
> +                horizontalCenter: parent.horizontalCenter
> +            }
> +
> +            columns: 3
> +            columnSpacing: units.gu(9.5)
> +            rowSpacing: units.gu(9.5)
> +
> +            Repeater {
> +                model: bottomEdge.options
> +                delegate: CircleButton {
> +                    id: optionsButton
> +
> +                    property var model: modelData
> +
> +                    iconName: model.isToggle ? model.icon : model.get(model.selectedIndex).icon
> +                    onClicked: optionValueSelector.toggle(model, optionsButton)
> +                    on: model.isToggle ? model.get(model.selectedIndex).value : true
> +                    visible: model.available
> +                    label: model.label
> +                }
> +            }
> +        }
> +
> +        Column {
> +            id: optionValueSelector
> +            anchors {
> +                bottom: optionsGrid.top
> +                bottomMargin: units.gu(2)
> +            }
> +            width: units.gu(12)
> +
> +            function toggle(model, callerButton) {
> +                if (optionValueSelectorVisible && optionsRepeater.model === model) {
> +                    hide();
> +                } else {
> +                    show(model, callerButton);
> +                }
> +            }
> +
> +            function show(model, callerButton) {
> +                alignWith(callerButton);
> +                optionsRepeater.model = model;
> +                optionValueSelectorVisible = true;
> +            }
> +
> +            function hide() {
> +                optionValueSelectorVisible = false;
> +            }
> +
> +            function alignWith(item) {
> +                // horizontally center optionValueSelector with the center of item
> +                // if there is enough space to do so, that is as long as optionValueSelector
> +                // does not get cropped by the edge of the screen
> +                var itemX = parent.mapFromItem(item, 0, 0).x;
> +                var centeredX = itemX + item.width / 2.0 - width / 2.0;
> +                var margin = units.gu(1);
> +
> +                if (centeredX < margin) {
> +                    x = itemX;
> +                } else if (centeredX + width > item.parent.width - margin) {
> +                    x = itemX + item.width - width;
> +                } else {
> +                    x = centeredX;
> +                }
> +            }
> +
> +            visible: opacity !== 0.0
> +            onVisibleChanged: if (!visible) optionsRepeater.model = null;
> +            opacity: optionValueSelectorVisible ? 1.0 : 0.0
> +            Behavior on opacity {UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration}}
> +
> +            Repeater {
> +                id: optionsRepeater
> +
> +                delegate: AbstractButton {
> +                    id: optionDelegate
> +
> +                    anchors {
> +                        right: optionValueSelector.right
> +                        left: optionValueSelector.left
> +                    }
> +                    height: units.gu(5)
> +
> +                    property bool selected: optionsRepeater.model.selectedIndex == index
> +                    onClicked: settings[optionsRepeater.model.settingsProperty] = optionsRepeater.model.get(index).value
> +
> +                    Icon {
> +                        id: icon
> +                        anchors {
> +                            top: parent.top
> +                            bottom: parent.bottom
> +                            left: parent.left
> +                            topMargin: units.gu(1)
> +                            bottomMargin: units.gu(1)
> +                            leftMargin: units.gu(1)
> +                        }
> +                        width: height
> +                        color: "white"
> +                        opacity: optionDelegate.selected ? 1.0 : 0.5
> +                        name: model.icon
> +                    }
> +
> +                    Label {
> +                        id: label
> +                        anchors {
> +                            left: model.icon != "" ? icon.right : parent.left
> +                            leftMargin: units.gu(2)
> +                            right: parent.right
> +                            rightMargin: units.gu(2)
> +                            verticalCenter: parent.verticalCenter
> +                        }
> +
> +                        color: "white"
> +                        opacity: optionDelegate.selected ? 1.0 : 0.5
> +                        text: model.label
> +                    }
> +
> +                    Rectangle {
> +                        anchors {
> +                            left: parent.left
> +                            right: parent.right
> +                            bottom: parent.bottom
> +                        }
> +                        height: units.dp(1)
> +                        color: "white"
> +                        opacity: 0.5
> +                        visible: index !== optionsRepeater.count - 1
> +                    }
> +                }
> +            }
> +        }
> +    }
> +}
> 
> === added file 'ViewFinderView.qml'
> --- ViewFinderView.qml	1970-01-01 00:00:00 +0000
> +++ ViewFinderView.qml	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,282 @@
> +/*
> + * Copyright 2014 Canonical Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 3.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +import QtQuick 2.2
> +import QtQuick.Window 2.0
> +import Ubuntu.Components 1.0
> +import QtMultimedia 5.0
> +import CameraApp 0.1
> +import QtGraphicalEffects 1.0
> +
> +Item {
> +    id: viewFinderView
> +
> +    property bool overlayVisible: true
> +    property bool optionValueSelectorVisible: false
> +    property bool touchAcquired: viewFinderOverlay.touchAcquired
> +    property bool inView
> +    signal photoTaken
> +
> +    Camera {
> +        id: camera
> +        captureMode: Camera.CaptureStillImage
> +
> +        function manualFocus(x, y) {
> +            viewFinderOverlay.showFocusRing(x, y);
> +            autoFocusTimer.restart();
> +            focus.focusMode = Camera.FocusAuto;
> +            focus.customFocusPoint = viewFinder.mapPointToSourceNormalized(Qt.point(x, y));
> +            focus.focusPointMode = Camera.FocusPointCustom;
> +        }
> +
> +        function autoFocus() {
> +            focus.focusMode = Camera.FocusContinuous;
> +            focus.focusPointMode = Camera.FocusPointAuto;
> +        }
> +
> +        property var autoFocusTimer: Timer {
> +            interval: 5000
> +            onTriggered: camera.autoFocus();
> +        }
> +
> +        focus {
> +            focusMode: Camera.FocusContinuous
> +            focusPointMode: Camera.FocusPointAuto
> +        }
> +
> +        property AdvancedCameraSettings advanced: AdvancedCameraSettings {
> +            camera: camera
> +        }
> +
> +        Component.onCompleted: {
> +            camera.start();
> +        }
> +        
> +        /* Use only digital zoom for now as it's what phone cameras mostly use.
> +               TODO: if optical zoom is available, maximumZoom should be the combined
> +               range of optical and digital zoom and currentZoom should adjust the two
> +               transparently based on the value. */
> +        property alias currentZoom: camera.digitalZoom
> +        property alias maximumZoom: camera.maximumDigitalZoom
> +        property bool captureInProgress: false
> +        property bool switchInProgress: false
> +        onCameraStateChanged: print("STATE", cameraState)
> +        onCameraStatusChanged: print("STATUS", cameraStatus)
> +        onAvailabilityChanged: print("AVAIL", availability)
> +        
> +        imageCapture {
> +            onCaptureFailed: {
> +                console.log("Capture failed for request " + requestId + ": " + message);
> +            }
> +            onImageCaptured: {
> +                print("CAPTURED")
> +                snapshot.source = preview;
> +            }
> +            onImageSaved: {
> +                viewFinderView.photoTaken();
> +                metricPhotos.increment();
> +                console.log("Picture saved as " + path);
> +            }
> +        }
> +        
> +        videoRecorder {
> +            onRecorderStateChanged: {
> +                if (videoRecorder.recorderState === CameraRecorder.StoppedState)
> +                    metricVideos.increment()
> +            }
> +            
> +        }
> +    }
> +    
> +    Connections {
> +        target: Qt.application
> +        onActiveChanged: {
> +            if (Qt.application.active)
> +                camera.start()
> +            else if (!application.desktopMode)
> +                camera.stop()
> +        }
> +    }
> +
> +    Item {
> +        id: viewFinderSwitcher
> +        anchors.fill: parent
> +        
> +        ShaderEffectSource {
> +            id: viewFinderGrab
> +            live: false
> +            sourceItem: viewFinder
> +
> +            onScheduledUpdateCompleted: {
> +                print("SCHEDULE UPDATE COMPLETED")
> +                if (camera.switchInProgress) {
> +                    // FIXME: hack to make viewFinder invisible
> +                    // 'viewFinder.visible = false' prevents the camera switching
> +                    viewFinder.width = 1;
> +                    viewFinder.height = 1;
> +                    camera.advanced.activeCameraIndex = (camera.advanced.activeCameraIndex === 0) ? 1 : 0;
> +                    viewFinderSwitcherRotation.angle = 180;
> +                }
> +            }
> +            transform: Rotation {
> +                origin.x: viewFinderGrab.width/2
> +                origin.y: viewFinderGrab.height/2
> +                axis.x: 0; axis.y: 1; axis.z: 0
> +                angle: 180
> +            }
> +        }
> +        
> +        transform: [
> +            Scale {
> +                id: viewFinderSwitcherScale
> +                origin.x: viewFinderSwitcher.width/2
> +                origin.y: viewFinderSwitcher.height/2
> +                xScale: 1
> +                yScale: xScale
> +            },
> +            Rotation {
> +                id: viewFinderSwitcherRotation
> +                origin.x: viewFinderSwitcher.width/2
> +                origin.y: viewFinderSwitcher.height/2
> +                axis.x: 0; axis.y: 1; axis.z: 0
> +                angle: 0
> +            }
> +        ]
> +        
> +        
> +        SequentialAnimation {
> +            id: viewFinderSwitcherAnimation
> +            
> +            SequentialAnimation {
> +                ParallelAnimation {
> +                    UbuntuNumberAnimation {target: viewFinderSwitcherScale; property: "xScale"; from: 1.0; to: 0.8; duration: UbuntuAnimation.BriskDuration ; easing: UbuntuAnimation.StandardEasing}
> +                    UbuntuNumberAnimation {
> +                        target: viewFinderSwitcherRotation
> +                        property: "angle"
> +                        from: 180
> +                        to: 90
> +                        duration: UbuntuAnimation.BriskDuration
> +                        easing: UbuntuAnimation.StandardEasing
> +                    }
> +                }
> +                PropertyAction { target: viewFinder; property: "width"; value: viewFinderSwitcher.width}
> +                PropertyAction { target: viewFinder; property: "height"; value: viewFinderSwitcher.height}
> +                PropertyAction { target: viewFinderGrab; property: "visible"; value: false }
> +                ParallelAnimation {
> +                    UbuntuNumberAnimation {target: viewFinderSwitcherScale; property: "xScale"; from: 0.8; to: 1.0; duration: UbuntuAnimation.BriskDuration; easing: UbuntuAnimation.StandardEasingReverse}
> +                    UbuntuNumberAnimation {
> +                        target: viewFinderSwitcherRotation
> +                        property: "angle"
> +                        from: 90
> +                        to: 0
> +                        duration: UbuntuAnimation.BriskDuration
> +                        easing: UbuntuAnimation.StandardEasingReverse
> +                    }
> +                }
> +            }
> +        }
> +        
> +        VideoOutput {
> +            id: viewFinder
> +            
> +            x: 0
> +            y: -viewFinderGeometry.y
> +            width: parent.width
> +            height: parent.height
> +            source: camera
> +            
> +            /* This rotation need to be applied since the camera hardware in the
> +                   Galaxy Nexus phone is mounted at an angle inside the device, so the video
> +                   feed is rotated too.
> +                   FIXME: This should come from a system configuration option so that we
> +                   don't have to have a different codebase for each different device we want
> +                   to run on */
> +            orientation: Screen.primaryOrientation === Qt.PortraitOrientation  ? -90 : 0
> +            
> +            /* Convenience item tracking the real position and size of the real video feed.
> +                   Having this helps since these values depend on a lot of rules:
> +                   - the feed is automatically scaled to fit the viewfinder
> +                   - the viewfinder might apply a rotation to the feed, depending on device orientation
> +                   - the resolution and aspect ratio of the feed changes depending on the active camera
> +                   The item is also separated in a component so it can be unit tested.
> +                 */
> +            
> +            transform: Rotation {
> +                origin.x: viewFinder.width / 2
> +                origin.y: viewFinder.height / 2
> +                axis.x: 0; axis.y: 1; axis.z: 0
> +                angle: application.desktopMode ? 180 : 0
> +            }
> +
> +            ViewFinderGeometry {
> +                id: viewFinderGeometry
> +                anchors.centerIn: parent
> +
> +                cameraResolution: camera.advanced.resolution
> +                viewFinderHeight: viewFinder.height
> +                viewFinderWidth: viewFinder.width
> +                viewFinderOrientation: viewFinder.orientation
> +            }
> +
> +            Rectangle {
> +                id: shootFeedback
> +                anchors.fill: parent
> +                color: "white"
> +                visible: opacity != 0.0
> +                opacity: 0.0
> +                
> +                function start() {
> +                    shootFeedback.opacity = 1.0;
> +                    viewFinderOverlay.visible = false;
> +                    shootFeedbackAnimation.restart();
> +                }
> +                
> +                OpacityAnimator {
> +                    id: shootFeedbackAnimation
> +                    target: shootFeedback
> +                    from: 1.0
> +                    to: 0.0
> +                    duration: 50
> +                    easing: UbuntuAnimation.StandardEasing
> +                }
> +            }
> +        }
> +    }
> +
> +    FastBlur {
> +        anchors.fill: viewFinderSwitcher
> +        radius: viewFinderOverlay.revealProgress * 64
> +        source: radius !== 0 ? viewFinderSwitcher : null
> +        visible: radius !== 0
> +    }
> +
> +    ViewFinderOverlay {
> +        id: viewFinderOverlay
> +
> +        anchors.fill: parent
> +        camera: camera
> +        opacity: overlayVisible ? 1.0 : 0.0
> +        Behavior on opacity {UbuntuNumberAnimation {duration: UbuntuAnimation.SnapDuration}}
> +    }
> +    
> +    Snapshot {
> +        id: snapshot
> +        anchors.fill: parent
> +        orientation: viewFinder.orientation
> +        geometry: viewFinderGeometry
> +        deviceDefaultIsPortrait: Screen.primaryOrientation === Qt.PortraitOrientation
> +    }
> +}
> 
> === modified file 'ZoomControl.qml'
> --- ZoomControl.qml	2013-06-27 15:22:59 +0000
> +++ ZoomControl.qml	2014-06-26 09:06:29 +0000
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright 2012 Canonical Ltd.
> + * Copyright 2014 Canonical Ltd.
>   *
>   * This program is free software; you can redistribute it and/or modify
>   * it under the terms of the GNU General Public License as published by
> @@ -15,79 +15,64 @@
>   */
>  
>  import QtQuick 2.0
> -import Ubuntu.Components 0.1// as SDK
> +import Ubuntu.Components 1.0
>  
>  Item {
> -    id: zoom
> +    id: zoomControl
> +    property alias minimumValue: slider.minimumValue
>      property alias maximumValue: slider.maximumValue
>      property alias value: slider.value
> -    property real zoomStep: (slider.maximumValue - slider.minimumValue) / 20
> -    property int iconsRotation
> -
> -    AbstractButton {
> -        id: minus
> -        objectName: "zoomMinus"
> -        anchors.left: parent.left
> -        anchors.verticalCenter: parent.verticalCenter
> -        width: minusIcon.width
> -        height: minusIcon.height
> -        onClicked: slider.value = Math.max(value - zoom.zoomStep, slider.minimumValue)
> -        onPressedChanged: if (pressed) minusTimer.restart(); else minusTimer.stop();
> -        rotation: iconsRotation
> -
> -        Image {
> -            id: minusIcon
> -            anchors.centerIn: parent
> -            source: "assets/zoom_minus.png"
> -            sourceSize.height: units.gu(2)
> -            smooth: true
> -        }
> -
> -        Timer {
> -            id: minusTimer
> -            interval: 40
> -            repeat: true
> -            onTriggered: slider.value = Math.max(value - zoom.zoomStep, slider.minimumValue)
> -        }
> +
> +    function show() {
> +        zoomAutoHide.restart();
> +        shown = true;
> +    }
> +
> +    property bool shown: false
> +    visible: opacity != 0.0
> +    opacity: shown ? 1.0 : 0.0
> +    layer.enabled: fadeAnimation.running
> +    Behavior on opacity { UbuntuNumberAnimation {id: fadeAnimation} }
> +
> +    Timer {
> +        id: zoomAutoHide
> +        interval: 2000
> +        onTriggered: {
> +            zoomControl.shown = false;
> +        }
> +    }
> +
> +    implicitHeight: slider.height
> +
> +    Image {
> +        id: minusIcon
> +        anchors {
> +            left: parent.left
> +            verticalCenter: parent.verticalCenter
> +        }
> +        source: "assets/zoom_minus.png"
>      }
>  
>      Slider {
>          id: slider
>          style: ThinSliderStyle {}
> -        anchors.left: minus.right
> -        anchors.right: plus.left
> -        anchors.verticalCenter: parent.verticalCenter
> +        anchors {
> +            left: minusIcon.right
> +            right: plusIcon.left
> +        }
> +
>          live: true
>          minimumValue: 1.0 // No zoom => 1.0 zoom factor
>          value: minimumValue
> -        height: slider.implicitHeight + units.gu(4)
>      }
>  
> -    AbstractButton {
> -        id: plus
> -        objectName: "zoomPlus"
> -        anchors.right: parent.right
> -        anchors.verticalCenter: parent.verticalCenter
> -        width: plusIcon.width
> -        height: plusIcon.height
> -        onClicked: slider.value = Math.min(value + zoom.zoomStep, slider.maximumValue)
> -        onPressedChanged: if (pressed) plusTimer.restart(); else plusTimer.stop();
> -        rotation: iconsRotation
> -
> -        Image {
> -            id: plusIcon
> -            anchors.centerIn: parent
> -            source: "assets/zoom_plus.png"
> -            sourceSize.height: units.gu(2)
> -            smooth: true
> -        }
> -
> -        Timer {
> -            id: plusTimer
> -            interval: 40
> -            repeat: true
> -            onTriggered: slider.value = Math.min(value + zoom.zoomStep, slider.maximumValue)
> -        }
> +    Image {
> +        id: plusIcon
> +        anchors {
> +            right: parent.right
> +            verticalCenter: parent.verticalCenter
> +        }
> +        source: "assets/zoom_plus.png"
>      }
>  }
>  
> 
> === removed file 'assets/camera at 18.png'
> Binary files assets/camera at 18.png	2012-11-21 15:41:50 +0000 and assets/camera at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/flash_auto at 18.png'
> Binary files assets/flash_auto at 18.png	2012-11-21 15:41:50 +0000 and assets/flash_auto at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/flash_off at 18.png'
> Binary files assets/flash_off at 18.png	2012-11-21 15:41:50 +0000 and assets/flash_off at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/flash_on at 18.png'
> Binary files assets/flash_on at 18.png	2012-11-21 15:41:50 +0000 and assets/flash_on at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/focus_ring at 18.png'
> Binary files assets/focus_ring at 18.png	2012-11-21 15:41:50 +0000 and assets/focus_ring at 18.png	1970-01-01 00:00:00 +0000 differ
> === added file 'assets/focus_ring at 27.png'
> Binary files assets/focus_ring at 27.png	1970-01-01 00:00:00 +0000 and assets/focus_ring at 27.png	2014-06-26 09:06:29 +0000 differ
> === removed file 'assets/gallery at 18.png'
> Binary files assets/gallery at 18.png	2012-11-21 15:41:50 +0000 and assets/gallery at 18.png	1970-01-01 00:00:00 +0000 differ
> === added file 'assets/gridview at 12.png'
> Binary files assets/gridview at 12.png	1970-01-01 00:00:00 +0000 and assets/gridview at 12.png	2014-06-26 09:06:29 +0000 differ
> === added file 'assets/options at 12.png'
> Binary files assets/options at 12.png	1970-01-01 00:00:00 +0000 and assets/options at 12.png	2014-06-26 09:06:29 +0000 differ
> === removed file 'assets/record_off at 18.png'
> Binary files assets/record_off at 18.png	2012-11-21 15:41:50 +0000 and assets/record_off at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/record_on at 18.png'
> Binary files assets/record_on at 18.png	2012-11-21 15:41:50 +0000 and assets/record_on at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/record_on_pulse at 18.png'
> Binary files assets/record_on_pulse at 18.png	2012-11-21 15:41:50 +0000 and assets/record_on_pulse at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/record_picture at 18.png'
> Binary files assets/record_picture at 18.png	2012-11-21 15:41:50 +0000 and assets/record_picture at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/record_video at 18.png'
> Binary files assets/record_video at 18.png	2012-11-21 15:41:50 +0000 and assets/record_video at 18.png	1970-01-01 00:00:00 +0000 differ
> === added file 'assets/shadow at 27.png'
> Binary files assets/shadow at 27.png	1970-01-01 00:00:00 +0000 and assets/shadow at 27.png	2014-06-26 09:06:29 +0000 differ
> === removed file 'assets/shoot at 18.png'
> Binary files assets/shoot at 18.png	2012-11-21 15:41:50 +0000 and assets/shoot at 18.png	1970-01-01 00:00:00 +0000 differ
> === added file 'assets/shutter_stills at 27.png'
> Binary files assets/shutter_stills at 27.png	1970-01-01 00:00:00 +0000 and assets/shutter_stills at 27.png	2014-06-26 09:06:29 +0000 differ
> === removed file 'assets/swap_camera at 18.png'
> Binary files assets/swap_camera at 18.png	2012-11-21 15:41:50 +0000 and assets/swap_camera at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/toolbar-left at 18.png'
> Binary files assets/toolbar-left at 18.png	2012-11-21 15:41:50 +0000 and assets/toolbar-left at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/toolbar-left at 18.sci'
> --- assets/toolbar-left at 18.sci	2012-11-21 15:41:50 +0000
> +++ assets/toolbar-left at 18.sci	1970-01-01 00:00:00 +0000
> @@ -1,7 +0,0 @@
> -source: toolbar-left at 18.png
> -border.left: 80
> -border.right: 0
> -border.top: 30
> -border.bottom: 30
> -horizontalTileMode: Stretch
> -verticalTileMode: Stretch
> 
> === removed file 'assets/toolbar-middle at 18.png'
> Binary files assets/toolbar-middle at 18.png	2012-11-21 15:41:50 +0000 and assets/toolbar-middle at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/toolbar-middle at 18.sci'
> --- assets/toolbar-middle at 18.sci	2012-11-21 15:41:50 +0000
> +++ assets/toolbar-middle at 18.sci	1970-01-01 00:00:00 +0000
> @@ -1,7 +0,0 @@
> -source: toolbar-middle at 18.png
> -border.left: 0
> -border.right: 0
> -border.top: 41
> -border.bottom: 41
> -horizontalTileMode: Stretch
> -verticalTileMode: Stretch
> 
> === removed file 'assets/toolbar-right at 18.png'
> Binary files assets/toolbar-right at 18.png	2012-11-21 15:41:50 +0000 and assets/toolbar-right at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/toolbar-right at 18.sci'
> --- assets/toolbar-right at 18.sci	2012-11-21 15:41:50 +0000
> +++ assets/toolbar-right at 18.sci	1970-01-01 00:00:00 +0000
> @@ -1,7 +0,0 @@
> -source: toolbar-right at 18.png
> -border.right: 80
> -border.left: 0
> -border.top: 30
> -border.bottom: 30
> -horizontalTileMode: Stretch
> -verticalTileMode: Stretch
> 
> === removed file 'assets/torch_off at 18.png'
> Binary files assets/torch_off at 18.png	2012-12-04 17:16:35 +0000 and assets/torch_off at 18.png	1970-01-01 00:00:00 +0000 differ
> === removed file 'assets/torch_on at 18.png'
> Binary files assets/torch_on at 18.png	2012-12-04 17:16:35 +0000 and assets/torch_on at 18.png	1970-01-01 00:00:00 +0000 differ
> === added file 'assets/ubuntu_shape.svg'
> --- assets/ubuntu_shape.svg	1970-01-01 00:00:00 +0000
> +++ assets/ubuntu_shape.svg	2014-06-26 09:06:29 +0000
> @@ -0,0 +1,74 @@
> +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
> +<!-- Created with Inkscape (http://www.inkscape.org/) -->
> +
> +<svg
> +   xmlns:dc="http://purl.org/dc/elements/1.1/"
> +   xmlns:cc="http://creativecommons.org/ns#"
> +   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
> +   xmlns:svg="http://www.w3.org/2000/svg"
> +   xmlns="http://www.w3.org/2000/svg"
> +   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
> +   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
> +   width="512"
> +   height="476"
> +   id="svg3336"
> +   version="1.1"
> +   inkscape:version="0.48.3.1 r9886"
> +   sodipodi:docname="New document 2">
> +  <defs
> +     id="defs3338" />
> +  <sodipodi:namedview
> +     id="base"
> +     pagecolor="#ffffff"
> +     bordercolor="#666666"
> +     borderopacity="1.0"
> +     inkscape:pageopacity="0.0"
> +     inkscape:pageshadow="2"
> +     inkscape:zoom="0.35"
> +     inkscape:cx="375"
> +     inkscape:cy="520"
> +     inkscape:document-units="px"
> +     inkscape:current-layer="layer1"
> +     showgrid="false"
> +     inkscape:window-width="1920"
> +     inkscape:window-height="1029"
> +     inkscape:window-x="0"
> +     inkscape:window-y="24"
> +     inkscape:window-maximized="1" />
> +  <metadata
> +     id="metadata3341">
> +    <rdf:RDF>
> +      <cc:Work
> +         rdf:about="">
> +        <dc:format>image/svg+xml</dc:format>
> +        <dc:type
> +           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
> +        <dc:title></dc:title>
> +      </cc:Work>
> +    </rdf:RDF>
> +  </metadata>
> +  <g
> +     inkscape:label="Layer 1"
> +     inkscape:groupmode="layer"
> +     id="layer1"
> +     transform="translate(0,-576.36218)">
> +    <g
> +       transform="matrix(3.5555556,0,0,3.5522388,0,-2685.8796)"
> +       id="layer1-3"
> +       inkscape:label="Layer 1"
> +       style="fill:#000000;display:inline">
> +      <g
> +         style="fill:#000000"
> +         inkscape:label="Layer 1"
> +         id="layer1-2"
> +         transform="translate(0,9.99998)">
> +        <path
> +           sodipodi:nodetypes="sssssssss"
> +           inkscape:connector-curvature="0"
> +           id="path3774"
> +           d="m 46.702703,908.3622 50.594594,0 C 138.16216,908.3622 144,914.2 144,955.06487 l 0,40.59483 c 0,40.8647 -5.83784,46.7025 -46.702703,46.7025 l -50.594594,0 C 5.8378378,1042.3622 0,1036.5244 0,995.6597 L 0,955.06487 C 0,914.2 5.8378378,908.3622 46.702703,908.3622 z"
> +           style="fill:#000000;fill-opacity:1;stroke:none;display:inline" />
> +      </g>
> +    </g>
> +  </g>
> +</svg>
> 
> === modified file 'assets/zoom_point at 18.png'
> Binary files assets/zoom_point at 18.png	2013-06-27 00:31:29 +0000 and assets/zoom_point at 18.png	2014-06-26 09:06:29 +0000 differ
> === modified file 'camera-app.qml'
> --- camera-app.qml	2014-05-17 07:42:35 +0000
> +++ camera-app.qml	2014-06-26 09:06:29 +0000
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright 2012 Canonical Ltd.
> + * Copyright 2014 Canonical Ltd.
>   *
>   * This program is free software; you can redistribute it and/or modify
>   * it under the terms of the GNU General Public License as published by
> @@ -14,44 +14,43 @@
>   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
>   */
>  
> -import QtQuick 2.0
> -import Ubuntu.Components 0.1
> +import QtQuick 2.2
> +import QtQuick.Window 2.0
> +import Ubuntu.Components 1.0
>  import Ubuntu.Unity.Action 1.1 as UnityActions
> -import QtMultimedia 5.0
> -import CameraApp 0.1
> -import QtQuick.Window 2.0
>  import UserMetrics 0.1
>  
> -Rectangle {
> +Item {
>      id: main
>      objectName: "main"
> -    width: application.desktopMode ? units.gu(120) : (device.naturalOrientation == "portrait" ? units.gu(40) : units.gu(80))
> -    height: application.desktopMode ? units.gu(60) : (device.naturalOrientation == "portrait" ? units.gu(80) : units.gu(40))
> -    color: "#252423"
> +    width: units.gu(40)
> +    height: units.gu(71)
> +//    width: application.desktopMode ? units.gu(120) : (Screen.primaryOrientation === Qt.PortraitOrientation ? units.gu(40) : units.gu(80))
> +//    height: application.desktopMode ? units.gu(60) : (Screen.primaryOrientation === Qt.PortraitOrientation ? units.gu(80) : units.gu(40))
>  
>      UnityActions.ActionManager {
>          actions: [
> -            UnityActions.Action {
> -                text: i18n.tr("Flash")
> -                keywords: i18n.tr("Light;Dark")
> -                onTriggered: toolbar.switchFlashMode()
> -            },
> -            UnityActions.Action {
> -                text: i18n.tr("Flip Camera")
> -                keywords: i18n.tr("Front Facing;Back Facing")
> -                onTriggered: toolbar.switchCamera()
> -            },
> -            UnityActions.Action {
> -                text: i18n.tr("Shutter")
> -                keywords: i18n.tr("Take a Photo;Snap;Record")
> -                onTriggered: toolbar.shoot()
> -            },
> -            UnityActions.Action {
> -                text: i18n.tr("Mode")
> -                keywords: i18n.tr("Stills;Video")
> -                onTriggered: toolbar.changeRecordMode()
> -                enabled: false
> -            },
> +//            UnityActions.Action {
> +//                text: i18n.tr("Flash")
> +//                keywords: i18n.tr("Light;Dark")
> +//                onTriggered: toolbar.switchFlashMode()
> +//            },
> +//            UnityActions.Action {
> +//                text: i18n.tr("Flip Camera")
> +//                keywords: i18n.tr("Front Facing;Back Facing")
> +//                onTriggered: toolbar.switchCamera()
> +//            },
> +//            UnityActions.Action {
> +//                text: i18n.tr("Shutter")
> +//                keywords: i18n.tr("Take a Photo;Snap;Record")
> +//                onTriggered: toolbar.shoot()
> +//            },
> +//            UnityActions.Action {
> +//                text: i18n.tr("Mode")
> +//                keywords: i18n.tr("Stills;Video")
> +//                onTriggered: toolbar.changeRecordMode()
> +//                enabled: false
> +//            },
>              UnityActions.Action {
>                  text: i18n.tr("White Balance")
>                  keywords: i18n.tr("Lighting Condition;Day;Cloudy;Inside")
> @@ -65,304 +64,108 @@
>  
>      Component.onCompleted: {
>          i18n.domain = "camera-app";
> -        camera.start();
> -    }
> -
> -    DeviceOrientation {
> -        id: device
> -    }
> -
> -    Camera {
> -        id: camera
> -        flash.mode: Camera.FlashOff
> -        captureMode: Camera.CaptureStillImage
> -        focus.focusMode: Camera.FocusAuto //TODO: Not sure if Continuous focus is better here
> -        focus.focusPointMode: application.desktopMode ? Camera.FocusPointAuto : (focusRing.opacity > 0 ? Camera.FocusPointCustom : Camera.FocusPointAuto)
> -
> -        property AdvancedCameraSettings advanced: AdvancedCameraSettings {
> -            camera: camera
> -        }
> -
> -        /* Use only digital zoom for now as it's what phone cameras mostly use.
> -           TODO: if optical zoom is available, maximumZoom should be the combined
> -           range of optical and digital zoom and currentZoom should adjust the two
> -           transparently based on the value. */
> -        property alias currentZoom: camera.digitalZoom
> -        property alias maximumZoom: camera.maximumDigitalZoom
> -
> -        imageCapture {
> -            onCaptureFailed: {
> -                console.log("Capture failed for request " + requestId + ": " + message);
> -                focusRing.opacity = 0.0;
> -            }
> -            onImageCaptured: {
> -                focusRing.opacity = 0.0;
> -                snapshot.source = preview;
> -            }
> -            onImageSaved: {
> -                metricPhotos.increment()
> -                console.log("Picture saved as " + path)
> -            }
> -        }
> -
> -        videoRecorder {
> -            onRecorderStateChanged: {
> -                if (videoRecorder.recorderState === CameraRecorder.StoppedState)
> -                    metricVideos.increment()
> -            }
> -
> -        }
> -    }
> -
> -    Connections {
> -        target: Qt.application
> -        onActiveChanged: {
> -            if (Qt.application.active)
> -                camera.start()
> -            else if (!application.desktopMode)
> -                camera.stop()
> -        }
> -    }
> -
> -    VideoOutput {
> -        id: viewFinder
> -
> -        property bool shouldBeCentered: device.isLandscape ||
> -                                        ((viewFinder.width > viewFinderGeometry.width) &&
> -                                         device.naturalOrientation === "portrait")
> -        property real anchoredY: viewFinderGeometry.y * (device.isInverted ? +1 : -1)
> -        property real anchoredX: viewFinderGeometry.x * (device.isInverted ? +1 : -1)
> -
> -        x: viewFinder.shouldBeCentered ? 0 : viewFinder.anchoredX
> -        y: viewFinder.shouldBeCentered || device.naturalOrientation === "landscape" ?
> -           0 : viewFinder.anchoredY
> -        width: parent.width
> -        height: parent.height
> -        source: camera
> -
> -        /* This rotation need to be applied since the camera hardware in the
> -           Galaxy Nexus phone is mounted at an angle inside the device, so the video
> -           feed is rotated too.
> -           FIXME: This should come from a system configuration option so that we
> -           don't have to have a different codebase for each different device we want
> -           to run on */
> -        orientation: device.naturalOrientation === "portrait"  ? -90 : 0
> -
> -        /* Convenience item tracking the real position and size of the real video feed.
> -           Having this helps since these values depend on a lot of rules:
> -           - the feed is automatically scaled to fit the viewfinder
> -           - the viewfinder might apply a rotation to the feed, depending on device orientation
> -           - the resolution and aspect ratio of the feed changes depending on the active camera
> -           The item is also separated in a component so it can be unit tested.
> -         */
> -
> -        transform: Rotation {
> -            origin.x: viewFinder.width / 2
> -            origin.y: viewFinder.height / 2
> -            axis.x: 0; axis.y: 1; axis.z: 0
> -            angle: application.desktopMode ? 180 : 0
> -        }
> -
> -        ViewFinderGeometry {
> -            id: viewFinderGeometry
> -            anchors.centerIn: parent
> -
> -            cameraResolution: camera.advanced.resolution
> -            viewFinderHeight: viewFinder.height
> -            viewFinderWidth: viewFinder.width
> -            viewFinderOrientation: viewFinder.orientation
> -
> -            Item {
> -                id: itemScale
> -                visible: false
> -            }
> -
> -            PinchArea {
> -                id: area
> -
> -                state: device.isLandscape ? "split" : "joined"
> -                anchors.left: viewFinderGeometry.left
> -                anchors.right: viewFinderGeometry.right
> -
> -                pinch.minimumScale: 0.0
> -                pinch.maximumScale: camera.maximumZoom
> -                pinch.target: itemScale
> -
> -                states: [
> -                    State {
> -                        name: "joined"
> -                        PropertyChanges {
> -                            target: area
> -                            height: zoomControl.y
> -                        }
> -                        AnchorChanges {
> -                            target: area;
> -                            anchors.top: viewFinderGeometry.top
> -                        }
> -                    },
> -                    State {
> -                        name: "split"
> -                        PropertyChanges {
> -                            target: area
> -                            y: device.isInverted ?  zoomControl.height : toolbar.height
> -                            height: viewFinderGeometry.height - zoomControl.height - toolbar.height
> -                        }
> -                        AnchorChanges {
> -                            target: area;
> -                            anchors.top: undefined
> -                        }
> -                    }
> -                ]
> -
> -                onPinchStarted: {
> -                    if (!application.desktopMode)
> -                        focusRing.center = main.mapFromItem(area, pinch.center.x, pinch.center.y);
> -                }
> -
> -                onPinchFinished: {
> -                    if (!application.desktopMode) {
> -                        focusRing.restartTimeout()
> -                        var center = pinch.center
> -                        var focusPoint = viewFinder.mapPointToSourceNormalized(pinch.center);
> -                        camera.focus.customFocusPoint = focusPoint;
> -                    }
> -                }
> -
> -                onPinchUpdated: {
> -                    if (!application.desktopMode) {
> -                        focusRing.center = main.mapFromItem(area, pinch.center.x, pinch.center.y);
> -                        camera.currentZoom = itemScale.scale
> -                    }
> -                }
> -
> -                MouseArea {
> -                    id: mouseArea
> -
> -                    anchors.fill: parent
> -
> -                    onPressed: {
> -                        if (!application.desktopMode && !area.pinch.active)
> -                            focusRing.center = main.mapFromItem(area, mouse.x, mouse.y);
> -                    }
> -
> -                    onReleased:  {
> -                        if (!application.desktopMode && !area.pinch.active) {
> -                            var focusPoint = viewFinder.mapPointToSourceNormalized(Qt.point(mouse.x, mouse.y))
> -
> -                            focusRing.restartTimeout()
> -                            camera.focus.customFocusPoint = focusPoint;
> -                        }
> -                    }
> -
> -                    drag {
> -                        target: application.desktopMode ? "" : focusRing
> -                        minimumY: area.y - focusRing.height / 2
> -                        maximumY: area.y + area.height - focusRing.height / 2
> -                        minimumX: area.x - focusRing.width / 2
> -                        maximumX: area.x + area.width - focusRing.width / 2
> -                    }
> -
> -                }
> -            }
> -
> -            Snapshot {
> -                id: snapshot
> -                anchors.left: parent.left
> -                anchors.right: parent.right
> -                height: parent.height
> -                y: 0
> -                orientation: viewFinder.orientation
> -                geometry: viewFinderGeometry
> -                deviceDefaultIsPortrait: device.naturalOrientation === "portrait"
> -            }
> -        }
> -    }
> -
> -    FocusRing {
> -        id: focusRing
> -        height: units.gu(13)
> -        width: units.gu(13)
> -        opacity: 0.0
> -    }
> -
> -    Item {
> -        id: controlsArea
> -        anchors.centerIn: parent
> -
> -        height: (device.naturalOrientation == "portrait") ? parent.height : parent.width
> -        width: (device.naturalOrientation == "portrait") ? parent.width : parent.height
> -
> -        rotation: device.naturalOrientation == "landscape" ?
> -                      ((device.isInverted) ? 90 : -90) :
> -                      (!device.isLandscape ? (device.isInverted ? 180 : 0) :
> -                                             (device.isInverted ? 0 : 180))
> -
> -        state: device.isLandscape ? "split" : "joined"
> -        states: [
> -            State { name: "joined"
> -                AnchorChanges { target: zoomControl; anchors.bottom: toolbar.top }
> -                AnchorChanges {
> -                    target: stopWatch
> -                    anchors.top: parent.top
> -                    anchors.horizontalCenter: parent.horizontalCenter
> -                }
> -            },
> -            State { name: "split"
> -                AnchorChanges { target: zoomControl; anchors.top: parent.top }
> -                AnchorChanges {
> -                    target: stopWatch
> -                    anchors.right: parent.right
> -                    anchors.verticalCenter: parent.verticalCenter
> -                }
> -            }
> -        ]
> -
> -        ZoomControl {
> -            id: zoomControl
> -            maximumValue: camera.maximumZoom
> -            height: units.gu(4.5)
> -
> -            anchors.left: parent.left
> -            anchors.right: parent.right
> -            anchors.leftMargin: units.gu(0.75)
> -            anchors.rightMargin: units.gu(0.75)
> -            anchors.bottomMargin: controlsArea.state == "split" ? units.gu(3.25) : units.gu(0.5)
> -            anchors.topMargin: controlsArea.state == "split" ? units.gu(3.25) : units.gu(0.5)
> -
> -            visible: camera.maximumZoom > 1
> -
> -            // Create a two way binding between the zoom control value and the actual camera zoom,
> -            // so that they can stay in sync when the zoom is changed from the UI or from the hardware
> -            Binding { target: zoomControl; property: "value"; value: camera.currentZoom }
> -            Binding { target: camera; property: "currentZoom"; value: zoomControl.value }
> -
> -            iconsRotation: device.rotationAngle - controlsArea.rotation
> -        }
> -
> -        Toolbar {
> -            id: toolbar
> -
> -            anchors.bottom: parent.bottom
> -            anchors.left: parent.left
> -            anchors.right: parent.right
> -            anchors.bottomMargin: units.gu(1)
> -            anchors.leftMargin: units.gu(1)
> -            anchors.rightMargin: units.gu(1)
> -
> -            camera: camera
> -            canCapture: camera.imageCapture.ready && !snapshot.sliding
> -            iconsRotation: device.rotationAngle - controlsArea.rotation
> -        }
> -
> -        StopWatch {
> -            id: stopWatch
> -            opacity: camera.videoRecorder.recorderState == CameraRecorder.StoppedState ? 0.0 : 1.0
> -            time: camera.videoRecorder.duration / 1000
> -            labelRotation: device.rotationAngle - controlsArea.rotation
> -            anchors.topMargin: units.gu(2)
> -            anchors.rightMargin: units.gu(2)
> -        }
> -    }
> +    }
> +
> +
> +    Flickable {
> +        id: viewSwitcher
> +        anchors.fill: parent
> +        flickableDirection: Flickable.HorizontalFlick
> +        boundsBehavior: Flickable.StopAtBounds
> +        contentWidth: contentItem.childrenRect.width
> +        contentHeight: contentItem.childrenRect.height
> +        interactive: !viewFinderView.touchAcquired
> +
> +        Component.onCompleted: {
> +            // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
> +            // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
> +            var scaleFactor = units.gridUnit / 8;
> +            maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
> +            flickDeceleration = flickDeceleration * scaleFactor;
> +        }
> +
> +        property bool settling: false
> +        property bool switching: false
> +        property real settleVelocity: units.dp(5000)
> +
> +        function settle() {
> +            settling = true;
> +            var velocity;
> +            if (horizontalVelocity < 0 || (horizontalVelocity == 0 && visibleArea.xPosition <= 0.25)) {
> +                // FIXME: compute velocity better to ensure it reaches rest position (maybe in a constant time)
> +                velocity = settleVelocity;
> +            } else {
> +                velocity = -settleVelocity;
> +            }
> +
> +            flick(velocity, 0);
> +        }
> +
> +        function switchToViewFinder() {
> +            cancelFlick();
> +            switching = true;
> +            flick(settleVelocity, 0);
> +        }
> +
> +        onMovementEnded: {
> +            // go to a rest position as soon as user stops interacting with the Flickable
> +            settle();
> +        }
> +
> +        onFlickStarted: {
> +            // cancel user triggered flicks
> +            if (!settling && !switching) {
> +                cancelFlick();
> +            }
> +        }
> +
> +        onFlickingHorizontallyChanged: {
> +            // use flickingHorizontallyChanged instead of flickEnded because flickEnded
> +            // is not called when a flick is interrupted by the user
> +            if (!flickingHorizontally) {
> +                if (settling) {
> +                    settling = false;
> +                }
> +                if (switching) {
> +                    switching = true;
> +                }
> +            }
> +        }
> +
> +        onHorizontalVelocityChanged: {
> +            // FIXME: this is a workaround for the lack of notification when
> +            // the user manually interrupts a flick by pressing and releasing
> +            if (horizontalVelocity == 0 && !atXBeginning && !atXEnd && !settling && !moving) {
> +                settle();
> +            }
> +        }
> +
> +        Row {
> +            anchors {
> +                top: parent.top
> +                bottom: parent.bottom
> +            }
> +            spacing: units.gu(1)
> +
> +            ViewFinderView {
> +                id: viewFinderView
> +                width: viewSwitcher.width
> +                height: viewSwitcher.height
> +                overlayVisible: !viewSwitcher.moving && !viewSwitcher.flicking
> +                inView: !viewSwitcher.atXEnd
> +                onPhotoTaken: galleryView.showLastPhotoTaken();
> +            }
> +
> +            GalleryView {
> +                id: galleryView
> +                width: viewSwitcher.width
> +                height: viewSwitcher.height
> +                inView: !viewSwitcher.atXBeginning
> +                onExit: viewSwitcher.switchToViewFinder()
> +            }
> +        }
> +    }
> +
>  
>      Metric {
>          id: metricPhotos
> 
> === modified file 'cameraapplication.cpp'
> --- cameraapplication.cpp	2014-03-28 02:27:15 +0000
> +++ cameraapplication.cpp	2014-06-26 09:06:29 +0000
> @@ -24,6 +24,8 @@
>  #include <QtCore/QDebug>
>  #include <QtCore/QStringList>
>  #include <QtCore/QLibrary>
> +#include <QtCore/QStandardPaths>
> +#include <QtCore/QDir>
>  #include <QDate>
>  #include <QQmlContext>
>  #include <QQmlEngine>
> @@ -86,6 +88,7 @@
>      m_view.reset(new QQuickView());
>      m_view->setResizeMode(QQuickView::SizeRootObjectToView);
>      m_view->setTitle("Camera");
> +    m_view->setColor("black");
>      m_view->rootContext()->setContextProperty("application", this);
>      m_view->engine()->setBaseUrl(QUrl::fromLocalFile(cameraAppDirectory()));
>      if (isClick()) {
> @@ -104,3 +107,11 @@
>  
>      return true;
>  }
> +
> +QString CameraApplication::mediaLocation() const
> +{
> +    QString location = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).at(0) + "/camera";
> +    QDir dir;
> +    dir.mkpath(location);
> +    return location;
> +}
> 
> === modified file 'cameraapplication.h'
> --- cameraapplication.h	2014-01-24 21:05:20 +0000
> +++ cameraapplication.h	2014-06-26 09:06:29 +0000
> @@ -29,12 +29,14 @@
>  {
>      Q_OBJECT
>      Q_PROPERTY(bool desktopMode READ isDesktopMode CONSTANT)
> +    Q_PROPERTY(QString mediaLocation READ mediaLocation CONSTANT)
>  
>  public:
>      CameraApplication(int &argc, char **argv);
>      virtual ~CameraApplication();
>      bool setup();
>      bool isDesktopMode() const;
> +    QString mediaLocation() const;
>  
>  private:
>      QScopedPointer<QQuickView> m_view;
> 
> === removed file 'constants.js'
> --- constants.js	2013-02-07 13:13:23 +0000
> +++ constants.js	1970-01-01 00:00:00 +0000
> @@ -1,19 +0,0 @@
> -/*
> - * Copyright 2012 Canonical Ltd.
> - *
> - * This program is free software; you can redistribute it and/or modify
> - * it under the terms of the GNU General Public License as published by
> - * the Free Software Foundation; version 3.
> - *
> - * This program is distributed in the hope that it will be useful,
> - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> - * GNU General Public License for more details.
> - *
> - * You should have received a copy of the GNU General Public License
> - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> - */
> -
> -.pragma library
> -
> -var iconFadeDuration = 300;
> 
> === modified file 'debian/camera-app.install'
> --- debian/camera-app.install	2014-02-13 17:32:29 +0000
> +++ debian/camera-app.install	2014-06-26 09:06:29 +0000
> @@ -1,7 +1,6 @@
>  /usr/lib/*/qt5/qml/*
>  usr/bin/camera-app
>  usr/share/applications/camera-app.desktop
> -usr/share/camera-app/*.js
>  usr/share/camera-app/*.qml
>  usr/share/camera-app/assets/*
>  usr/share/icons
> 
> === modified file 'debian/control'
> --- debian/control	2014-05-09 16:03:59 +0000
> +++ debian/control	2014-06-26 09:06:29 +0000
> @@ -16,6 +16,7 @@
>                 qtdeclarative5-ubuntu-ui-toolkit-plugin,
>                 qtdeclarative5-unity-action-plugin (>= 1.1.0),
>                 qtdeclarative5-usermetrics0.1,
> +               qtdeclarative5-ubuntu-content0.1,
>                 qtmultimedia5-dev,
>                 libusermetricsinput1-dev,
>                 gettext,
> 
> === modified file 'tests/autopilot/CMakeLists.txt'
> --- tests/autopilot/CMakeLists.txt	2014-05-06 18:41:31 +0000
> +++ tests/autopilot/CMakeLists.txt	2014-06-26 09:06:29 +0000
> @@ -2,7 +2,8 @@
>      OUTPUT_VARIABLE PYTHON_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
>  
>  if(INSTALL_TESTS)
> -install(DIRECTORY ${AUTOPILOT_DIR}
> -    DESTINATION ${PYTHON_PACKAGE_DIR}
> -    )
> +message(WARNING ${PYTHON_PACKAGE_DIR})
> +#install(DIRECTORY ${AUTOPILOT_DIR}
> +#    DESTINATION ${PYTHON_PACKAGE_DIR}
> +#    )
>  endif(INSTALL_TESTS)
> 


-- 
https://code.launchpad.net/~fboucault/camera-app/ui_rewrite/+merge/224451
Your team Ubuntu Phablet Team is requested to review the proposed merge of lp:~fboucault/camera-app/ui_rewrite into lp:camera-app.



More information about the Ubuntu-reviews mailing list