Эх сурвалжийг харах

Merge pull request #84 from nextcloud/whatsNew

Whats new
Andy Scherzinger 8 жил өмнө
parent
commit
0e8b51e8a5

+ 6 - 4
AndroidManifest.xml

@@ -20,8 +20,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.owncloud.android"
-    android:versionCode="10040002"
-    android:versionName="1.4.0 RC2">
+    android:versionCode="10040003"
+    android:versionName="1.4.0 RC3">
 
     <uses-sdk
         android:minSdkVersion="14"
@@ -183,7 +183,7 @@
             android:name=".authentication.AuthenticatorActivity"
             android:exported="true"
             android:launchMode="singleTask"
-            android:theme="@style/Theme.ownCloud.noActionBar.Login" >
+            android:theme="@style/Theme.ownCloud.noActionBar.Login">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
 
@@ -219,7 +219,9 @@
 
         <activity android:name=".ui.errorhandling.ErrorShowActivity" />
         <activity android:name=".ui.activity.UploadListActivity" />
-
+        <activity android:name=".ui.activity.WhatsNewActivity"
+                  android:theme="@style/Theme.ownCloud.noActionBar.Login" />
+        
         <receiver android:name=".files.services.ConnectivityActionReceiver"
 		    android:enabled="true" android:label="ConnectivityActionReceiver">
 		    <intent-filter>

+ 27 - 0
res/drawable-anydpi-v21/arrow_right.xml

@@ -0,0 +1,27 @@
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2016 Andy Scherzinger
+  Copyright (C) 2016 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or any later version.
+
+  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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path android:fillColor="#ffffff"
+          android:pathData="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" />
+</vector>

BIN
res/drawable-hdpi/arrow_right.png


BIN
res/drawable-hdpi/ic_done_white.png


BIN
res/drawable-hdpi/what_new_instant_upload.png


BIN
res/drawable-hdpi/whats_new_accounts.png


BIN
res/drawable-hdpi/whats_new_files.png


BIN
res/drawable-mdpi/arrow_right.png


BIN
res/drawable-mdpi/ic_done_white.png


BIN
res/drawable-xhdpi/arrow_right.png


BIN
res/drawable-xhdpi/ic_done_white.png


BIN
res/drawable-xxhdpi/arrow_right.png


BIN
res/drawable-xxhdpi/ic_done_white.png


BIN
res/drawable-xxxhdpi/arrow_right.png


+ 31 - 0
res/drawable/indicator_dot_not_selected.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2015-2016 Bartosz Przybylski
+  Copyright (C) 2015 ownCloud Inc.
+  Copyright (C) 2016 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or any later version.
+
+  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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:bottom="5dp" android:top="5dp" android:right="5dp" android:left="5dp">
+
+        <shape
+            android:shape="oval">
+            <solid android:color="@color/owncloud_blue_dark_transparent"/>
+            <size android:width="8dp" android:height="8dp" />
+        </shape>
+    </item>
+</layer-list>

+ 30 - 0
res/drawable/indicator_dot_selected.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2015-2016 Bartosz Przybylski
+  Copyright (C) 2015 ownCloud Inc.
+  Copyright (C) 2016 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or any later version.
+
+  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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:bottom="5dp" android:top="5dp" android:right="5dp" android:left="5dp">
+        <shape
+            android:shape="oval">
+            <solid android:color="@color/white"/>
+            <size android:width="8dp" android:height="8dp" />
+        </shape>
+    </item>
+</layer-list>

+ 25 - 0
res/drawable/round_button.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2016 Andy Scherzinger
+  Copyright (C) 2015 ownCloud Inc.
+  Copyright (C) 2016 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or any later version.
+
+  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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="@color/owncloud_blue_accent"/>
+</shape>

+ 24 - 0
res/drawable/whats_new_progress_transition.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2015 Bartosz Przybylski
+  Copyright (C) 2016 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or any later version.
+
+  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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/indicator_dot_not_selected" />
+    <item android:drawable="@drawable/indicator_dot_selected" />
+</transition>

+ 90 - 0
res/layout/whats_new_activity.xml

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2015-2016 Bartosz Przybylski
+  Copyright (C) 2015 ownCloud Inc.
+  Copyright (C) 2016 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or any later version.
+
+  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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical"
+              android:weightSum="100">
+
+    <TextView
+        android:id="@+id/welcomeText"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_margin="5dp"
+        android:layout_weight="10"
+        android:gravity="center"
+        android:text="@string/placeholder_sentence"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textColor="@color/login_text_hint_color"/>
+
+    <android.support.v4.view.ViewPager
+        android:id="@+id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="80">
+    </android.support.v4.view.ViewPager>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_marginBottom="@dimen/standard_margin"
+        android:layout_marginLeft="@dimen/standard_margin"
+        android:layout_marginRight="@dimen/standard_margin"
+        android:layout_weight="10"
+        android:orientation="horizontal"
+        android:weightSum="3">
+
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/skip"
+            style="@style/Button.Borderless.Login"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical|center_horizontal"
+            android:layout_weight="1"
+            android:text="@string/whats_new_skip"/>
+
+        <com.owncloud.android.ui.whatsnew.ProgressIndicator
+            android:id="@+id/progressIndicator"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical|center_horizontal"
+            android:layout_weight="1">
+        </com.owncloud.android.ui.whatsnew.ProgressIndicator>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <ImageButton
+                android:id="@+id/forward"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:padding="@dimen/standard_padding"
+                android:src="@drawable/arrow_right"/>
+        </LinearLayout>
+    </LinearLayout>
+
+</LinearLayout>

+ 62 - 0
res/layout/whats_new_element.xml

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  Copyright (C) 2015-2016 Bartosz Przybylski
+  Copyright (C) 2015 ownCloud Inc.
+  Copyright (C) 2016 Nextcloud.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or any later version.
+
+  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 AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:weightSum="100">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:id="@+id/whatsNewImage"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginBottom="10dp"
+        android:layout_weight="50"
+        android:src="@drawable/whats_new_files"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/NextcloudTextAppearanceHeadline"
+        android:textColor="@color/white"
+        android:textStyle="bold"
+        android:text="@string/welcome_feature_1_title"
+        android:id="@+id/whatsNewTitle"
+        android:layout_margin="@dimen/standard_margin"
+        android:layout_gravity="center_horizontal"
+        android:gravity="center"/>
+
+    <TextView
+        android:id="@+id/whatsNewText"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginBottom="@dimen/standard_margin"
+        android:layout_marginLeft="@dimen/standard_double_margin"
+        android:layout_marginRight="@dimen/standard_double_margin"
+        android:layout_marginTop="@dimen/standard_margin"
+        android:gravity="center"
+        android:text="@string/welcome_feature_1_text"
+        android:textAppearance="@style/NextcloudTextAppearanceMedium"
+        android:textColor="@color/login_text_hint_color"/>
+</LinearLayout>

+ 1 - 0
res/values/dims.xml

@@ -38,6 +38,7 @@
     <dimen name="standard_half_padding">8dp</dimen>
     <dimen name="standard_quarter_padding">4dp</dimen>
     <dimen name="standard_margin">16dp</dimen>
+    <dimen name="standard_double_margin">32dp</dimen>
     <dimen name="standard_half_margin">8dp</dimen>
     <dimen name="standard_quarter_margin">4dp</dimen>
     <dimen name="standard_eighth_margin">2dp</dimen>

+ 3 - 0
res/values/setup.xml

@@ -1,5 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
+    <!-- Beta indicator -->
+    <bool name="is_beta">false</bool>
+
     <!-- App name  and other strings-->
     <string name="app_name">Nextcloud</string>
     <string name="account_type">nextcloud</string>	<!-- better if was a domain name; but changing it now would require migrate accounts when the app is updated -->

+ 15 - 1
res/values/strings.xml

@@ -347,7 +347,7 @@
     <string name="network_error_socket_timeout_exception">An error occurred while waiting for the server; the operation couldn\'t be completed</string>
     <string name="network_error_connect_timeout_exception">An error occurred while waiting for the server; the operation couldn\'t be completed</string>
     <string name="network_host_not_available">The operation couldn\'t be completed; server is unavailable</string>
-    <string name="empty" />
+    <string name="empty" translatable="false" />
 
     <string name="forbidden_permissions">You do not have permission %s</string>
     <string name="forbidden_permissions_rename">to rename this file</string>
@@ -545,4 +545,18 @@
     <string name="storage_description_sd_no">SD card %1$d</string>
     <string name="storage_description_unknown">Unknown</string>
 
+    <!-- What's new feature and texts to show -->
+    <string name="whats_new_title">What\'s new in Nextcloud</string>
+
+    <!-- Welcome to Nc intro features -->
+    <string name="welcome_feature_1_title">A safe home for all your data</string>
+    <string name="welcome_feature_1_text">Access, share &amp; protect your files at home and in your enterprise</string>
+
+    <string name="welcome_feature_2_title">Multi account</string>
+    <string name="welcome_feature_2_text">Connect to all your clouds</string>
+
+    <string name="welcome_feature_3_title">Instant upload</string>
+    <string name="welcome_feature_3_text">Keep your photos safe</string>
+
+    <string name="whats_new_skip">Skip</string>
 </resources>

+ 14 - 0
res/values/styles.xml

@@ -113,6 +113,10 @@
 		<item name="android:textColor">@color/color_accent</item>
 	</style>
 
+	<style name="Button.Borderless.Login" parent="Base.Widget.AppCompat.Button.Borderless">
+		<item name="android:textColor">@color/white</item>
+	</style>
+
 	<!-- separat translucent action bar style -->
 	<style name="Theme.ownCloud.Overlay" parent="style/Theme.ownCloud">
 		<item name="android:actionBarStyle">@style/Theme.ownCloud.Overlay.ActionBar</item>
@@ -277,4 +281,14 @@
     </style>
     <style name="Theme.ownCloud.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
     <style name="Theme.ownCloud.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+	<!-- Text styles -->
+	<style name="NextcloudTextAppearanceHeadline" parent="style/TextAppearance.AppCompat.Headline">
+		<item name="android:textSize">26sp</item>
+		<item name="android:textColor">#000000</item>
+	</style>
+	<style name="NextcloudTextAppearanceMedium" parent="style/TextAppearance.AppCompat.Medium">
+	</style>
+	<style name="NextcloudTextAppearanceSmall" parent="style/TextAppearance.AppCompat.Small">
+	</style>
 </resources>

+ 13 - 0
src/com/owncloud/android/MainApp.java

@@ -41,6 +41,7 @@ import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.services.observer.SyncedFolderObserverService;
 import com.owncloud.android.ui.activity.Preferences;
+import com.owncloud.android.ui.activity.WhatsNewActivity;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
@@ -118,6 +119,7 @@ public class MainApp extends Application {
             @Override
             public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                 Log_OC.d(activity.getClass().getSimpleName(),  "onCreate(Bundle) starting" );
+                WhatsNewActivity.runIfNeeded(activity);
                 PassCodeManager.getPassCodeManager().onActivityCreated(activity);
             }
 
@@ -174,6 +176,17 @@ public class MainApp extends Application {
         return getAppContext().getResources().getString(R.string.account_type);
     }
 
+    // Non gradle build systems do not provide BuildConfig.VERSION_CODE
+    // so we must fallback to this method :(
+    public static int getVersionCode() {
+        try {
+            String thisPackageName = getAppContext().getPackageName();
+            return getAppContext().getPackageManager().getPackageInfo(thisPackageName, 0).versionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            return 0;
+        }
+    }
+
     //  From AccountAuthenticator 
     //  public static final String AUTHORITY = "org.owncloud";
     public static String getAuthority() {

+ 162 - 0
src/com/owncloud/android/features/FeatureList.java

@@ -0,0 +1,162 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2015 Bartosz Przybylski
+ *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 Nextcloud.
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or any later version.
+ *
+ *   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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.features;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * @author Bartosz Przybylski
+ */
+public class FeatureList {
+    private static final boolean SHOW_ON_FIRST_RUN = true;
+
+    private static final String VERSION_1_0_0 = "1.0.0";
+    private static final String BETA_VERSION_0 = "0";
+
+    static final private FeatureItem featuresList[] = {
+            // Basic features showed on first install
+            new FeatureItem(R.drawable.whats_new_files,
+                    R.string.welcome_feature_1_title, R.string.welcome_feature_1_text,
+                    VERSION_1_0_0, BETA_VERSION_0, SHOW_ON_FIRST_RUN),
+            new FeatureItem(R.drawable.whats_new_accounts,
+                    R.string.welcome_feature_2_title, R.string.welcome_feature_2_text,
+                    VERSION_1_0_0, BETA_VERSION_0, SHOW_ON_FIRST_RUN),
+            new FeatureItem(R.drawable.what_new_instant_upload,
+                    R.string.welcome_feature_3_title, R.string.welcome_feature_3_text,
+                    VERSION_1_0_0, BETA_VERSION_0, SHOW_ON_FIRST_RUN),
+            // Features introduced in certain point in time
+    };
+
+    static public FeatureItem[] get() {
+        return featuresList;
+    }
+
+    static public FeatureItem[] getFiltered(final int lastSeenVersionCode, final boolean isFirstRun, boolean isBeta) {
+        List<FeatureItem> features = new LinkedList<>();
+
+        for (FeatureItem item : get()) {
+            final int itemVersionCode = isBeta ? item.getBetaVersionNumber() : item.getVersionNumber();
+            if (isFirstRun && item.shouldShowOnFirstRun()) {
+                features.add(item);
+            } else if (!isFirstRun && !item.shouldShowOnFirstRun() &&
+                    MainApp.getVersionCode() >= itemVersionCode &&
+                    lastSeenVersionCode < itemVersionCode) {
+                features.add(item);
+            }
+        }
+        return features.toArray(new FeatureItem[features.size()]);
+    }
+
+    static public class FeatureItem implements Parcelable {
+        public static final int DO_NOT_SHOW = -1;
+        private int image;
+        private int titleText;
+        private int contentText;
+        private int versionNumber;
+        private int betaVersion;
+        private boolean showOnInitialRun;
+
+        public FeatureItem(int image, int titleText, int contentText, String version, String betaVersion) {
+            this(image, titleText, contentText, version, betaVersion, false);
+        }
+
+        public FeatureItem(int image, int titleText, int contentText, String version, String betaVersion, boolean showOnInitialRun) {
+            this.image = image;
+            this.titleText = titleText;
+            this.contentText = contentText;
+            this.versionNumber = versionCodeFromString(version);
+            this.betaVersion = Integer.parseInt(betaVersion);
+            this.showOnInitialRun = showOnInitialRun;
+        }
+
+        public boolean shouldShowImage() { return image != DO_NOT_SHOW; }
+        public int getImage() { return image; }
+
+        public boolean shouldShowTitleText() { return titleText != DO_NOT_SHOW; }
+        public int getTitleText() { return titleText; }
+
+        public boolean shouldShowContentText() { return contentText != DO_NOT_SHOW; }
+        public int getContentText() { return contentText; }
+
+        public int getVersionNumber() { return versionNumber; }
+        public int getBetaVersionNumber() { return betaVersion; }
+        public boolean shouldShowOnFirstRun() { return showOnInitialRun; }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(image);
+            dest.writeInt(titleText);
+            dest.writeInt(contentText);
+            dest.writeInt(versionNumber);
+            dest.writeInt(betaVersion);
+            dest.writeByte((byte) (showOnInitialRun ? 1 : 0));
+        }
+
+        private FeatureItem(Parcel p) {
+            image = p.readInt();
+            titleText = p.readInt();
+            contentText = p.readInt();
+            versionNumber = p.readInt();
+            betaVersion = p.readInt();
+            showOnInitialRun = p.readByte() == 1;
+        }
+        public static final Parcelable.Creator CREATOR =
+                new Parcelable.Creator() {
+
+                    @Override
+                    public Object createFromParcel(Parcel source) {
+                        return new FeatureItem(source);
+                    }
+
+                    @Override
+                    public Object[] newArray(int size) {
+                        return new FeatureItem[size];
+                    }
+                };
+    }
+
+    private static int versionCodeFromString(String version) {
+        String v[] = version.split(Pattern.quote("."));
+        if (v.length != 3) {
+            Log_OC.e("FeatureList", "Version string is incorrect " + version);
+            return 0;
+        }
+        return Integer.parseInt(v[0])*(int)(10e6) +
+                Integer.parseInt(v[1])*(int)(10e4) +
+                Integer.parseInt(v[2])*100;
+    }
+}

+ 249 - 0
src/com/owncloud/android/ui/activity/WhatsNewActivity.java

@@ -0,0 +1,249 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2015 Bartosz Przybylski
+ *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 Nextcloud.
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or any later version.
+ *
+ *   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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountAuthenticatorActivity;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.features.FeatureList;
+import com.owncloud.android.features.FeatureList.FeatureItem;
+import com.owncloud.android.ui.whatsnew.ProgressIndicator;
+
+/**
+ * Activity displaying general feature after a fresh install and new features after an update.
+ */
+public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPageChangeListener {
+
+    private static final String KEY_LAST_SEEN_VERSION_CODE = "lastSeenVersionCode";
+
+    private ImageButton mForwardFinishButton;
+    private Button mSkipButton;
+    private ProgressIndicator mProgress;
+    private ViewPager mPager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.whats_new_activity);
+
+        mProgress = (ProgressIndicator) findViewById(R.id.progressIndicator);
+        mPager = (ViewPager)findViewById(R.id.contentPanel);
+        final boolean isBeta = getResources().getBoolean(R.bool.is_beta);
+        FeaturesViewAdapter adapter = new FeaturesViewAdapter(getSupportFragmentManager(),
+                FeatureList.getFiltered(getLastSeenVersionCode(), isFirstRun(), isBeta));
+
+        mProgress.setNumberOfSteps(adapter.getCount());
+        mPager.setAdapter(adapter);
+        mPager.addOnPageChangeListener(this);
+
+
+        mForwardFinishButton = (ImageButton) findViewById(R.id.forward);
+        mForwardFinishButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (mProgress.hasNextStep()) {
+                    mPager.setCurrentItem(mPager.getCurrentItem()+1, true);
+                    mProgress.animateToStep(mPager.getCurrentItem()+1);
+                } else {
+                    onFinish();
+                    finish();
+                }
+                updateNextButtonIfNeeded();
+            }
+        });
+
+        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            mForwardFinishButton.setBackground(null);
+        } else {
+            mForwardFinishButton.setBackgroundDrawable(null);
+        }
+
+        mSkipButton = (Button) findViewById(R.id.skip);
+        mSkipButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onFinish();
+                finish();
+            }
+        });
+
+        TextView tv = (TextView)findViewById(R.id.welcomeText);
+        tv.setText(isFirstRun() ? R.string.empty : R.string.whats_new_title);
+
+        updateNextButtonIfNeeded();
+    }
+
+    @Override
+    public void onBackPressed() {
+        onFinish();
+        super.onBackPressed();
+    }
+
+
+    private void updateNextButtonIfNeeded() {
+        if (!mProgress.hasNextStep()) {
+            mForwardFinishButton.setImageResource(R.drawable.ic_done_white);
+            mSkipButton.setVisibility(View.INVISIBLE);
+        } else {
+            mForwardFinishButton.setImageResource(R.drawable.arrow_right);
+            mSkipButton.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void onFinish() {
+        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
+        SharedPreferences.Editor editor = pref.edit();
+        editor.putInt(KEY_LAST_SEEN_VERSION_CODE, MainApp.getVersionCode());
+        editor.apply();
+    }
+
+    static private int getLastSeenVersionCode() {
+        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(MainApp.getAppContext());
+        return pref.getInt(KEY_LAST_SEEN_VERSION_CODE, 0);
+    }
+
+    static private boolean isFirstRun() {
+        return getLastSeenVersionCode() == 0 && AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext()) == null;
+    }
+
+    static public void runIfNeeded(Context context) {
+        if (context instanceof WhatsNewActivity) {
+            return;
+        }
+
+        if (shouldShow(context)) {
+            context.startActivity(new Intent(context, WhatsNewActivity.class));
+        }
+    }
+
+    static private boolean shouldShow(Context context) {
+        final boolean isBeta = context.getResources().getBoolean(R.bool.is_beta);
+        return (isFirstRun() && context instanceof AccountAuthenticatorActivity) ||
+                (
+                        !(isFirstRun() && (context instanceof FileDisplayActivity)) &&
+                        !(context instanceof PassCodeActivity) &&
+                        (FeatureList.getFiltered(getLastSeenVersionCode(), isFirstRun(), isBeta).length > 0)
+                );
+    }
+
+    @Override
+    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        // unused but to be implemented due to abstract parent
+    }
+
+    @Override
+    public void onPageSelected(int position) {
+        mProgress.animateToStep(position+1);
+        updateNextButtonIfNeeded();
+    }
+
+    @Override
+    public void onPageScrollStateChanged(int state) {
+        // unused but to be implemented due to abstract parent
+    }
+
+    private final class FeaturesViewAdapter extends FragmentPagerAdapter {
+
+        private FeatureItem[] mFeatures;
+
+        public FeaturesViewAdapter(FragmentManager fm, FeatureItem[]features) {
+            super(fm);
+            mFeatures = features;
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            return FeatureFragment.newInstance(mFeatures[position]);
+        }
+
+        @Override
+        public int getCount() {
+            return mFeatures.length;
+        }
+    }
+
+    public static class FeatureFragment extends Fragment {
+        private FeatureItem mItem;
+
+        static public FeatureFragment newInstance(FeatureItem item) {
+            FeatureFragment f = new FeatureFragment();
+            Bundle args = new Bundle();
+            args.putParcelable("feature", item);
+            f.setArguments(args);
+            return f;
+        }
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mItem = getArguments() != null ? (FeatureItem)getArguments().getParcelable("feature") : null;
+        }
+
+        @Nullable
+        @Override
+        public View onCreateView(LayoutInflater inflater,
+                                 @Nullable ViewGroup container,
+                                 @Nullable Bundle savedInstanceState) {
+            View v = inflater.inflate(R.layout.whats_new_element, container, false);
+
+            ImageView iv = (ImageView)v.findViewById(R.id.whatsNewImage);
+            if (mItem.shouldShowImage()) {
+                iv.setImageResource(mItem.getImage());
+            }
+
+            TextView tv2 = (TextView)v.findViewById(R.id.whatsNewTitle);
+            if (mItem.shouldShowTitleText()) {
+                tv2.setText(mItem.getTitleText());
+            }
+
+            tv2 = (TextView)v.findViewById(R.id.whatsNewText);
+            if (mItem.shouldShowContentText()) {
+                tv2.setText(mItem.getContentText());
+            }
+
+            return v;
+        }
+    }
+
+}

+ 103 - 0
src/com/owncloud/android/ui/whatsnew/ProgressIndicator.java

@@ -0,0 +1,103 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2015 Bartosz Przybylski
+ *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 Nextcloud.
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or any later version.
+ *
+ *   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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.whatsnew;
+
+import android.content.Context;
+import android.graphics.drawable.TransitionDrawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.owncloud.android.R;
+
+/**
+ * Progress indicator visualizing the actual progress with dots.
+ */
+public class ProgressIndicator extends FrameLayout {
+
+    protected LinearLayout mDotsContainer;
+
+    protected int mNumberOfSteps = -1;
+    protected int mCurrentStep = -1;
+
+    public ProgressIndicator(Context context) {
+        super(context);
+        setup();
+    }
+
+    public ProgressIndicator(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setup();
+    }
+
+    public ProgressIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setup();
+    }
+
+    public boolean hasNextStep() {
+        return mNumberOfSteps > mCurrentStep;
+    }
+
+    public void setNumberOfSteps(int steps) {
+        mNumberOfSteps = steps;
+        mDotsContainer.removeAllViews();
+        for (int i = 0; i < steps; ++i) {
+            ImageView iv = new ImageView(getContext());
+            iv.setImageDrawable(getContext().getResources().getDrawable(R.drawable.whats_new_progress_transition));
+            mDotsContainer.addView(iv);
+        }
+        animateToStep(1);
+    }
+
+    public void animateToStep(int step) {
+        if (step < 1 || step > mNumberOfSteps) {
+            return;
+        }
+
+        if (mCurrentStep != -1) {
+            ImageView prevDot = (ImageView) mDotsContainer.getChildAt(mCurrentStep-1);
+            TransitionDrawable transition = (TransitionDrawable)prevDot.getDrawable();
+            transition.resetTransition();
+        }
+
+        mCurrentStep = step;
+        ImageView dot = (ImageView)mDotsContainer.getChildAt(step-1);
+        TransitionDrawable transition = (TransitionDrawable)dot.getDrawable();
+        transition.startTransition(500);
+    }
+
+    private void setup() {
+        mDotsContainer = new LinearLayout(getContext());
+        mDotsContainer.setGravity(Gravity.CENTER);
+        FrameLayout.LayoutParams params = generateDefaultLayoutParams();
+        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+        params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+        mDotsContainer.setLayoutParams(params);
+        addView(mDotsContainer);
+    }
+
+}