diff --git a/UI/Templates/Themes/mobile/CommonUI/UIxPageFrame.wox b/UI/Templates/Themes/mobile/CommonUI/UIxPageFrame.wox
new file mode 100644
index 000000000..90101fb0b
--- /dev/null
+++ b/UI/Templates/Themes/mobile/CommonUI/UIxPageFrame.wox
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox
new file mode 100644
index 000000000..fa19f3ded
--- /dev/null
+++ b/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UI/Templates/Themes/mobile/MainUI/SOGoRootPage.wox b/UI/Templates/Themes/mobile/MainUI/SOGoRootPage.wox
new file mode 100644
index 000000000..1e743cdc8
--- /dev/null
+++ b/UI/Templates/Themes/mobile/MainUI/SOGoRootPage.wox
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UI/WebServerResources/js/Common/ui-mobile.js b/UI/WebServerResources/js/Common/ui-mobile.js
new file mode 100644
index 000000000..96fd8c2cd
--- /dev/null
+++ b/UI/WebServerResources/js/Common/ui-mobile.js
@@ -0,0 +1,41 @@
+/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* JavaScript for SOGoContacts */
+
+(function() {
+ 'use strict';
+
+ /* Constructor */
+ function Dialog() {
+ }
+
+ Dialog.alert = function(title, content) {
+ var alertPopup = this.$ionicPopup.alert({
+ title: title,
+ template: content
+ });
+ // alertPopup.then(function(res) {
+ // console.log('Thank you for not eating my delicious ice cream cone');
+ // });
+ };
+
+ Dialog.$factory = ['$ionicPopup', function($ionicPopup) {
+ angular.extend(Dialog, { $ionicPopup: $ionicPopup });
+
+ return Dialog; // return constructor
+ }];
+
+ angular.module('SOGo.UIMobile', ['ionic'])
+
+ .factory('sgDialog', Dialog.$factory);
+ // angular.module('SOGo').factory('sgDialog', Dialog);
+
+ // Dialog.prototype.alert = function(title, content) {
+ // var alertPopup = $ionicPopup.alert({
+ // title: title,
+ // template: content
+ // });
+ // alertPopup.then(function(res) {
+ // console.log('Thank you for not eating my delicious ice cream cone');
+ // });
+ // };
+})();
diff --git a/UI/WebServerResources/js/mobile/ContactsUI.js b/UI/WebServerResources/js/mobile/ContactsUI.js
new file mode 100644
index 000000000..00a234fe5
--- /dev/null
+++ b/UI/WebServerResources/js/mobile/ContactsUI.js
@@ -0,0 +1,167 @@
+/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* JavaScript for SOGoContacts (mobile) */
+
+(function() {
+ 'use strict';
+
+ angular.module('SOGo.Common', []);
+
+ angular.module('SOGo.Contacts', ['ionic', 'SOGo.Common', 'SOGo.Contacts'])
+
+ .constant('sgSettings', {
+ 'baseURL': '/SOGo/so/francis/Contacts'
+ })
+
+ .run(function($ionicPlatform) {
+ $ionicPlatform.ready(function() {
+ // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
+ // for form inputs)
+ if(window.cordova && window.cordova.plugins.Keyboard) {
+ cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
+ }
+ if(window.StatusBar) {
+ // org.apache.cordova.statusbar required
+ StatusBar.styleDefault();
+ }
+ });
+ })
+
+ .config(function($stateProvider, $urlRouterProvider) {
+ $stateProvider
+ .state('app', {
+ url: "/app",
+ abstract: true,
+ templateUrl: "menu.html",
+ controller: 'AppCtrl'
+ })
+
+ .state('app.addressbooks', {
+ url: "/addressbooks",
+ views: {
+ 'menuContent': {
+ templateUrl: "addressbooks.html",
+ controller: 'AddressBooksCtrl'
+ }
+ }
+ })
+
+ .state('app.addressbook', {
+ url: "/addressbook/:addressbook_id",
+ views: {
+ 'menuContent': {
+ templateUrl: "addressbook.html",
+ controller: 'AddressBookCtrl'
+ }
+ }
+ })
+
+ .state('app.contact', {
+ url: "/addressbook/:addressbook_id/:card_id",
+ views: {
+ 'menuContent': {
+ templateUrl: "card.html",
+ controller: 'CardCtrl'
+ }
+ }
+ });
+
+ // if none of the above states are matched, use this as the fallback
+ $urlRouterProvider.otherwise('/app/addressbooks');
+ })
+
+// .directive('sgAddress', function() {
+// return {
+// restrict: 'A',
+// replace: false,
+// scope: { data: '=sgAddress' },
+// controller: ['$scope', function($scope) {
+// $scope.addressLines = function(data) {
+// var lines = [];
+// if (data.street) lines.push(data.street);
+// if (data.street2) lines.push(data.street2);
+// var locality_region = [];
+// if (data.locality) locality_region.push(data.locality);
+// if (data.region) locality_region.push(data.region);
+// if (locality_region.length > 0) lines.push(locality_region.join(', '));
+// if (data.country) lines.push(data.country);
+// if (data.postalcode) lines.push(data.postalcode);
+// return lines.join('
');
+// };
+// }],
+// template: '
'
+// }
+// })
+
+.controller('AppCtrl', ['$scope', '$http', function($scope, $http) {
+ $scope.UserLogin = UserLogin;
+ $scope.UserFolderURL = UserFolderURL;
+ $scope.ApplicationBaseURL = ApplicationBaseURL;
+ // $scope.logout = function(url) {
+ // $http.get(url)
+ // .success(function(data, status, headers) {
+ // console.debug(headers);
+ // });
+ // };
+}])
+
+.controller('AddressBooksCtrl', ['$scope', '$rootScope', '$timeout', 'sgAddressBook', function($scope, $rootScope, $timeout, AddressBook) {
+ // Initialize with data from template
+ $scope.addressbooks = AddressBook.$all(contactFolders);
+ // $scope.select = function(rowIndex) {
+ // $rootScope.selectedAddressBook = $rootScope.addressbooks[rowIndex];
+ // };
+ // $scope.rename = function() {
+ // console.debug("rename folder");
+ // $scope.editMode = $rootScope.addressbook.id;
+ // //focus('folderName');
+ // };
+ // $scope.save = function() {
+ // console.debug("save addressbook");
+ // $rootScope.addressbook.$save()
+ // .then(function(data) {
+ // console.debug("saved!");
+ // $scope.editMode = false;
+ // }, function(data, status) {
+ // console.debug("failed");
+ // });
+ // };
+}])
+
+ .controller('AddressBookCtrl', ['$scope', '$rootScope', '$stateParams', 'sgAddressBook', function($scope, $rootScope, $stateParams, AddressBook) {
+ var id = $stateParams.addressbook_id;
+ $rootScope.addressbook = AddressBook.$find(id);
+
+ $scope.search = { 'status': null, 'filter': null, 'last_filter': null };
+ $scope.doSearch = function(keyEvent) {
+ if ($scope.search.last_filter != $scope.search.filter) {
+ if ($scope.search.filter.length > 2) {
+ $rootScope.addressbook.$filter($scope.search.filter).then(function(data) {
+ if (data.length == 0)
+ $scope.search.status = 'no-result';
+ else
+ $scope.search.status = '';
+ });
+ }
+ else if ($scope.search.filter.length == 0) {
+ $scope.searchStatus = '';
+ $rootScope.addressbook = AddressBook.$find(id);
+ }
+ else {
+ $scope.search.status = 'min-char';
+ $rootScope.addressbook.cards = [];
+ }
+ }
+ $scope.search.last_filter = $scope.search.filter;
+ };
+
+ }])
+
+ .controller('CardCtrl', ['$scope', '$rootScope', '$stateParams', 'sgAddressBook', 'sgCard', function($scope, $rootScope, $stateParams, AddressBook, Card) {
+ $scope.UserFolderURL = UserFolderURL;
+ if (!$rootScope.addressbook) {
+ $rootScope.addressbook = AddressBook.$find($stateParams.addressbook_id);
+ }
+ $rootScope.addressbook.$getCard($stateParams.card_id);
+ }])
+
+})();
diff --git a/UI/WebServerResources/js/mobile/SOGoRootPage.js b/UI/WebServerResources/js/mobile/SOGoRootPage.js
new file mode 100644
index 000000000..f52ff9d19
--- /dev/null
+++ b/UI/WebServerResources/js/mobile/SOGoRootPage.js
@@ -0,0 +1,66 @@
+/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* JavaScript for SOGoRootPage (mobile) */
+
+(function() {
+ 'use strict';
+
+ angular.module('SOGo.RootPage', ['SOGo.Authentication', 'SOGo.UIMobile', 'ionic'])
+
+ .constant('sgSettings', {
+ 'baseURL': '/SOGo/so/francis/'
+ })
+
+ .run(function($ionicPlatform) {
+ $ionicPlatform.ready(function() {
+ // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
+ // for form inputs)
+ if(window.cordova && window.cordova.plugins.Keyboard) {
+ cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
+ }
+ if(window.StatusBar) {
+ // org.apache.cordova.statusbar required
+ StatusBar.styleDefault();
+ }
+ });
+ })
+
+ .config(function($stateProvider, $urlRouterProvider) {
+ $stateProvider
+
+ .state('app', {
+ url: "/app",
+ abstract: true,
+ templateUrl: "menu.html",
+ controller: 'AppCtrl'
+ })
+
+ .state('app.login', {
+ url: "/login",
+ views: {
+ 'menuContent': {
+ templateUrl: "login.html",
+ controller: 'LoginCtrl'
+ }
+ }
+ });
+
+ // if none of the above states are matched, use this as the fallback
+ $urlRouterProvider.otherwise('/app/login');
+ })
+
+ .controller('AppCtrl', function($scope) {
+ $scope.ApplicationBaseURL = ApplicationBaseURL;
+ })
+
+ .controller('LoginCtrl', ['$scope', 'Authentication', 'sgDialog', function($scope, Authentication, Dialog) {
+ $scope.creds = { 'username': null, 'password': null };
+ $scope.login = function(creds) {
+ Authentication.login(creds)
+ .then(function(url) {
+ window.location.href = url;
+ }, function(msg) {
+ Dialog.alert(l('Warning'), msg.error);
+ });
+ };
+ }]);
+})();
diff --git a/UI/WebServerResources/scss/mobile.scss b/UI/WebServerResources/scss/mobile.scss
new file mode 100644
index 000000000..77557011d
--- /dev/null
+++ b/UI/WebServerResources/scss/mobile.scss
@@ -0,0 +1,35 @@
+@import "ionic/ionic";
+
+.list-clear {
+ .list {
+ border-top: 1px solid $item-light-border;
+ ion-item {
+ border: 0;
+ &, a {
+ padding-top: 5px !important;
+ padding-bottom: 5px !important;
+ }
+ address {
+ margin-bottom: 5px !important;
+ }
+ }
+ }
+}
+
+ion-content {
+ a {
+ &.button {
+ margin-left: 5px;
+ margin-right: 5px;
+ }
+ }
+}
+
+ion-item {
+ small {
+ display: block;
+ color: $positive;
+ }
+ .list-clear & {
+ }
+}
\ No newline at end of file