diff --git a/.gitignore b/.gitignore
index a287a2a..afbdab3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,57 +1,6 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
.DS_Store
-
-# Local sandbox
-_sandbox
-
-# built application files
-*.apk
-*.ap_
-
-# files for the dex VM
-*.dex
-
-# Java class files
-*.class
-
-# generated files
-out/
-bin/
-gen/
-
-# Local configuration file (sdk path, etc)
-local.properties
-
-# Eclipse project files
-.classpath
-.project
-
-# IDEA files
-*.iml
-*.ipr
-*.iws
-
-# Other build files
-project.properties
-ant.properties
-build.xml
-proguard-project.txt
-
-# Built application files
-/*/build/
/build
-
-# Gradle generated files
-.gradle/
-
-# User-specific configurations
-.idea/libraries/
-.idea/workspace.xml
-.idea/tasks.xml
-.idea/.name
-.idea/compiler.xml
-.idea/copyright/profiles_settings.xml
-.idea/encodings.xml
-.idea/misc.xml
-.idea/modules.xml
-.idea/scopes/scope_settings.xml
-.idea/vcs.xml
\ No newline at end of file
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..e2fb291
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Android-Material-Wizard
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..217af47
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..e7bedf3
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..e206d70
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 2caec4f..aaafd26 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -3,15 +3,17 @@
-
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..3f9d109
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Abstraction issues
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.7
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..4ad9c50
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml
new file mode 100644
index 0000000..922003b
--- /dev/null
+++ b/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..c80f219
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 1f99ad1..18d3cd4 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,15 @@
-Android WizardPager Sample Code
+Android Lollipop wizard template
===============================
-Example `ViewPager`-based wizard UI sample code. See [my Google+ post](https://plus.google.com/+RomanNurik/posts/6cVymZvn3f4) for more details.
+Fork of Roman Nurik's Example `ViewPager`-based wizard UI sample code. For further information on his wizard, see [his Google+ post](https://plus.google.com/+RomanNurik/posts/6cVymZvn3f4) or [his Github project page](https://github.com/romannurik/android-wizardpager).
-
-
-
+This template wizard is a Lollipop version of the original wizard.
-Additional page type examples (boolean and single text field) can be found in str4d's fork (`model` , `ui` ).
+
+
+
+
+
+I've provided a GIF showing the wizard in action.
+
+
diff --git a/Wizards.iml b/Wizards.iml
new file mode 100644
index 0000000..0bb6048
--- /dev/null
+++ b/Wizards.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/app.iml b/app/app.iml
new file mode 100644
index 0000000..74ba43b
--- /dev/null
+++ b/app/app.iml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d83b9f5
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ applicationId "com.markosullivan.wizards"
+ minSdkVersion 21
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile "com.android.support:support-v4:22.0.+"
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..a64ee44
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\MOS182\Documents\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/app/src/androidTest/java/com/markosullivan/wizards/ApplicationTest.java b/app/src/androidTest/java/com/markosullivan/wizards/ApplicationTest.java
new file mode 100644
index 0000000..ff64db9
--- /dev/null
+++ b/app/src/androidTest/java/com/markosullivan/wizards/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.markosullivan.wizards;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d6b4342
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/markosullivan/wizards/MainActivity.java b/app/src/main/java/com/markosullivan/wizards/MainActivity.java
new file mode 100644
index 0000000..6b0b19c
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/MainActivity.java
@@ -0,0 +1,261 @@
+package com.markosullivan.wizards;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.markosullivan.wizards.wizard.model.AbstractWizardModel;
+import com.markosullivan.wizards.wizard.model.ModelCallbacks;
+import com.markosullivan.wizards.wizard.model.Page;
+import com.markosullivan.wizards.wizard.ui.PageFragmentCallbacks;
+import com.markosullivan.wizards.wizard.ui.ReviewFragment;
+import com.markosullivan.wizards.wizard.ui.StepPagerStrip;
+
+import java.util.List;
+
+public class MainActivity extends FragmentActivity implements
+ PageFragmentCallbacks,
+ ReviewFragment.Callbacks,
+ ModelCallbacks {
+ private ViewPager mPager;
+ private MyPagerAdapter mPagerAdapter;
+
+ private boolean mEditingAfterReview;
+
+ private AbstractWizardModel mWizardModel = new PresentWizardModel(this);
+
+ private boolean mConsumePageSelectedEvent;
+
+ private Button mNextButton;
+ private Button mPrevButton;
+
+ private List mCurrentPageSequence;
+ private StepPagerStrip mStepPagerStrip;
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ if (savedInstanceState != null) {
+ mWizardModel.load(savedInstanceState.getBundle("model"));
+ }
+
+ mWizardModel.registerListener(this);
+
+ mPagerAdapter = new MyPagerAdapter(getSupportFragmentManager());
+ mPager = (ViewPager) findViewById(R.id.pager);
+ mPager.setAdapter(mPagerAdapter);
+ mStepPagerStrip = (StepPagerStrip) findViewById(R.id.strip);
+ mStepPagerStrip.setOnPageSelectedListener(new StepPagerStrip.OnPageSelectedListener() {
+ @Override
+ public void onPageStripSelected(int position) {
+ position = Math.min(mPagerAdapter.getCount() - 1, position);
+ if (mPager.getCurrentItem() != position) {
+ mPager.setCurrentItem(position);
+ }
+ }
+ });
+
+ mNextButton = (Button) findViewById(R.id.next_button);
+ mPrevButton = (Button) findViewById(R.id.prev_button);
+
+ mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ mStepPagerStrip.setCurrentPage(position);
+
+ if (mConsumePageSelectedEvent) {
+ mConsumePageSelectedEvent = false;
+ return;
+ }
+
+ mEditingAfterReview = false;
+ updateBottomBar();
+ }
+ });
+
+ mNextButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mPager.getCurrentItem() == mCurrentPageSequence.size()) {
+ DialogFragment dg = new DialogFragment() {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.submit_confirm_message)
+ .setPositiveButton(R.string.submit_confirm_button, null)
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ }
+ };
+ dg.show(getSupportFragmentManager(), "place_order_dialog");
+ } else {
+ if (mEditingAfterReview) {
+ mPager.setCurrentItem(mPagerAdapter.getCount() - 1);
+ } else {
+ mPager.setCurrentItem(mPager.getCurrentItem() + 1);
+ }
+ }
+ }
+ });
+
+ mPrevButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mPager.setCurrentItem(mPager.getCurrentItem() - 1);
+ }
+ });
+
+ onPageTreeChanged();
+ updateBottomBar();
+ }
+
+ @Override
+ public void onPageTreeChanged() {
+ mCurrentPageSequence = mWizardModel.getCurrentPageSequence();
+ recalculateCutOffPage();
+ mStepPagerStrip.setPageCount(mCurrentPageSequence.size() + 1); // + 1 = review step
+ mPagerAdapter.notifyDataSetChanged();
+ updateBottomBar();
+ }
+
+ private void updateBottomBar() {
+ int position = mPager.getCurrentItem();
+ if (position == mCurrentPageSequence.size()) {
+ mNextButton.setText(R.string.finish);
+ } else {
+ mNextButton.setText(mEditingAfterReview
+ ? R.string.review
+ : R.string.next);
+ }
+
+ mPrevButton.setVisibility(position <= 0 ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mWizardModel.unregisterListener(this);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBundle("model", mWizardModel.save());
+ }
+
+ @Override
+ public AbstractWizardModel onGetModel() {
+ return mWizardModel;
+ }
+
+ @Override
+ public void onEditScreenAfterReview(String key) {
+ for (int i = mCurrentPageSequence.size() - 1; i >= 0; i--) {
+ if (mCurrentPageSequence.get(i).getKey().equals(key)) {
+ mConsumePageSelectedEvent = true;
+ mEditingAfterReview = true;
+ mPager.setCurrentItem(i);
+ updateBottomBar();
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onPageDataChanged(Page page) {
+ if (page.isRequired()) {
+ if (recalculateCutOffPage()) {
+ mPagerAdapter.notifyDataSetChanged();
+ updateBottomBar();
+ }
+ }
+ }
+
+ @Override
+ public Page onGetPage(String key) {
+ return mWizardModel.findByKey(key);
+ }
+
+ private boolean recalculateCutOffPage() {
+ // Cut off the pager adapter at first required page that isn't completed
+ int cutOffPage = mCurrentPageSequence.size() + 1;
+ for (int i = 0; i < mCurrentPageSequence.size(); i++) {
+ Page page = mCurrentPageSequence.get(i);
+ if (page.isRequired() && !page.isCompleted()) {
+ cutOffPage = i;
+ break;
+ }
+ }
+
+ if (mPagerAdapter.getCutOffPage() != cutOffPage) {
+ mPagerAdapter.setCutOffPage(cutOffPage);
+ return true;
+ }
+
+ return false;
+ }
+
+ public class MyPagerAdapter extends FragmentStatePagerAdapter {
+ private int mCutOffPage;
+ private Fragment mPrimaryItem;
+
+ public MyPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int i) {
+ if (i >= mCurrentPageSequence.size()) {
+ return new ReviewFragment();
+ }
+
+ return mCurrentPageSequence.get(i).createFragment();
+ }
+
+ @Override
+ public int getItemPosition(Object object) {
+ // TODO: be smarter about this
+ if (object == mPrimaryItem) {
+ // Re-use the current fragment (its position never changes)
+ return POSITION_UNCHANGED;
+ }
+
+ return POSITION_NONE;
+ }
+
+ @Override
+ public void setPrimaryItem(ViewGroup container, int position, Object object) {
+ super.setPrimaryItem(container, position, object);
+ mPrimaryItem = (Fragment) object;
+ }
+
+ @Override
+ public int getCount() {
+ if (mCurrentPageSequence == null) {
+ return 0;
+ }
+ return Math.min(mCutOffPage + 1, mCurrentPageSequence.size() + 1);
+ }
+
+ public void setCutOffPage(int cutOffPage) {
+ if (cutOffPage < 0) {
+ cutOffPage = Integer.MAX_VALUE;
+ }
+ mCutOffPage = cutOffPage;
+ }
+
+ public int getCutOffPage() {
+ return mCutOffPage;
+ }
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/PresentWizardModel.java b/app/src/main/java/com/markosullivan/wizards/PresentWizardModel.java
new file mode 100644
index 0000000..7055799
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/PresentWizardModel.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards;
+
+import android.content.Context;
+
+import com.markosullivan.wizards.wizard.model.AbstractWizardModel;
+import com.markosullivan.wizards.wizard.model.BranchPage;
+import com.markosullivan.wizards.wizard.model.InstructionPage;
+import com.markosullivan.wizards.wizard.model.MultipleFixedChoicePage;
+import com.markosullivan.wizards.wizard.model.PageList;
+import com.markosullivan.wizards.wizard.model.SingleFixedChoicePage;
+
+public class PresentWizardModel extends AbstractWizardModel {
+ public PresentWizardModel(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected PageList onNewRootPageList() {
+ return new PageList(
+
+ // BranchPage shows all of the branches available: Branch One, Branch Two, Branch Three. Each of these branches
+ // have their own questions and the choices of the user will be summarised in the review section at the end
+ new BranchPage(this, "Select one options")
+ .addBranch("Branch One",
+ new SingleFixedChoicePage(this, "Question One")
+ .setChoices("A", "B", "C", "D")
+ .setRequired(true),
+
+ new MultipleFixedChoicePage(this, "Question Two")
+ .setChoices("A", "B", "C", "D",
+ "E")
+ )
+
+ // Second branch of questions
+ .addBranch("Branch Two",
+ new SingleFixedChoicePage(this, "Question One")
+ .setChoices("A", "B")
+ .setRequired(true),
+
+ new SingleFixedChoicePage(this, "Question Two")
+ .setChoices("A", "B", "C",
+ "D", "E", "F")
+ .setRequired(true),
+
+ new SingleFixedChoicePage(this, "Question Three")
+ .setChoices("A", "B", "C")
+ )
+
+ // Third branch of questions
+ .addBranch("Branch Three",
+ new InstructionPage(this, "Info"),
+
+ new SingleFixedChoicePage(this, "Question One")
+ .setChoices("A", "B", "C")
+ .setRequired(true)
+ )
+ );
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/AbstractWizardModel.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/AbstractWizardModel.java
new file mode 100644
index 0000000..7e1725f
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/AbstractWizardModel.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a wizard model, including the pages/steps in the wizard, their dependencies, and their
+ * currently populated choices/values/selections.
+ *
+ * To create an actual wizard model, extend this class and implement {@link #onNewRootPageList()}.
+ */
+public abstract class AbstractWizardModel implements ModelCallbacks {
+ protected Context mContext;
+
+ private List mListeners = new ArrayList();
+ private PageList mRootPageList;
+
+ public AbstractWizardModel(Context context) {
+ mContext = context;
+ mRootPageList = onNewRootPageList();
+ }
+
+ /**
+ * Override this to define a new wizard model.
+ */
+ protected abstract PageList onNewRootPageList();
+
+ @Override
+ public void onPageDataChanged(Page page) {
+ // can't use for each because of concurrent modification (review fragment
+ // can get added or removed and will register itself as a listener)
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onPageDataChanged(page);
+ }
+ }
+
+ @Override
+ public void onPageTreeChanged() {
+ // can't use for each because of concurrent modification (review fragment
+ // can get added or removed and will register itself as a listener)
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onPageTreeChanged();
+ }
+ }
+
+ public Page findByKey(String key) {
+ return mRootPageList.findByKey(key);
+ }
+
+ public void load(Bundle savedValues) {
+ for (String key : savedValues.keySet()) {
+ mRootPageList.findByKey(key).resetData(savedValues.getBundle(key));
+ }
+ }
+
+ public void registerListener(ModelCallbacks listener) {
+ mListeners.add(listener);
+ }
+
+ public Bundle save() {
+ Bundle bundle = new Bundle();
+ for (Page page : getCurrentPageSequence()) {
+ bundle.putBundle(page.getKey(), page.getData());
+ }
+ return bundle;
+ }
+
+ /**
+ * Gets the current list of wizard steps, flattening nested (dependent) pages based on the
+ * user's choices.
+ */
+ public List getCurrentPageSequence() {
+ ArrayList flattened = new ArrayList();
+ mRootPageList.flattenCurrentPageSequence(flattened);
+ return flattened;
+ }
+
+ public void unregisterListener(ModelCallbacks listener) {
+ mListeners.remove(listener);
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/BranchPage.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/BranchPage.java
new file mode 100644
index 0000000..b1f3427
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/BranchPage.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.markosullivan.wizards.wizard.ui.SingleChoiceFragment;
+
+/**
+ * A page representing a branching point in the wizard. Depending on which choice is selected, the
+ * next set of steps in the wizard may change.
+ */
+public class BranchPage extends SingleFixedChoicePage {
+ private List mBranches = new ArrayList();
+
+ public BranchPage(ModelCallbacks callbacks, String title) {
+ super(callbacks, title);
+ }
+
+ @Override
+ public Page findByKey(String key) {
+ if (getKey().equals(key)) {
+ return this;
+ }
+
+ for (Branch branch : mBranches) {
+ Page found = branch.childPageList.findByKey(key);
+ if (found != null) {
+ return found;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void flattenCurrentPageSequence(ArrayList destination) {
+ super.flattenCurrentPageSequence(destination);
+ for (Branch branch : mBranches) {
+ if (branch.choice.equals(mData.getString(Page.SIMPLE_DATA_KEY))) {
+ branch.childPageList.flattenCurrentPageSequence(destination);
+ break;
+ }
+ }
+ }
+
+ public BranchPage addBranch(String choice, Page... childPages) {
+ PageList childPageList = new PageList(childPages);
+ for (Page page : childPageList) {
+ page.setParentKey(choice);
+ }
+ mBranches.add(new Branch(choice, childPageList));
+ return this;
+ }
+
+ public BranchPage addBranch(String choice) {
+ mBranches.add(new Branch(choice, new PageList()));
+ return this;
+ }
+
+ @Override
+ public Fragment createFragment() {
+ return SingleChoiceFragment.create(getKey());
+ }
+
+ public String getOptionAt(int position) {
+ return mBranches.get(position).choice;
+ }
+
+ public int getOptionCount() {
+ return mBranches.size();
+ }
+
+ @Override
+ public void getReviewItems(ArrayList dest) {
+ dest.add(new ReviewItem(getTitle(), mData.getString(SIMPLE_DATA_KEY), getKey()));
+ }
+
+ @Override
+ public boolean isCompleted() {
+ return !TextUtils.isEmpty(mData.getString(SIMPLE_DATA_KEY));
+ }
+
+ @Override
+ public void notifyDataChanged() {
+ mCallbacks.onPageTreeChanged();
+ super.notifyDataChanged();
+ }
+
+ public BranchPage setValue(String value) {
+ mData.putString(SIMPLE_DATA_KEY, value);
+ return this;
+ }
+
+ private static class Branch {
+ public String choice;
+ public PageList childPageList;
+
+ private Branch(String choice, PageList childPageList) {
+ this.choice = choice;
+ this.childPageList = childPageList;
+ }
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/CustomerInfoPage.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/CustomerInfoPage.java
new file mode 100644
index 0000000..aa3c8af
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/CustomerInfoPage.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+
+import com.markosullivan.wizards.wizard.ui.CustomerInfoFragment;
+
+import java.util.ArrayList;
+
+/**
+ * A page asking for a name and an email.
+ */
+public class CustomerInfoPage extends Page {
+ public static final String NAME_DATA_KEY = "name";
+ public static final String EMAIL_DATA_KEY = "email";
+
+ public CustomerInfoPage(ModelCallbacks callbacks, String title) {
+ super(callbacks, title);
+ }
+
+ @Override
+ public Fragment createFragment() {
+ return CustomerInfoFragment.create(getKey());
+ }
+
+ @Override
+ public void getReviewItems(ArrayList dest) {
+ dest.add(new ReviewItem("Your name", mData.getString(NAME_DATA_KEY), getKey(), -1));
+ dest.add(new ReviewItem("Your email", mData.getString(EMAIL_DATA_KEY), getKey(), -1));
+ }
+
+ @Override
+ public boolean isCompleted() {
+ return !TextUtils.isEmpty(mData.getString(NAME_DATA_KEY));
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/InstructionPage.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/InstructionPage.java
new file mode 100644
index 0000000..03fba67
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/InstructionPage.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+
+import com.markosullivan.wizards.wizard.ui.InstructionFragment;
+
+import java.util.ArrayList;
+
+/**
+ * A page offering the user a number of mutually exclusive choices.
+ */
+public class InstructionPage extends Page {
+ protected ArrayList mChoices = new ArrayList();
+
+ public InstructionPage(ModelCallbacks callbacks, String title) {
+ super(callbacks, title);
+ }
+
+ @Override
+ public Fragment createFragment() {
+ return InstructionFragment.create(getKey());
+ }
+
+ public String getOptionAt(int position) {
+ return mChoices.get(position);
+ }
+
+ public int getOptionCount() {
+ return mChoices.size();
+ }
+
+ @Override
+ public void getReviewItems(ArrayList dest) {
+
+ /*
+ The line below is commented out to prevent another ReviewItem being added to the review
+ at the end of the wizard. If you want to enable this the value displayed will be '(None)'
+ but you can change this value by changing the value inside mData.getString()
+ */
+
+
+ //dest.add(new ReviewItem(getTitle(), mData.getString(SIMPLE_DATA_KEY), getKey()));
+ }
+
+ @Override
+ public boolean isCompleted() {
+ return !TextUtils.isEmpty(mData.getString(SIMPLE_DATA_KEY));
+ }
+
+ public InstructionPage setValue(String value) {
+ mData.putString(SIMPLE_DATA_KEY, value);
+ return this;
+ }
+
+
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/ModelCallbacks.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/ModelCallbacks.java
new file mode 100644
index 0000000..6908976
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/ModelCallbacks.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+/**
+ * Callback interface connecting {@link Page}, {@link AbstractWizardModel}, and model container
+ * objects (e.g. {@link mos.present.MainActivity}.
+ */
+public interface ModelCallbacks {
+ void onPageDataChanged(Page page);
+ void onPageTreeChanged();
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/MultipleFixedChoicePage.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/MultipleFixedChoicePage.java
new file mode 100644
index 0000000..000ccef
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/MultipleFixedChoicePage.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+import android.support.v4.app.Fragment;
+
+import com.markosullivan.wizards.wizard.ui.MultipleChoiceFragment;
+
+import java.util.ArrayList;
+
+/**
+ * A page offering the user a number of non-mutually exclusive choices.
+ */
+public class MultipleFixedChoicePage extends SingleFixedChoicePage {
+ public MultipleFixedChoicePage(ModelCallbacks callbacks, String title) {
+ super(callbacks, title);
+ }
+
+ @Override
+ public Fragment createFragment() {
+ return MultipleChoiceFragment.create(getKey());
+ }
+
+ @Override
+ public void getReviewItems(ArrayList dest) {
+ StringBuilder sb = new StringBuilder();
+
+ ArrayList selections = mData.getStringArrayList(Page.SIMPLE_DATA_KEY);
+ if (selections != null && selections.size() > 0) {
+ for (String selection : selections) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(selection);
+ }
+ }
+
+ dest.add(new ReviewItem(getTitle(), sb.toString(), getKey()));
+ }
+
+ @Override
+ public boolean isCompleted() {
+ ArrayList selections = mData.getStringArrayList(Page.SIMPLE_DATA_KEY);
+ return selections != null && selections.size() > 0;
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/Page.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/Page.java
new file mode 100644
index 0000000..266c68f
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/Page.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+
+import java.util.ArrayList;
+
+/**
+ * Represents a single page in the wizard.
+ */
+public abstract class Page implements PageTreeNode {
+ /**
+ * The key into {@link #getData()} used for wizards with simple (single) values.
+ */
+ public static final String SIMPLE_DATA_KEY = "_";
+
+ protected ModelCallbacks mCallbacks;
+
+ /**
+ * Current wizard values/selections.
+ */
+ protected Bundle mData = new Bundle();
+ protected String mTitle;
+ protected boolean mRequired = false;
+ protected String mParentKey;
+
+ protected Page(ModelCallbacks callbacks, String title) {
+ mCallbacks = callbacks;
+ mTitle = title;
+ }
+
+ public Bundle getData() {
+ return mData;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public boolean isRequired() {
+ return mRequired;
+ }
+
+ void setParentKey(String parentKey) {
+ mParentKey = parentKey;
+ }
+
+ @Override
+ public Page findByKey(String key) {
+ return getKey().equals(key) ? this : null;
+ }
+
+ @Override
+ public void flattenCurrentPageSequence(ArrayList dest) {
+ dest.add(this);
+ }
+
+ public abstract Fragment createFragment();
+
+ public String getKey() {
+ return (mParentKey != null) ? mParentKey + ":" + mTitle : mTitle;
+ }
+
+ public abstract void getReviewItems(ArrayList dest);
+
+ public boolean isCompleted() {
+ return true;
+ }
+
+ public void resetData(Bundle data) {
+ mData = data;
+ notifyDataChanged();
+ }
+
+ public void notifyDataChanged() {
+ mCallbacks.onPageDataChanged(this);
+ }
+
+ public Page setRequired(boolean required) {
+ mRequired = required;
+ return this;
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/PageList.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/PageList.java
new file mode 100644
index 0000000..b0766c0
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/PageList.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+import java.util.ArrayList;
+
+/**
+ * Represents a list of wizard pages.
+ */
+public class PageList extends ArrayList implements PageTreeNode {
+
+ public PageList() {
+
+ }
+
+ public PageList(Page... pages) {
+ for (Page page : pages) {
+ add(page);
+ }
+ }
+
+ @Override
+ public Page findByKey(String key) {
+ for (Page childPage : this) {
+ Page found = childPage.findByKey(key);
+ if (found != null) {
+ return found;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void flattenCurrentPageSequence(ArrayList dest) {
+ for (Page childPage : this) {
+ childPage.flattenCurrentPageSequence(dest);
+ }
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/PageTreeNode.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/PageTreeNode.java
new file mode 100644
index 0000000..fd512da
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/PageTreeNode.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+import java.util.ArrayList;
+
+/**
+ * Represents a node in the page tree. Can either be a single page, or a page container.
+ */
+public interface PageTreeNode {
+ public Page findByKey(String key);
+ public void flattenCurrentPageSequence(ArrayList dest);
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/ReviewItem.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/ReviewItem.java
new file mode 100644
index 0000000..cb63657
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/ReviewItem.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+/**
+ * Represents a single line item on the final review page.
+ *
+ * @see com.markosullivan.wizards.wizard.ui.ReviewFragment
+ */
+public class ReviewItem {
+ public static final int DEFAULT_WEIGHT = 0;
+
+ private int mWeight;
+ private String mTitle;
+ private String mDisplayValue;
+ private String mPageKey;
+
+ public ReviewItem(String title, String displayValue, String pageKey) {
+ this(title, displayValue, pageKey, DEFAULT_WEIGHT);
+ }
+
+ public ReviewItem(String title, String displayValue, String pageKey, int weight) {
+ mTitle = title;
+ mDisplayValue = displayValue;
+ mPageKey = pageKey;
+ mWeight = weight;
+ }
+
+ public String getDisplayValue() {
+ return mDisplayValue;
+ }
+
+ public void setDisplayValue(String displayValue) {
+ mDisplayValue = displayValue;
+ }
+
+ public String getPageKey() {
+ return mPageKey;
+ }
+
+ public void setPageKey(String pageKey) {
+ mPageKey = pageKey;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ public int getWeight() {
+ return mWeight;
+ }
+
+ public void setWeight(int weight) {
+ mWeight = weight;
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/model/SingleFixedChoicePage.java b/app/src/main/java/com/markosullivan/wizards/wizard/model/SingleFixedChoicePage.java
new file mode 100644
index 0000000..4fb42f5
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/model/SingleFixedChoicePage.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.model;
+
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+
+import com.markosullivan.wizards.wizard.ui.SingleChoiceFragment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * A page offering the user a number of mutually exclusive choices.
+ */
+public class SingleFixedChoicePage extends Page {
+ protected ArrayList mChoices = new ArrayList();
+
+ public SingleFixedChoicePage(ModelCallbacks callbacks, String title) {
+ super(callbacks, title);
+ }
+
+ @Override
+ public Fragment createFragment() {
+ return SingleChoiceFragment.create(getKey());
+ }
+
+ public String getOptionAt(int position) {
+ return mChoices.get(position);
+ }
+
+ public int getOptionCount() {
+ return mChoices.size();
+ }
+
+ @Override
+ public void getReviewItems(ArrayList dest) {
+ dest.add(new ReviewItem(getTitle(), mData.getString(SIMPLE_DATA_KEY), getKey()));
+ }
+
+ @Override
+ public boolean isCompleted() {
+ return !TextUtils.isEmpty(mData.getString(SIMPLE_DATA_KEY));
+ }
+
+ public SingleFixedChoicePage setChoices(String... choices) {
+ mChoices.addAll(Arrays.asList(choices));
+ return this;
+ }
+
+ public SingleFixedChoicePage setValue(String value) {
+ mData.putString(SIMPLE_DATA_KEY, value);
+ return this;
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/ui/CustomerInfoFragment.java b/app/src/main/java/com/markosullivan/wizards/wizard/ui/CustomerInfoFragment.java
new file mode 100644
index 0000000..32558ef
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/ui/CustomerInfoFragment.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+
+import com.markosullivan.wizards.R;
+import com.markosullivan.wizards.wizard.model.CustomerInfoPage;
+
+public class CustomerInfoFragment extends Fragment {
+ private static final String ARG_KEY = "key";
+
+ private PageFragmentCallbacks mCallbacks;
+ private String mKey;
+ private CustomerInfoPage mPage;
+ private TextView mNameView;
+ private TextView mEmailView;
+
+ public static CustomerInfoFragment create(String key) {
+ Bundle args = new Bundle();
+ args.putString(ARG_KEY, key);
+
+ CustomerInfoFragment fragment = new CustomerInfoFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ public CustomerInfoFragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle args = getArguments();
+ mKey = args.getString(ARG_KEY);
+ mPage = (CustomerInfoPage) mCallbacks.onGetPage(mKey);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_page_customer_info, container, false);
+ ((TextView) rootView.findViewById(android.R.id.title)).setText(mPage.getTitle());
+
+ mNameView = ((TextView) rootView.findViewById(R.id.your_name));
+ mNameView.setText(mPage.getData().getString(CustomerInfoPage.NAME_DATA_KEY));
+
+ mEmailView = ((TextView) rootView.findViewById(R.id.your_email));
+ mEmailView.setText(mPage.getData().getString(CustomerInfoPage.EMAIL_DATA_KEY));
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ if (!(activity instanceof PageFragmentCallbacks)) {
+ throw new ClassCastException("Activity must implement PageFragmentCallbacks");
+ }
+
+ mCallbacks = (PageFragmentCallbacks) activity;
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mCallbacks = null;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mNameView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1,
+ int i2) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ mPage.getData().putString(CustomerInfoPage.NAME_DATA_KEY,
+ (editable != null) ? editable.toString() : null);
+ mPage.notifyDataChanged();
+ }
+ });
+
+ mEmailView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1,
+ int i2) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ mPage.getData().putString(CustomerInfoPage.EMAIL_DATA_KEY,
+ (editable != null) ? editable.toString() : null);
+ mPage.notifyDataChanged();
+ }
+ });
+ }
+
+ @Override
+ public void setMenuVisibility(boolean menuVisible) {
+ super.setMenuVisibility(menuVisible);
+
+ // In a future update to the support library, this should override setUserVisibleHint
+ // instead of setMenuVisibility.
+ if (mNameView != null) {
+ InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ if (!menuVisible) {
+ imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/ui/InstructionFragment.java b/app/src/main/java/com/markosullivan/wizards/wizard/ui/InstructionFragment.java
new file mode 100644
index 0000000..6700476
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/ui/InstructionFragment.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.markosullivan.wizards.R;
+import com.markosullivan.wizards.wizard.model.InstructionPage;
+import com.markosullivan.wizards.wizard.model.Page;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InstructionFragment extends Fragment {
+ private static final String ARG_KEY = "key";
+
+ private PageFragmentCallbacks mCallbacks;
+ private List mChoices;
+ private String mKey;
+ private Page mPage;
+
+ public static InstructionFragment create(String key) {
+ Bundle args = new Bundle();
+ args.putString(ARG_KEY, key);
+
+ InstructionFragment fragment = new InstructionFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ public InstructionFragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle args = getArguments();
+ mKey = args.getString(ARG_KEY);
+ mPage = mCallbacks.onGetPage(mKey);
+
+ InstructionPage fixedChoicePage = (InstructionPage) mPage;
+ mChoices = new ArrayList();
+ for (int i = 0; i < fixedChoicePage.getOptionCount(); i++) {
+ mChoices.add(fixedChoicePage.getOptionAt(i));
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_instruction, container, false);
+ ((TextView) rootView.findViewById(R.id.testingID)).setText("Help");
+
+
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ if (!(activity instanceof PageFragmentCallbacks)) {
+ throw new ClassCastException("Activity must implement PageFragmentCallbacks");
+ }
+
+ mCallbacks = (PageFragmentCallbacks) activity;
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mCallbacks = null;
+ }
+
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/ui/MultipleChoiceFragment.java b/app/src/main/java/com/markosullivan/wizards/wizard/ui/MultipleChoiceFragment.java
new file mode 100644
index 0000000..4f94dbd
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/ui/MultipleChoiceFragment.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.ListFragment;
+import android.util.SparseBooleanArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.markosullivan.wizards.R;
+import com.markosullivan.wizards.wizard.model.MultipleFixedChoicePage;
+import com.markosullivan.wizards.wizard.model.Page;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class MultipleChoiceFragment extends ListFragment {
+ private static final String ARG_KEY = "key";
+
+ private PageFragmentCallbacks mCallbacks;
+ private String mKey;
+ private List mChoices;
+ private Page mPage;
+
+ public static MultipleChoiceFragment create(String key) {
+ Bundle args = new Bundle();
+ args.putString(ARG_KEY, key);
+
+ MultipleChoiceFragment fragment = new MultipleChoiceFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ public MultipleChoiceFragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle args = getArguments();
+ mKey = args.getString(ARG_KEY);
+ mPage = mCallbacks.onGetPage(mKey);
+
+ MultipleFixedChoicePage fixedChoicePage = (MultipleFixedChoicePage) mPage;
+ mChoices = new ArrayList();
+ for (int i = 0; i < fixedChoicePage.getOptionCount(); i++) {
+ mChoices.add(fixedChoicePage.getOptionAt(i));
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_page, container, false);
+ ((TextView) rootView.findViewById(android.R.id.title)).setText(mPage.getTitle());
+
+ final ListView listView = (ListView) rootView.findViewById(android.R.id.list);
+ setListAdapter(new ArrayAdapter(getActivity(),
+ android.R.layout.simple_list_item_multiple_choice,
+ android.R.id.text1,
+ mChoices));
+ listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+
+ // Pre-select currently selected items.
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ ArrayList selectedItems = mPage.getData().getStringArrayList(
+ Page.SIMPLE_DATA_KEY);
+ if (selectedItems == null || selectedItems.size() == 0) {
+ return;
+ }
+
+ Set selectedSet = new HashSet(selectedItems);
+
+ for (int i = 0; i < mChoices.size(); i++) {
+ if (selectedSet.contains(mChoices.get(i))) {
+ listView.setItemChecked(i, true);
+ }
+ }
+ }
+ });
+
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ if (!(activity instanceof PageFragmentCallbacks)) {
+ throw new ClassCastException("Activity must implement PageFragmentCallbacks");
+ }
+
+ mCallbacks = (PageFragmentCallbacks) activity;
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mCallbacks = null;
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ SparseBooleanArray checkedPositions = getListView().getCheckedItemPositions();
+ ArrayList selections = new ArrayList();
+ for (int i = 0; i < checkedPositions.size(); i++) {
+ if (checkedPositions.valueAt(i)) {
+ selections.add(getListAdapter().getItem(checkedPositions.keyAt(i)).toString());
+ }
+ }
+
+ mPage.getData().putStringArrayList(Page.SIMPLE_DATA_KEY, selections);
+ mPage.notifyDataChanged();
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/ui/PageFragmentCallbacks.java b/app/src/main/java/com/markosullivan/wizards/wizard/ui/PageFragmentCallbacks.java
new file mode 100644
index 0000000..388ce8f
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/ui/PageFragmentCallbacks.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.ui;
+
+public interface PageFragmentCallbacks {
+ com.markosullivan.wizards.wizard.model.Page onGetPage(String key);
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/ui/ReviewFragment.java b/app/src/main/java/com/markosullivan/wizards/wizard/ui/ReviewFragment.java
new file mode 100644
index 0000000..3adfe12
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/ui/ReviewFragment.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.markosullivan.wizards.R;
+import com.markosullivan.wizards.wizard.model.AbstractWizardModel;
+import com.markosullivan.wizards.wizard.model.ModelCallbacks;
+import com.markosullivan.wizards.wizard.model.Page;
+import com.markosullivan.wizards.wizard.model.ReviewItem;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class ReviewFragment extends ListFragment implements ModelCallbacks {
+ private Callbacks mCallbacks;
+ private AbstractWizardModel mWizardModel;
+ private List mCurrentReviewItems;
+
+ private ReviewAdapter mReviewAdapter;
+
+ public ReviewFragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mReviewAdapter = new ReviewAdapter();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_page, container, false);
+
+ TextView titleView = (TextView) rootView.findViewById(android.R.id.title);
+ titleView.setText(R.string.review);
+ titleView.setTextColor(getResources().getColor(R.color.step_pager_selected_tab_color));
+
+ ListView listView = (ListView) rootView.findViewById(android.R.id.list);
+ setListAdapter(mReviewAdapter);
+ listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ if (!(activity instanceof Callbacks)) {
+ throw new ClassCastException("Activity must implement fragment's callbacks");
+ }
+
+ mCallbacks = (Callbacks) activity;
+
+ mWizardModel = mCallbacks.onGetModel();
+ mWizardModel.registerListener(this);
+ onPageTreeChanged();
+ }
+
+ @Override
+ public void onPageTreeChanged() {
+ onPageDataChanged(null);
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mCallbacks = null;
+
+ mWizardModel.unregisterListener(this);
+ }
+
+ @Override
+ public void onPageDataChanged(Page changedPage) {
+ ArrayList reviewItems = new ArrayList();
+ for (Page page : mWizardModel.getCurrentPageSequence()) {
+ page.getReviewItems(reviewItems);
+ }
+ Collections.sort(reviewItems, new Comparator() {
+ @Override
+ public int compare(ReviewItem a, ReviewItem b) {
+ return a.getWeight() > b.getWeight() ? +1 : a.getWeight() < b.getWeight() ? -1 : 0;
+ }
+ });
+ mCurrentReviewItems = reviewItems;
+
+ if (mReviewAdapter != null) {
+ mReviewAdapter.notifyDataSetInvalidated();
+ }
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ mCallbacks.onEditScreenAfterReview(mCurrentReviewItems.get(position).getPageKey());
+ }
+
+ public interface Callbacks {
+ AbstractWizardModel onGetModel();
+ void onEditScreenAfterReview(String pageKey);
+ }
+
+ private class ReviewAdapter extends BaseAdapter {
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mCurrentReviewItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mCurrentReviewItems.get(position).hashCode();
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup container) {
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ View rootView = inflater.inflate(R.layout.list_item_review, container, false);
+
+ ReviewItem reviewItem = mCurrentReviewItems.get(position);
+ String value = reviewItem.getDisplayValue();
+ if (TextUtils.isEmpty(value)) {
+ value = "(None)";
+ }
+ ((TextView) rootView.findViewById(android.R.id.text1)).setText(reviewItem.getTitle());
+ ((TextView) rootView.findViewById(android.R.id.text2)).setText(value);
+ return rootView;
+ }
+
+ @Override
+ public int getCount() {
+ return mCurrentReviewItems.size();
+ }
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/ui/SingleChoiceFragment.java b/app/src/main/java/com/markosullivan/wizards/wizard/ui/SingleChoiceFragment.java
new file mode 100644
index 0000000..a0419cb
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/ui/SingleChoiceFragment.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.ListFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.markosullivan.wizards.R;
+import com.markosullivan.wizards.wizard.model.Page;
+import com.markosullivan.wizards.wizard.model.SingleFixedChoicePage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SingleChoiceFragment extends ListFragment {
+ private static final String ARG_KEY = "key";
+
+ private PageFragmentCallbacks mCallbacks;
+ private List mChoices;
+ private String mKey;
+ private Page mPage;
+
+ public static SingleChoiceFragment create(String key) {
+ Bundle args = new Bundle();
+ args.putString(ARG_KEY, key);
+
+ SingleChoiceFragment fragment = new SingleChoiceFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ public SingleChoiceFragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle args = getArguments();
+ mKey = args.getString(ARG_KEY);
+ mPage = mCallbacks.onGetPage(mKey);
+
+ SingleFixedChoicePage fixedChoicePage = (SingleFixedChoicePage) mPage;
+ mChoices = new ArrayList();
+ for (int i = 0; i < fixedChoicePage.getOptionCount(); i++) {
+ mChoices.add(fixedChoicePage.getOptionAt(i));
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_page, container, false);
+ ((TextView) rootView.findViewById(android.R.id.title)).setText(mPage.getTitle());
+
+ final ListView listView = (ListView) rootView.findViewById(android.R.id.list);
+ setListAdapter(new ArrayAdapter(getActivity(),
+ android.R.layout.simple_list_item_single_choice,
+ android.R.id.text1,
+ mChoices));
+ listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+
+ // Pre-select currently selected item.
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ String selection = mPage.getData().getString(Page.SIMPLE_DATA_KEY);
+ for (int i = 0; i < mChoices.size(); i++) {
+ if (mChoices.get(i).equals(selection)) {
+ listView.setItemChecked(i, true);
+ break;
+ }
+ }
+ }
+ });
+
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ if (!(activity instanceof PageFragmentCallbacks)) {
+ throw new ClassCastException("Activity must implement PageFragmentCallbacks");
+ }
+
+ mCallbacks = (PageFragmentCallbacks) activity;
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mCallbacks = null;
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ mPage.getData().putString(Page.SIMPLE_DATA_KEY,
+ getListAdapter().getItem(position).toString());
+ mPage.notifyDataChanged();
+ }
+}
diff --git a/app/src/main/java/com/markosullivan/wizards/wizard/ui/StepPagerStrip.java b/app/src/main/java/com/markosullivan/wizards/wizard/ui/StepPagerStrip.java
new file mode 100644
index 0000000..2d53afb
--- /dev/null
+++ b/app/src/main/java/com/markosullivan/wizards/wizard/ui/StepPagerStrip.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.markosullivan.wizards.wizard.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.markosullivan.wizards.R;
+
+public class StepPagerStrip extends View {
+ private static final int[] ATTRS = new int[]{
+ android.R.attr.gravity
+ };
+ private int mPageCount;
+ private int mCurrentPage;
+
+ private int mGravity = Gravity.LEFT | Gravity.TOP;
+ private float mTabWidth;
+ private float mTabHeight;
+ private float mTabSpacing;
+
+ private Paint mPrevTabPaint;
+ private Paint mSelectedTabPaint;
+ private Paint mSelectedLastTabPaint;
+ private Paint mNextTabPaint;
+
+ private RectF mTempRectF = new RectF();
+
+ //private Scroller mScroller;
+
+ private OnPageSelectedListener mOnPageSelectedListener;
+
+ public StepPagerStrip(Context context) {
+ this(context, null, 0);
+ }
+
+ public StepPagerStrip(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public StepPagerStrip(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
+ mGravity = a.getInteger(0, mGravity);
+ a.recycle();
+
+ final Resources res = getResources();
+ mTabWidth = res.getDimensionPixelSize(R.dimen.step_pager_tab_width);
+ mTabHeight = res.getDimensionPixelSize(R.dimen.step_pager_tab_height);
+ mTabSpacing = res.getDimensionPixelSize(R.dimen.step_pager_tab_spacing);
+
+ mPrevTabPaint = new Paint();
+ mPrevTabPaint.setColor(res.getColor(R.color.step_pager_previous_tab_color));
+
+ mSelectedTabPaint = new Paint();
+ mSelectedTabPaint.setColor(res.getColor(R.color.step_pager_selected_tab_color));
+
+ mSelectedLastTabPaint = new Paint();
+ mSelectedLastTabPaint.setColor(res.getColor(R.color.step_pager_selected_tab_color));
+
+ mNextTabPaint = new Paint();
+ mNextTabPaint.setColor(res.getColor(R.color.step_pager_next_tab_color));
+ }
+
+ public void setOnPageSelectedListener(OnPageSelectedListener onPageSelectedListener) {
+ mOnPageSelectedListener = onPageSelectedListener;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mPageCount == 0) {
+ return;
+ }
+
+ float totalWidth = mPageCount * (mTabWidth + mTabSpacing) - mTabSpacing;
+ float totalLeft;
+ boolean fillHorizontal = false;
+
+ switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ totalLeft = (getWidth() - totalWidth) / 2;
+ break;
+ case Gravity.RIGHT:
+ totalLeft = getWidth() - getPaddingRight() - totalWidth;
+ break;
+ case Gravity.FILL_HORIZONTAL:
+ totalLeft = getPaddingLeft();
+ fillHorizontal = true;
+ break;
+ default:
+ totalLeft = getPaddingLeft();
+ }
+
+ switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
+ case Gravity.CENTER_VERTICAL:
+ mTempRectF.top = (int) (getHeight() - mTabHeight) / 2;
+ break;
+ case Gravity.BOTTOM:
+ mTempRectF.top = getHeight() - getPaddingBottom() - mTabHeight;
+ break;
+ default:
+ mTempRectF.top = getPaddingTop();
+ }
+
+ mTempRectF.bottom = mTempRectF.top + mTabHeight;
+
+ float tabWidth = mTabWidth;
+ if (fillHorizontal) {
+ tabWidth = (getWidth() - getPaddingRight() - getPaddingLeft()
+ - (mPageCount - 1) * mTabSpacing) / mPageCount;
+ }
+
+ for (int i = 0; i < mPageCount; i++) {
+ mTempRectF.left = totalLeft + (i * (tabWidth + mTabSpacing));
+ mTempRectF.right = mTempRectF.left + tabWidth;
+ canvas.drawRect(mTempRectF, i < mCurrentPage
+ ? mPrevTabPaint
+ : (i > mCurrentPage
+ ? mNextTabPaint
+ : (i == mPageCount - 1
+ ? mSelectedLastTabPaint
+ : mSelectedTabPaint)));
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(
+ View.resolveSize(
+ (int) (mPageCount * (mTabWidth + mTabSpacing) - mTabSpacing)
+ + getPaddingLeft() + getPaddingRight(),
+ widthMeasureSpec),
+ View.resolveSize(
+ (int) mTabHeight
+ + getPaddingTop() + getPaddingBottom(),
+ heightMeasureSpec));
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ scrollCurrentPageIntoView();
+ super.onSizeChanged(w, h, oldw, oldh);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mOnPageSelectedListener != null) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ int position = hitTest(event.getX());
+ if (position >= 0) {
+ mOnPageSelectedListener.onPageStripSelected(position);
+ }
+ return true;
+ }
+ }
+ return super.onTouchEvent(event);
+ }
+
+ private int hitTest(float x) {
+ if (mPageCount == 0) {
+ return -1;
+ }
+
+ float totalWidth = mPageCount * (mTabWidth + mTabSpacing) - mTabSpacing;
+ float totalLeft;
+ boolean fillHorizontal = false;
+
+ switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ totalLeft = (getWidth() - totalWidth) / 2;
+ break;
+ case Gravity.RIGHT:
+ totalLeft = getWidth() - getPaddingRight() - totalWidth;
+ break;
+ case Gravity.FILL_HORIZONTAL:
+ totalLeft = getPaddingLeft();
+ fillHorizontal = true;
+ break;
+ default:
+ totalLeft = getPaddingLeft();
+ }
+
+ float tabWidth = mTabWidth;
+ if (fillHorizontal) {
+ tabWidth = (getWidth() - getPaddingRight() - getPaddingLeft()
+ - (mPageCount - 1) * mTabSpacing) / mPageCount;
+ }
+
+ float totalRight = totalLeft + (mPageCount * (tabWidth + mTabSpacing));
+ if (x >= totalLeft && x <= totalRight && totalRight > totalLeft) {
+ return (int) (((x - totalLeft) / (totalRight - totalLeft)) * mPageCount);
+ } else {
+ return -1;
+ }
+ }
+
+ public void setCurrentPage(int currentPage) {
+ mCurrentPage = currentPage;
+ invalidate();
+ scrollCurrentPageIntoView();
+
+ // TODO: Set content description appropriately
+ }
+
+ private void scrollCurrentPageIntoView() {
+ // TODO: only works with left gravity for now
+//
+// float widthToActive = getPaddingLeft() + (mCurrentPage + 1) * (mTabWidth + mTabSpacing)
+// - mTabSpacing;
+// int viewWidth = getWidth();
+//
+// int startScrollX = getScrollX();
+// int destScrollX = (widthToActive > viewWidth) ? (int) (widthToActive - viewWidth) : 0;
+//
+// if (mScroller == null) {
+// mScroller = new Scroller(getContext());
+// }
+//
+// mScroller.abortAnimation();
+// mScroller.startScroll(startScrollX, 0, destScrollX - startScrollX, 0);
+// postInvalidate();
+ }
+
+ public void setPageCount(int count) {
+ mPageCount = count;
+ invalidate();
+
+ // TODO: Set content description appropriately
+ }
+
+ public static interface OnPageSelectedListener {
+ void onPageStripSelected(int position);
+ }
+
+//
+// @Override
+// public void computeScroll() {
+// super.computeScroll();
+// if (mScroller.computeScrollOffset()) {
+// setScrollX(mScroller.getCurrX());
+// }
+// }
+}
diff --git a/app/src/main/res/drawable/finish_background.xml b/app/src/main/res/drawable/finish_background.xml
new file mode 100644
index 0000000..81789e4
--- /dev/null
+++ b/app/src/main/res/drawable/finish_background.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/selectable_item_background.xml b/app/src/main/res/drawable/selectable_item_background.xml
new file mode 100644
index 0000000..718995f
--- /dev/null
+++ b/app/src/main/res/drawable/selectable_item_background.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..6a42e7b
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_instruction.xml b/app/src/main/res/layout/fragment_instruction.xml
new file mode 100644
index 0000000..ba5eb85
--- /dev/null
+++ b/app/src/main/res/layout/fragment_instruction.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_page.xml b/app/src/main/res/layout/fragment_page.xml
new file mode 100644
index 0000000..8c6a44d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_page.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_page_customer_info.xml b/app/src/main/res/layout/fragment_page_customer_info.xml
new file mode 100644
index 0000000..0cb0c44
--- /dev/null
+++ b/app/src/main/res/layout/fragment_page_customer_info.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/list_item_review.xml b/app/src/main/res/layout/list_item_review.xml
new file mode 100644
index 0000000..a7dae06
--- /dev/null
+++ b/app/src/main/res/layout/list_item_review.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..87a750e
--- /dev/null
+++ b/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..2b171e6
--- /dev/null
+++ b/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..78027ca
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,26 @@
+
+
+
+ #4433b5e5
+ #ff0099cc
+ #10000000
+
+
+
+ #66000000
+ #9E9E9E
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..d9c2c4f
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+
+
+
+ 32dp
+ 3dp
+ 4dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..9d0dce3
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,36 @@
+
+
+
+ Wizards
+
+ Next
+ Previous
+ Submit
+ Review
+
+ Name
+ Email
+ e.g. Larry
+ Optional
+
+ Are you happy with your choices?
+ OK
+
+ Settings
+ Herp derpsum sherpus derpler berps tee herp ter sherper der derpus terp? Merp ner, dee der perp.
+ Sherpus ter berp derpler derperker pee herpler. Le ner derpus terpus, der ter herpy? Ner terp serp herpem berps herpy der ter.
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..d9dcc07
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 5b6bf7e..d3ff69d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,42 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
buildscript {
repositories {
- mavenCentral()
+ jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.0.1'
- }
-}
-apply plugin: 'android'
+ classpath 'com.android.tools.build:gradle:1.1.0'
-dependencies {
- compile fileTree(dir: 'libs', include: '*.jar')
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
}
-android {
- compileSdkVersion 16
- buildToolsVersion "21.1.2"
-
- sourceSets {
- main {
- manifest.srcFile 'AndroidManifest.xml'
- java.srcDirs = ['src']
- resources.srcDirs = ['src']
- aidl.srcDirs = ['src']
- renderscript.srcDirs = ['src']
- res.srcDirs = ['res']
- assets.srcDirs = ['assets']
- }
-
- // Move the tests to tests/java, tests/res, etc...
- instrumentTest.setRoot('tests')
-
- // Move the build types to build-types/
- // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
- // This moves them out of them default location under src//... which would
- // conflict with src/ being used by the main source set.
- // Adding new build types or product flavors should be accompanied
- // by a similar customization.
- debug.setRoot('build-types/debug')
- release.setRoot('build-types/release')
+allprojects {
+ repositories {
+ jcenter()
}
-}
\ No newline at end of file
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'