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

Add Whats new activity and embed it into project

Bartosz Przybylski 9 жил өмнө
parent
commit
2087d2d545

+ 2 - 1
AndroidManifest.xml

@@ -219,7 +219,8 @@
 
         <activity android:name=".ui.errorhandling.ErrorShowActivity" />
         <activity android:name=".ui.activity.UploadListActivity" />
-
+        <activity android:name=".ui.activity.WhatsNewActivity" />
+        
         <receiver android:name=".files.services.ConnectivityActionReceiver"
 		    android:enabled="true" android:label="ConnectivityActionReceiver">
 		    <intent-filter>

BIN
res/drawable-hdpi/ic_menu_forward.png


BIN
res/drawable-mdpi/ic_menu_forward.png


BIN
res/drawable-xhdpi/ic_menu_forward.png


BIN
res/drawable-xxhdpi/ic_menu_forward.png


+ 9 - 0
res/drawable/indicator_dot_background.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="ring"
+       android:innerRadius="4dp"
+       android:thickness="1dp"
+       android:useLevel="false">
+    <solid android:color="@color/owncloud_blue_accent"/>
+    <size android:width="15dp" android:height="10dp" />
+</shape>

+ 9 - 0
res/drawable/indicator_dot_selected.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="ring"
+       android:innerRadius="0dp"
+       android:thickness="3dp"
+       android:useLevel="false">
+    <solid android:color="@color/owncloud_blue_accent"/>
+    <size android:width="15dp" android:height="10dp" />
+</shape>

+ 59 - 0
res/layout/whats_new_activity.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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">
+
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text=""
+        android:id="@+id/welcomeText"
+        android:layout_margin="5dp"
+        android:layout_weight="10"
+        android:gravity="center"/>
+
+    <LinearLayout
+        android:id="@+id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="80"
+        android:orientation="horizontal">
+    </LinearLayout>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="10"
+        android:weightSum="3">
+
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/skip"
+            style="@style/Button.Borderless"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="Skip" />
+
+        <com.owncloud.android.ui.whatsnew.ProgressIndicator
+            android:id="@+id/progressIndicator"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1">
+        </com.owncloud.android.ui.whatsnew.ProgressIndicator>
+
+        <ImageButton
+            android:id="@+id/forward"
+            android:src="@drawable/ic_menu_forward"
+            style="@style/Button.Borderless"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/folder_picker_choose_button_text" />
+    </LinearLayout>
+
+</LinearLayout>

+ 30 - 0
res/layout/whats_new_element.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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="wrap_content"
+        android:id="@+id/whatsNewImage"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginBottom="10dp"
+        android:layout_weight="50"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textStyle="bold"
+        android:id="@+id/whatsNewTitle"
+        android:layout_gravity="center_horizontal"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:id="@+id/whatsNewText"
+        android:layout_margin="15dp"
+        android:layout_gravity="center_horizontal"/>
+</LinearLayout>

+ 18 - 0
res/values/strings.xml

@@ -545,4 +545,22 @@
     <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="welcome_to_oc_title">Welcome to nextCloud!</string>
+    <string name="whats_new_title">What\'s new in nextCloud</string>
+
+    <!-- Welcome to oC intro features -->
+    <string name="welcome_feature_1_title">Instant upload</string>
+    <string name="welcome_feature_1_text">Keep your photos safe</string>
+
+    <string name="welcome_feature_2_title">Manage all your files</string>
+    <string name="welcome_feature_2_text">You can delete, move</string>
+
+    <string name="welcome_feature_3_title">Share</string>
+    <string name="welcome_feature_3_text">You can share files and folders</string>
+
+    <string name="welcome_feature_4_title">MultiAccount</string>
+    <string name="welcome_feature_4_text">Connect to all your clouds</string>
+
+    <string name="welcome_feature_5_title">Your private files synced anywhere</string>
 </resources>

+ 2 - 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);
             }
 

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

@@ -0,0 +1,219 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2015 Bartosz Przybylski
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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/>.
+ *
+ */
+
+package com.owncloud.android.ui.activity;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.util.DisplayMetrics;
+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.LinearLayout;
+import android.widget.TextView;
+
+import com.owncloud.android.BuildConfig;
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+import com.owncloud.android.ui.whatsnew.ProgressIndicator;
+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 WhatsNewActivity extends Activity {
+	private static String TAG = WhatsNewActivity.class.getSimpleName();
+
+	private static final String KEY_LAST_SEEN_VERSION_CODE = "lastSeenVersionCode";
+
+	private FeatureItem[] mFeaturesToShow;
+
+	private ImageButton mForwardFinishButton;
+	private ProgressIndicator mProgress;
+	private LinearLayout mContentPanel;
+
+	int currentStep = 0;
+
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.whats_new_activity);
+		mProgress = (ProgressIndicator) findViewById(R.id.progressIndicator);
+		mFeaturesToShow = filterFeaturesToShow();
+		mProgress.setNumberOfSteps(mFeaturesToShow.length);
+		mForwardFinishButton = (ImageButton) findViewById(R.id.forward);
+		mForwardFinishButton.setOnClickListener(new View.OnClickListener() {
+			@Override
+			public void onClick(View view) {
+				if (mProgress.hasNextStep()) {
+					mProgress.animateToNextStep();
+					mContentPanel.animate().x(-mContentPanel.getChildAt(++currentStep).getLeft());
+				} else {
+					onFinish();
+					finish();
+				}
+				if (!mProgress.hasNextStep()) {
+					mForwardFinishButton.setImageResource(R.drawable.ic_ok);
+				}
+			}
+		});
+		Button skipButton = (Button) findViewById(R.id.skip);
+		skipButton.setOnClickListener(new View.OnClickListener() {
+			@Override
+			public void onClick(View view) {
+				onFinish();
+				finish();
+			}
+		});
+
+		SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
+		final int lastSeenVersionCode = pref.getInt(KEY_LAST_SEEN_VERSION_CODE, 0);
+		final boolean isFirstRun = lastSeenVersionCode == 0;
+		TextView tv = (TextView)findViewById(R.id.welcomeText);
+		tv.setText(isFirstRun ? R.string.welcome_to_oc_title : R.string.whats_new_title);
+
+		DisplayMetrics dm = new DisplayMetrics();
+		getWindowManager().getDefaultDisplay().getMetrics(dm);
+
+		mContentPanel = (LinearLayout)findViewById(R.id.contentPanel);
+		LinearLayout.LayoutParams ll2 = (LinearLayout.LayoutParams) mContentPanel.getLayoutParams();
+		ll2.width = dm.widthPixels*mFeaturesToShow.length;
+		mContentPanel.setLayoutParams(ll2);
+		LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
+
+
+		for (int i = 0; i < mFeaturesToShow.length; ++i) {
+			FeatureItem item = mFeaturesToShow[i];
+			LinearLayout newElement = (LinearLayout)inflater.inflate(R.layout.whats_new_element, null);
+
+			LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.MATCH_PARENT);
+			newElement.setLayoutParams(params);
+
+			mContentPanel.addView(newElement);
+			ImageView iv = (ImageView)newElement.findViewById(R.id.whatsNewImage);
+			if (item.getImage() != FeatureItem.doNotShow)
+				iv.setImageResource(item.getImage());
+
+			TextView tv2 = (TextView)newElement.findViewById(R.id.whatsNewTitle);
+			if (item.getTitleText() != FeatureItem.doNotShow)
+				tv2.setText(item.getTitleText());
+
+			tv2 = (TextView)newElement.findViewById(R.id.whatsNewText);
+			if (item.getContentText() != FeatureItem.doNotShow)
+				tv2.setText(item.getContentText());
+		}
+	}
+
+	private void onFinish() {
+		SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
+		SharedPreferences.Editor editor = pref.edit();
+		editor.putInt(KEY_LAST_SEEN_VERSION_CODE, BuildConfig.VERSION_CODE);
+		editor.apply();
+	}
+
+	static public void runIfNeeded(Context context) {
+		if (context instanceof WhatsNewActivity)
+			return;
+
+		if (filterFeaturesToShow().length > 0)
+			context.startActivity(new Intent(context, WhatsNewActivity.class));
+	}
+
+	static private FeatureItem[] filterFeaturesToShow() {
+		List<FeatureItem> features = new LinkedList<>();
+
+		SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(MainApp.getAppContext());
+		final int lastSeenVersionCode = pref.getInt(KEY_LAST_SEEN_VERSION_CODE, 0);
+		final boolean isFirstRun = lastSeenVersionCode == 0;
+
+		for (FeatureItem item : featuresToShow) {
+			if (isFirstRun && item.shouldShowOnFirstRun()) {
+				features.add(item);
+			} else if (!isFirstRun && !item.shouldShowOnFirstRun() &&
+					BuildConfig.VERSION_CODE >= item.getVersionNumber() &&
+					lastSeenVersionCode < item.getVersionNumber()) {
+				features.add(item);
+			}
+		}
+		return features.toArray(new FeatureItem[features.size()]);
+	}
+
+	static FeatureItem featuresToShow[] = {
+			new FeatureItem(R.drawable.logo, R.string.welcome_feature_1_title, R.string.welcome_feature_1_text, "1.0.0", true),
+			new FeatureItem(R.drawable.logo, R.string.welcome_feature_2_title,  R.string.welcome_feature_2_text, "1.0.0", true),
+			new FeatureItem(R.drawable.logo, R.string.welcome_feature_3_title,  R.string.welcome_feature_3_text, "1.0.0", true),
+			new FeatureItem(R.drawable.logo, R.string.welcome_feature_4_title,  R.string.welcome_feature_4_text, "1.0.0", true),
+			new FeatureItem(R.drawable.logo, R.string.welcome_feature_5_title,  FeatureItem.doNotShow, "1.0.0", true),
+			new FeatureItem(R.drawable.logo, R.string.welcome_feature_1_title,  FeatureItem.doNotShow, "1.8.3"),
+			new FeatureItem(R.drawable.logo, R.string.welcome_feature_2_title,  FeatureItem.doNotShow, "1.8.4"),
+	};
+
+	static private class FeatureItem {
+		static final int doNotShow = -1;
+		private int image;
+		private int titleText;
+		private int contentText;
+		private int versionNumber;
+		private boolean showOnInitialRun;
+
+		public FeatureItem(int image, int titleText, int contentText, String version) {
+			this(image, titleText, contentText, version, false);
+		}
+
+		public FeatureItem(int image, int titleText, int contentText, String version, boolean showOnInitialRun) {
+			this.image = image;
+			this.titleText = titleText;
+			this.contentText = contentText;
+			this.versionNumber = versionCodeFromString(version);
+			this.showOnInitialRun = showOnInitialRun;
+		}
+
+		public int getImage() { return image; }
+		public int getTitleText() { return titleText; }
+		public int getContentText() { return contentText; }
+		public int getVersionNumber() { return versionNumber; }
+		public boolean shouldShowOnFirstRun() { return showOnInitialRun; }
+	}
+
+	static int versionCodeFromString(String version) {
+		String v[] = version.split(Pattern.quote("."));
+		if (v.length != 3) {
+			Log_OC.wtf(TAG, "Version string is incorrect " + version);
+			return 0;
+		}
+		int result = Integer.parseInt(v[0])*(int)(10e6) +
+				Integer.parseInt(v[1])*(int)(10e4) +
+				Integer.parseInt(v[2]);
+
+		return result;
+	}
+}

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

@@ -0,0 +1,128 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2015 Bartosz Przybylski
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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/>.
+ *
+ */
+
+package com.owncloud.android.ui.whatsnew;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.owncloud.android.R;
+
+/**
+ * @author Bartosz Przybylski
+ */
+public class ProgressIndicator extends FrameLayout {
+
+	protected LinearLayout mDotsContainer;
+	protected ImageView mCurrentProgressDot;
+
+	protected int mNumberOfSteps;
+	protected int mCurrentStep;
+
+	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();
+	}
+
+	@Override
+	public void onWindowFocusChanged(boolean hasWindowFocus) {
+		super.onWindowFocusChanged(hasWindowFocus);
+		// This is not the best place to reset steps but I couldn't find a better one
+		resetStep();
+	}
+
+	public boolean hasNextStep() {
+		return mNumberOfSteps > mCurrentStep;
+	}
+
+	public void animateToNextStep() {
+		animateToStep(++mCurrentStep);
+	}
+
+	public void resetStep() {
+		setStep(1);
+	}
+
+	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.indicator_dot_background));
+			mDotsContainer.addView(iv);
+		}
+	}
+
+	private void setStep(int step) {
+		if (step < 1 || step > mNumberOfSteps) return;
+
+		View dot = mDotsContainer.getChildAt(step-1);
+		FrameLayout.LayoutParams lp = (LayoutParams) mCurrentProgressDot.getLayoutParams();
+		lp.leftMargin = dot.getLeft();
+		lp.topMargin = dot.getTop();
+	}
+
+	private void animateToStep(int step) {
+		if (step < 1 || step > mNumberOfSteps) return;
+		View dot = mDotsContainer.getChildAt(step-1);
+		mCurrentProgressDot
+				.animate()
+				.x(dot.getLeft())
+				.y(dot.getTop());
+	}
+
+	private void setup() {
+		mCurrentStep = 1;
+
+		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);
+
+		mCurrentProgressDot = new ImageView(getContext());
+		params = generateDefaultLayoutParams();
+		params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+		params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+		mCurrentProgressDot.setLayoutParams(params);
+		mCurrentProgressDot.setImageDrawable(getContext().getResources().getDrawable(R.drawable.indicator_dot_selected));
+		addView(mCurrentProgressDot);
+	}
+
+}