diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5edb4ee
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+*.iml
+.gradle
+/local.properties
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser
new file mode 100644
index 0000000..98a40e8
Binary files /dev/null and b/.idea/caches/build_file_checksums.ser differ
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..30aa626
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..7ac24c7
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..2ce9f65
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
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/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..91cdbae
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,39 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 27
+ defaultConfig {
+ applicationId "lk.paradox.kekayan.fabfit"
+ minSdkVersion 19
+ targetSdkVersion 27
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'com.android.support:appcompat-v7:27.1.1'
+ implementation 'com.android.support:design:27.1.1'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.0'
+ implementation 'com.android.support:support-vector-drawable:27.1.1'
+ implementation 'com.android.support:support-v4:27.1.1'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation 'com.github.blackfizz:eazegraph:1.2.5l@aar'
+ implementation 'com.nineoldandroids:library:2.4.0'
+ implementation 'com.google.android.apps.dashclock:dashclock-api:2.0.0'
+ implementation 'com.github.j4velin.colorpicker:colorpicker:1.20.6'
+ implementation 'com.twitter.sdk.android:twitter:3.1.1'
+ implementation 'com.squareup.okhttp3:okhttp:3.9.0'
+ implementation 'com.squareup.picasso:picasso:2.5.2'
+
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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 *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/lk/paradox/kekayan/fabfit/ExampleInstrumentedTest.java b/app/src/androidTest/java/lk/paradox/kekayan/fabfit/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..3bd0bb5
--- /dev/null
+++ b/app/src/androidTest/java/lk/paradox/kekayan/fabfit/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package lk.paradox.kekayan.fabfit;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("lk.paradox.kekayan.fabfit", appContext.getPackageName());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9ef1b85
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/MainActivity.java b/app/src/main/java/lk/paradox/kekayan/fabfit/MainActivity.java
new file mode 100644
index 0000000..611ea36
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/MainActivity.java
@@ -0,0 +1,99 @@
+package lk.paradox.kekayan.fabfit;
+
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.BottomNavigationView;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import lk.paradox.kekayan.fabfit.fragments.ProfileFragment;
+import lk.paradox.kekayan.fabfit.fragments.StepsFragment;
+import lk.paradox.kekayan.fabfit.fragments.TweetsFragment;
+import lk.paradox.kekayan.fabfit.sensors.SensorListener;
+
+public class MainActivity extends AppCompatActivity {
+
+ private static final int TIME_INTERVAL = 2000;
+ private TextView mTextMessage;
+ private long mBackPressed;
+ private FragmentManager fm;
+ private FragmentTransaction ft;
+ //click events for bottom navigation buttons
+ private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
+ = new BottomNavigationView.OnNavigationItemSelectedListener() {
+
+ @Override
+ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.navigation_home:
+ StepsFragment stepsFragment = new StepsFragment();
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.frame_container, stepsFragment)
+ .addToBackStack(null)
+ .commit();
+
+ return true;
+ case R.id.navigation_tweets:
+ TweetsFragment tweetsFragment = new TweetsFragment();
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.frame_container, tweetsFragment)
+ .addToBackStack(null)
+ .commit();
+ return true;
+ case R.id.navigation_settings:
+ ProfileFragment profileFragment = new ProfileFragment();
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.frame_container, profileFragment)
+ .addToBackStack(null)
+ .commit();
+ return true;
+ }
+ return false;
+ }
+ };
+
+ @Override
+ public void onBackPressed() {
+
+ fm = getSupportFragmentManager();
+ ft = fm.beginTransaction();
+ if (fm.getBackStackEntryCount() > 0) {
+ fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
+ ft.commit();
+ } else if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) {
+ super.onBackPressed();
+ } else {
+ Toast.makeText(this, "Press again to exit the app", Toast.LENGTH_SHORT).show();
+ }
+ mBackPressed = System.currentTimeMillis();
+
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ //start the sensorlistener service
+ startService(new Intent(this, SensorListener.class));
+ //loading the stepsfragment as main when app launched
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ Fragment fragment = fragmentManager.findFragmentById(R.id.frame_container);
+
+ if (fragment == null) {
+ fragment = new StepsFragment();
+ fragmentManager.beginTransaction().add(R.id.frame_container, fragment).commit();
+ }
+
+ BottomNavigationView navigation = findViewById(R.id.navigation);
+ navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
+ }
+
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/SplashActivity.java b/app/src/main/java/lk/paradox/kekayan/fabfit/SplashActivity.java
new file mode 100644
index 0000000..c2063b6
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/SplashActivity.java
@@ -0,0 +1,20 @@
+package lk.paradox.kekayan.fabfit;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+public class SplashActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ //specify splash screen’s background as the activity’s theme background
+ //So we can avoid blank white page appears during splash launching
+ //we do not have setContentView() for this SplashActivity.
+ // View is displaying from the theme and this way it is faster than creating a layout
+ Intent intent = new Intent(this, MainActivity.class);
+ startActivity(intent);
+ finish();
+ }
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/db/Database.java b/app/src/main/java/lk/paradox/kekayan/fabfit/db/Database.java
new file mode 100644
index 0000000..e49821f
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/db/Database.java
@@ -0,0 +1,370 @@
+package lk.paradox.kekayan.fabfit.db;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import lk.paradox.kekayan.fabfit.BuildConfig;
+import lk.paradox.kekayan.fabfit.helpers.Logger;
+import lk.paradox.kekayan.fabfit.helpers.Util;
+
+
+public class Database extends SQLiteOpenHelper {
+
+ private final static String DB_NAME = "steps";
+ private final static int DB_VERSION = 2;
+ private static final AtomicInteger openCounter = new AtomicInteger();
+ private static Database instance;
+
+ private Database(final Context context) {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ public static synchronized Database getInstance(final Context c) {
+ if (instance == null) {
+ instance = new Database(c.getApplicationContext());
+ }
+ openCounter.incrementAndGet();
+ return instance;
+ }
+
+ @Override
+ public void close() {
+ if (openCounter.decrementAndGet() == 0) {
+ super.close();
+ }
+ }
+
+ @Override
+ public void onCreate(final SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + DB_NAME + " (date INTEGER, steps INTEGER)");
+ }
+
+ @Override
+ public void onUpgrade(final SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion == 1) {
+ // drop PRIMARY KEY constraint
+ db.execSQL("CREATE TABLE " + DB_NAME + "2 (date INTEGER, steps INTEGER)");
+ db.execSQL("INSERT INTO " + DB_NAME + "2 (date, steps) SELECT date, steps FROM " +
+ DB_NAME);
+ db.execSQL("DROP TABLE " + DB_NAME);
+ db.execSQL("ALTER TABLE " + DB_NAME + "2 RENAME TO " + DB_NAME + "");
+ }
+ }
+
+ /**
+ * Query the 'steps' table. Remember to close the cursor!
+ *
+ * @param columns the colums
+ * @param selection the selection
+ * @param selectionArgs the selction arguments
+ * @param groupBy the group by statement
+ * @param having the having statement
+ * @param orderBy the order by statement
+ * @return the cursor
+ */
+ public Cursor query(final String[] columns, final String selection,
+ final String[] selectionArgs, final String groupBy, final String having,
+ final String orderBy, final String limit) {
+ return getReadableDatabase()
+ .query(DB_NAME, columns, selection, selectionArgs, groupBy, having, orderBy, limit);
+ }
+
+ /**
+ * Inserts a new entry in the database, if there is no entry for the given
+ * date yet. Steps should be the current number of steps and it's negative
+ * value will be used as offset for the new date. Also adds 'steps' steps to
+ * the previous day, if there is an entry for that date.
+ *
+ * This method does nothing if there is already an entry for 'date' - use
+ * {@link } in this case.
+ *
+ * To restore data from a backup, use {@link #insertDayFromBackup}
+ *
+ * @param date the date in ms since 1970
+ * @param steps the current step value to be used as negative offset for the
+ * new day; must be >= 0
+ */
+ public void insertNewDay(long date, int steps) {
+ getWritableDatabase().beginTransaction();
+ try {
+ Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"date"}, "date = ?",
+ new String[]{String.valueOf(date)}, null, null, null);
+ if (c.getCount() == 0 && steps >= 0) {
+
+ // add 'steps' to yesterdays count
+ addToLastEntry(steps);
+
+ // add today
+ ContentValues values = new ContentValues();
+ values.put("date", date);
+ // use the negative steps as offset
+ values.put("steps", -steps);
+ getWritableDatabase().insert(DB_NAME, null, values);
+ }
+ c.close();
+ if (BuildConfig.DEBUG) {
+ Logger.log("insertDay " + date + " / " + steps);
+ logState();
+ }
+ getWritableDatabase().setTransactionSuccessful();
+ } finally {
+ getWritableDatabase().endTransaction();
+ }
+ }
+
+ /**
+ * Adds the given number of steps to the last entry in the database
+ *
+ * @param steps the number of steps to add. Must be > 0
+ */
+ public void addToLastEntry(int steps) {
+ if (steps > 0) {
+ getWritableDatabase().execSQL("UPDATE " + DB_NAME + " SET steps = steps + " + steps +
+ " WHERE date = (SELECT MAX(date) FROM " + DB_NAME + ")");
+ }
+ }
+
+ /**
+ * Inserts a new entry in the database, overwriting any existing entry for the given date.
+ * Use this method for restoring data from a backup.
+ *
+ * @param date the date in ms since 1970
+ * @param steps the step value for 'date'; must be >= 0
+ * @return true if a new entry was created, false if there was already an
+ * entry for 'date' (and it was overwritten)
+ */
+ public boolean insertDayFromBackup(long date, int steps) {
+ getWritableDatabase().beginTransaction();
+ boolean newEntryCreated = false;
+ try {
+ ContentValues values = new ContentValues();
+ values.put("steps", steps);
+ int updatedRows = getWritableDatabase()
+ .update(DB_NAME, values, "date = ?", new String[]{String.valueOf(date)});
+ if (updatedRows == 0) {
+ values.put("date", date);
+ getWritableDatabase().insert(DB_NAME, null, values);
+ newEntryCreated = true;
+ }
+ getWritableDatabase().setTransactionSuccessful();
+ } finally {
+ getWritableDatabase().endTransaction();
+ }
+ return newEntryCreated;
+ }
+
+ /**
+ * Writes the current steps database to the log
+ */
+ public void logState() {
+ if (BuildConfig.DEBUG) {
+ Cursor c = getReadableDatabase()
+ .query(DB_NAME, null, null, null, null, null, "date DESC", "5");
+ Logger.log(c);
+ c.close();
+ }
+ }
+
+ /**
+ * Get the total of steps taken without today's value
+ *
+ * @return number of steps taken, ignoring today
+ */
+ public int getTotalWithoutToday() {
+ Cursor c = getReadableDatabase()
+ .query(DB_NAME, new String[]{"SUM(steps)"}, "steps > 0 AND date > 0 AND date < ?",
+ new String[]{String.valueOf(Util.getToday())}, null, null, null);
+ c.moveToFirst();
+ int re = c.getInt(0);
+ c.close();
+ return re;
+ }
+
+ /**
+ * Get the maximum of steps walked in one day
+ *
+ * @return the maximum number of steps walked in one day
+ */
+ public int getRecord() {
+ Cursor c = getReadableDatabase()
+ .query(DB_NAME, new String[]{"MAX(steps)"}, "date > 0", null, null, null, null);
+ c.moveToFirst();
+ int re = c.getInt(0);
+ c.close();
+ return re;
+ }
+
+ /**
+ * Get the maximum of steps walked in one day and the date that happend
+ *
+ * @return a pair containing the date (Date) in millis since 1970 and the
+ * step value (Integer)
+ */
+ public Pair getRecordData() {
+ Cursor c = getReadableDatabase()
+ .query(DB_NAME, new String[]{"date, steps"}, "date > 0", null, null, null,
+ "steps DESC", "1");
+ c.moveToFirst();
+ Pair p = new Pair(new Date(c.getLong(0)), c.getInt(1));
+ c.close();
+ return p;
+ }
+
+ /**
+ * Get the number of steps taken for a specific date.
+ *
+ * If date is Util.getToday(), this method returns the offset which needs to
+ * be added to the value returned by getCurrentSteps() to get todays steps.
+ *
+ * @param date the date in millis since 1970
+ * @return the steps taken on this date or Integer.MIN_VALUE if date doesn't
+ * exist in the database
+ */
+ public int getSteps(final long date) {
+ Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"steps"}, "date = ?",
+ new String[]{String.valueOf(date)}, null, null, null);
+ c.moveToFirst();
+ int re;
+ if (c.getCount() == 0) re = Integer.MIN_VALUE;
+ else re = c.getInt(0);
+ c.close();
+ return re;
+ }
+
+ /**
+ * Gets the last num entries in descending order of date (newest first)
+ *
+ * @param num the number of entries to get
+ * @return a list of long,integer pair - the first being the date, the second the number of steps
+ */
+ public List> getLastEntries(int num) {
+ Cursor c = getReadableDatabase()
+ .query(DB_NAME, new String[]{"date", "steps"}, "date > 0", null, null, null,
+ "date DESC", String.valueOf(num));
+ int max = c.getCount();
+ List> result = new ArrayList<>(max);
+ if (c.moveToFirst()) {
+ do {
+ result.add(new Pair<>(c.getLong(0), c.getInt(1)));
+ } while (c.moveToNext());
+ }
+ return result;
+ }
+
+ /**
+ * Get the number of steps taken between 'start' and 'end' date
+ *
+ * Note that todays entry might have a negative value, so take care of that
+ * if 'end' >= Util.getToday()!
+ *
+ * @param start start date in ms since 1970 (steps for this date included)
+ * @param end end date in ms since 1970 (steps for this date included)
+ * @return the number of steps from 'start' to 'end'. Can be < 0 as todays
+ * entry might have negative value
+ */
+ public int getSteps(final long start, final long end) {
+ Cursor c = getReadableDatabase()
+ .query(DB_NAME, new String[]{"SUM(steps)"}, "date >= ? AND date <= ?",
+ new String[]{String.valueOf(start), String.valueOf(end)}, null, null, null);
+ int re;
+ if (c.getCount() == 0) {
+ re = 0;
+ } else {
+ c.moveToFirst();
+ re = c.getInt(0);
+ }
+ c.close();
+ return re;
+ }
+
+ /**
+ * Removes all entries with negative values.
+ *
+ * Only call this directly after boot, otherwise it might remove the current
+ * day as the current offset is likely to be negative
+ */
+ public void removeNegativeEntries() {
+ getWritableDatabase().delete(DB_NAME, "steps < ?", new String[]{"0"});
+ }
+
+ /**
+ * Removes invalid entries from the database.
+ *
+ * Currently, an invalid input is such with steps >= 200,000
+ */
+ public void removeInvalidEntries() {
+ getWritableDatabase().delete(DB_NAME, "steps >= ?", new String[]{"200000"});
+ }
+
+ /**
+ * Get the number of 'valid' days (= days with a step value > 0).
+ *
+ * The current day is not added to this number.
+ *
+ * @return the number of days with a step value > 0, return will be >= 0
+ */
+ public int getDaysWithoutToday() {
+ Cursor c = getReadableDatabase()
+ .query(DB_NAME, new String[]{"COUNT(*)"}, "steps > ? AND date < ? AND date > 0",
+ new String[]{String.valueOf(0), String.valueOf(Util.getToday())}, null,
+ null, null);
+ c.moveToFirst();
+ int re = c.getInt(0);
+ c.close();
+ return re < 0 ? 0 : re;
+ }
+
+ /**
+ * Get the number of 'valid' days (= days with a step value > 0).
+ *
+ * The current day is also added to this number, even if the value in the
+ * database might still be < 0.
+ *
+ * It is safe to divide by the return value as this will be at least 1 (and
+ * not 0).
+ *
+ * @return the number of days with a step value > 0, return will be >= 1
+ */
+ public int getDays() {
+ // todays is not counted yet
+ int re = this.getDaysWithoutToday() + 1;
+ return re;
+ }
+
+ /**
+ * Saves the current 'steps since boot' sensor value in the database.
+ *
+ * @param steps since boot
+ */
+ public void saveCurrentSteps(int steps) {
+ ContentValues values = new ContentValues();
+ values.put("steps", steps);
+ if (getWritableDatabase().update(DB_NAME, values, "date = -1", null) == 0) {
+ values.put("date", -1);
+ getWritableDatabase().insert(DB_NAME, null, values);
+ }
+ if (BuildConfig.DEBUG) {
+ Logger.log("saving steps in db: " + steps);
+ }
+ }
+
+ /**
+ * Reads the latest saved value for the 'steps since boot' sensor value.
+ *
+ * @return the current number of steps saved in the database or 0 if there
+ * is no entry
+ */
+ public int getCurrentSteps() {
+ int re = getSteps(-1);
+ return re == Integer.MIN_VALUE ? 0 : re;
+ }
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/fragments/ProfileFragment.java b/app/src/main/java/lk/paradox/kekayan/fabfit/fragments/ProfileFragment.java
new file mode 100644
index 0000000..1a31f91
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/fragments/ProfileFragment.java
@@ -0,0 +1,22 @@
+package lk.paradox.kekayan.fabfit.fragments;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import lk.paradox.kekayan.fabfit.R;
+
+public class ProfileFragment extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View view = inflater.inflate(R.layout.fragment_profile, container, false);
+ return view;
+ }
+
+
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/fragments/SettingsFragment.java b/app/src/main/java/lk/paradox/kekayan/fabfit/fragments/SettingsFragment.java
new file mode 100644
index 0000000..eac69be
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/fragments/SettingsFragment.java
@@ -0,0 +1,29 @@
+package lk.paradox.kekayan.fabfit.fragments;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Locale;
+
+import lk.paradox.kekayan.fabfit.R;
+
+
+public class SettingsFragment extends Fragment {
+ final static int DEFAULT_WEIGHT = 52;
+ final static int DEFAULT_GOAL = 10000;
+ final static float DEFAULT_STEP_SIZE = Locale.getDefault() == Locale.US ? 2.5f : 75f;
+ final static String DEFAULT_STEP_UNIT = Locale.getDefault() == Locale.US ? "ft" : "cm";
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_settings, container, false);
+ }
+
+
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/fragments/StepsFragment.java b/app/src/main/java/lk/paradox/kekayan/fabfit/fragments/StepsFragment.java
new file mode 100644
index 0000000..bd7f50d
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/fragments/StepsFragment.java
@@ -0,0 +1,265 @@
+package lk.paradox.kekayan.fabfit.fragments;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.eazegraph.lib.charts.PieChart;
+import org.eazegraph.lib.models.PieModel;
+
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.Objects;
+
+import lk.paradox.kekayan.fabfit.BuildConfig;
+import lk.paradox.kekayan.fabfit.R;
+import lk.paradox.kekayan.fabfit.db.Database;
+import lk.paradox.kekayan.fabfit.helpers.Logger;
+import lk.paradox.kekayan.fabfit.helpers.Util;
+
+
+public class StepsFragment extends Fragment implements SensorEventListener {
+
+ public final static NumberFormat formatter = NumberFormat.getInstance(Locale.getDefault());
+
+ ImageView footImage;
+ private TextView stepsView, totalView, averageView, caloriesView;
+ private PieModel sliceGoal, sliceCurrent;
+ private PieChart pg;
+ private int todayOffset, total_start, goal, since_boot, total_days;
+ private boolean showSteps = true;
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ final View v = inflater.inflate(R.layout.fragment_steps, null);
+
+ footImage = v.findViewById(R.id.stepsimageView);
+
+ stepsView = v.findViewById(R.id.steps);
+ totalView = v.findViewById(R.id.total);
+ averageView = v.findViewById(R.id.average);
+ caloriesView = v.findViewById(R.id.tv_calories);
+
+ pg = v.findViewById(R.id.graph);
+ pg.setInnerPadding(88.f);
+ // slice for the steps taken today
+ sliceCurrent = new PieModel("", 0, Color.parseColor("#99CC00"));
+ pg.addPieSlice(sliceCurrent);
+
+ // slice for the "missing" steps until reaching the goal
+ sliceGoal = new PieModel("", SettingsFragment.DEFAULT_GOAL, Color.parseColor("#CC0000"));
+ pg.addPieSlice(sliceGoal);
+
+ pg.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ showSteps = !showSteps;
+ stepsDistanceChanged();
+ }
+ });
+ pg.startAnimation();
+ return v;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+
+ Database db = Database.getInstance(getActivity());
+
+ if (BuildConfig.DEBUG) db.logState();
+ // read todays offset
+ todayOffset = db.getSteps(Util.getToday());
+
+ SharedPreferences prefs =
+ Objects.requireNonNull(getActivity()).getSharedPreferences("FabFit", Context.MODE_PRIVATE);
+
+ goal = prefs.getInt("goal", SettingsFragment.DEFAULT_GOAL);
+ since_boot = db.getCurrentSteps(); // do not use the value from the sharedPreferences
+ int pauseDifference = since_boot - prefs.getInt("pauseCount", since_boot);
+
+ // register a sensorlistener to live update the UI if a step is taken
+ if (!prefs.contains("pauseCount")) {
+ SensorManager sm =
+ (SensorManager) getActivity().getSystemService(Context.SENSOR_SERVICE);
+ Sensor sensor = sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
+ if (sensor == null) {
+ new AlertDialog.Builder(getActivity()).setTitle(R.string.no_sensor)
+ .setMessage(R.string.no_sensor_explain)
+ .setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(final DialogInterface dialogInterface) {
+ getActivity().finish();
+ }
+ }).setNeutralButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialogInterface, int i) {
+ dialogInterface.dismiss();
+ }
+ }).create().show();
+ } else {
+ sm.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI, 0);
+ }
+ }
+
+ since_boot -= pauseDifference;
+
+ total_start = db.getTotalWithoutToday();
+ total_days = db.getDays();
+
+ db.close();
+
+ stepsDistanceChanged();
+ }
+
+ /**
+ * Call this method if the Fragment should update the "steps"/"km" text in
+ * the pie graph as well as the pie and the bars graphs.
+ */
+ private void stepsDistanceChanged() {
+ if (showSteps) {
+ getView().findViewById(R.id.stepsimageView).setVisibility(View.VISIBLE);
+ getView().findViewById(R.id.unit).setVisibility(View.GONE);
+ ((TextView) getView().findViewById(R.id.unit)).setText("Steps");
+
+ } else {
+ String unit = getActivity().getSharedPreferences("FabFit", Context.MODE_PRIVATE)
+ .getString("stepsize_unit", SettingsFragment.DEFAULT_STEP_UNIT);
+ if (unit.equals("cm")) {
+ unit = "km";
+ } else {
+ unit = "mi";
+ }
+ getView().findViewById(R.id.stepsimageView).setVisibility(View.GONE);
+ ((TextView) getView().findViewById(R.id.unit)).setText(unit);
+ getView().findViewById(R.id.unit).setVisibility(View.VISIBLE);
+ }
+
+ updatePie();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ try {
+ SensorManager sm =
+ (SensorManager) getActivity().getSystemService(Context.SENSOR_SERVICE);
+ sm.unregisterListener(this);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ Database db = Database.getInstance(getActivity());
+ db.saveCurrentSteps(since_boot);
+ db.close();
+ }
+
+
+ @Override
+ public void onAccuracyChanged(final Sensor sensor, int accuracy) {
+ // won't happen
+ }
+
+ @Override
+ public void onSensorChanged(final SensorEvent event) {
+ if (BuildConfig.DEBUG)
+ Logger.log("UI - sensorChanged | todayOffset: " + todayOffset + " since boot: " +
+ event.values[0]);
+ if (event.values[0] > Integer.MAX_VALUE || event.values[0] == 0) {
+ return;
+ }
+ if (todayOffset == Integer.MIN_VALUE) {
+ // no values for today
+ // we dont know when the reboot was, so set todays steps to 0 by
+ // initializing them with -STEPS_SINCE_BOOT
+ todayOffset = -(int) event.values[0];
+ Database db = Database.getInstance(getActivity());
+ db.insertNewDay(Util.getToday(), (int) event.values[0]);
+ db.close();
+ }
+ since_boot = (int) event.values[0];
+ updatePie();
+ }
+
+ /**
+ * Updates the pie graph to show todays steps/distance as well as the
+ * yesterday and total values. Should be called when switching from step
+ * count to distance.
+ */
+ private void updatePie() {
+ if (BuildConfig.DEBUG) Logger.log("UI - update steps: " + since_boot);
+ // todayOffset might still be Integer.MIN_VALUE on first start
+ int steps_today = Math.max(todayOffset + since_boot, 0);
+ sliceCurrent.setValue(steps_today);
+ if (goal - steps_today > 0) {
+ // goal not reached yet
+ if (pg.getData().size() == 1) {
+ // can happen if the goal value was changed: old goal value was
+ // reached but now there are some steps missing for the new goal
+ pg.addPieSlice(sliceGoal);
+ }
+ sliceGoal.setValue(goal - steps_today);
+ } else {
+ // goal reached
+ pg.clearChart();
+ pg.addPieSlice(sliceCurrent);
+ }
+ pg.update();
+ if (showSteps) {
+ stepsView.setText(formatter.format(steps_today));
+ totalView.setText(formatter.format(total_start + steps_today));
+ averageView.setText(formatter.format((total_start + steps_today) / total_days));
+ caloriesView.setText(formatter.format(calculateCalories(steps_today)));
+ } else {
+ // update only every 10 steps when displaying distance
+ SharedPreferences prefs =
+ Objects.requireNonNull(getActivity()).getSharedPreferences("FabFit", Context.MODE_PRIVATE);
+ float stepsize = prefs.getFloat("stepsize_value", SettingsFragment.DEFAULT_STEP_SIZE);
+ float distance_today = steps_today * stepsize;
+ float distance_total = (total_start + steps_today) * stepsize;
+ if (prefs.getString("stepsize_unit", SettingsFragment.DEFAULT_STEP_UNIT)
+ .equals("cm")) {
+ distance_today /= 100000;
+ distance_total /= 100000;
+ } else {
+ distance_today /= 5280;
+ distance_total /= 5280;
+ }
+
+ stepsView.setText(formatter.format(distance_today));
+ totalView.setText(formatter.format(distance_total));
+ averageView.setText(formatter.format(distance_total / total_days));
+ caloriesView.setText(formatter.format(calculateCalories(steps_today)));
+
+ }
+ }
+
+ public double calculateCalories(int stepscount) {
+
+ d return mCalories;
+ }
+
+
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/helpers/Logger.java b/app/src/main/java/lk/paradox/kekayan/fabfit/helpers/Logger.java
new file mode 100644
index 0000000..34bea03
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/helpers/Logger.java
@@ -0,0 +1,69 @@
+package lk.paradox.kekayan.fabfit.helpers;
+
+import android.database.Cursor;
+import android.os.Environment;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Date;
+
+import lk.paradox.kekayan.fabfit.BuildConfig;
+
+public abstract class Logger {
+
+ private static final Date date = new Date();
+ private final static String APP = "FabFit";
+ private static FileWriter fw;
+
+ public static void log(Throwable ex) {
+ log(ex.getMessage());
+ for (StackTraceElement ste : ex.getStackTrace()) {
+ log(ste.toString());
+ }
+ }
+
+ public static void log(final Cursor c) {
+ if (!BuildConfig.DEBUG) return;
+ c.moveToFirst();
+ String title = "";
+ for (int i = 0; i < c.getColumnCount(); i++)
+ title += c.getColumnName(i) + "\t| ";
+ log(title);
+ while (!c.isAfterLast()) {
+ title = "";
+ for (int i = 0; i < c.getColumnCount(); i++)
+ title += c.getString(i) + "\t| ";
+ log(title);
+ c.moveToNext();
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public static void log(String msg) {
+ if (!BuildConfig.DEBUG) return;
+ android.util.Log.d(APP, msg);
+ try {
+ if (fw == null) {
+ fw = new FileWriter(new File(
+ Environment.getExternalStorageDirectory().toString() + "/" + APP + ".log"),
+ true);
+ }
+ date.setTime(System.currentTimeMillis());
+ fw.write(date.toLocaleString() + " - " + msg + "\n");
+ fw.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ // }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (fw != null) fw.close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/helpers/Util.java b/app/src/main/java/lk/paradox/kekayan/fabfit/helpers/Util.java
new file mode 100644
index 0000000..af4ed0d
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/helpers/Util.java
@@ -0,0 +1,33 @@
+package lk.paradox.kekayan.fabfit.helpers;
+
+import java.util.Calendar;
+
+public abstract class Util {
+
+ /**
+ * @return milliseconds since 1.1.1970 for today 0:00:00 local timezone
+ */
+ public static long getToday() {
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(System.currentTimeMillis());
+ c.set(Calendar.HOUR_OF_DAY, 0);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ return c.getTimeInMillis();
+ }
+
+ /**
+ * @return milliseconds since 1.1.1970 for tomorrow 0:00:01 local timezone
+ */
+ public static long getTomorrow() {
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(System.currentTimeMillis());
+ c.set(Calendar.HOUR_OF_DAY, 0);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 1);
+ c.set(Calendar.MILLISECOND, 0);
+ c.add(Calendar.DATE, 1);
+ return c.getTimeInMillis();
+ }
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/BootReceiver.java b/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/BootReceiver.java
new file mode 100644
index 0000000..6df379f
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/BootReceiver.java
@@ -0,0 +1,38 @@
+package lk.paradox.kekayan.fabfit.sensors;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import lk.paradox.kekayan.fabfit.BuildConfig;
+import lk.paradox.kekayan.fabfit.db.Database;
+import lk.paradox.kekayan.fabfit.helpers.Logger;
+
+public class BootReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (BuildConfig.DEBUG) Logger.log("booted");
+
+ SharedPreferences prefs = context.getSharedPreferences("FabFit", Context.MODE_PRIVATE);
+
+ Database db = Database.getInstance(context);
+
+ if (!prefs.getBoolean("correctShutdown", false)) {
+ if (BuildConfig.DEBUG) Logger.log("Incorrect shutdown");
+ // can we at least recover some steps?
+ int steps = db.getCurrentSteps();
+ if (BuildConfig.DEBUG) Logger.log("Trying to recover " + steps + " steps");
+ db.addToLastEntry(steps);
+ }
+ // last entry might still have a negative step value, so remove that
+ // row if that's the case
+ db.removeNegativeEntries();
+ db.saveCurrentSteps(0);
+ db.close();
+ prefs.edit().remove("correctShutdown").apply();
+
+ context.startService(new Intent(context, SensorListener.class));
+ }
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/PowerReceiver.java b/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/PowerReceiver.java
new file mode 100644
index 0000000..7949113
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/PowerReceiver.java
@@ -0,0 +1,25 @@
+package lk.paradox.kekayan.fabfit.sensors;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+public class PowerReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ SharedPreferences prefs =
+ context.getSharedPreferences("FabFit", Context.MODE_MULTI_PROCESS);
+ if (Intent.ACTION_POWER_CONNECTED.equals(intent.getAction()) &&
+ !prefs.contains("pauseCount")) {
+ // if power connected & not already paused, then pause now
+ context.startService(new Intent(context, SensorListener.class)
+ .putExtra("action", SensorListener.ACTION_PAUSE));
+ } else if (Intent.ACTION_POWER_DISCONNECTED.equals(intent.getAction()) &&
+ prefs.contains("pauseCount")) {
+ // if power disconnected & currently paused, then resume now
+ context.startService(new Intent(context, SensorListener.class)
+ .putExtra("action", SensorListener.ACTION_PAUSE));
+ }
+ }
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/SensorListener.java b/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/SensorListener.java
new file mode 100644
index 0000000..41b9b0d
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/SensorListener.java
@@ -0,0 +1,242 @@
+package lk.paradox.kekayan.fabfit.sensors;
+
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.IBinder;
+
+import java.text.NumberFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import lk.paradox.kekayan.fabfit.BuildConfig;
+import lk.paradox.kekayan.fabfit.MainActivity;
+import lk.paradox.kekayan.fabfit.R;
+import lk.paradox.kekayan.fabfit.db.Database;
+import lk.paradox.kekayan.fabfit.helpers.Logger;
+import lk.paradox.kekayan.fabfit.helpers.Util;
+import lk.paradox.kekayan.fabfit.widget.WidgetUpdateService;
+
+/**
+ * Background service which keeps the step-sensor listener alive to always get
+ * the number of steps since boot.
+ *
+ * This service won't be needed any more if there is a way to read the
+ * step-value without waiting for a sensor event
+ */
+public class SensorListener extends Service implements SensorEventListener {
+
+ public final static String ACTION_PAUSE = "pause";
+ public final static String ACTION_UPDATE_NOTIFICATION = "updateNotificationState";
+ private final static int NOTIFICATION_ID = 1;
+ private final static long MICROSECONDS_IN_ONE_MINUTE = 60000000;
+ private final static long SAVE_OFFSET_TIME = AlarmManager.INTERVAL_HOUR;
+ private final static int SAVE_OFFSET_STEPS = 500;
+ private static int steps;
+ private static int lastSaveSteps;
+ private static long lastSaveTime;
+
+ @Override
+ public void onAccuracyChanged(final Sensor sensor, int accuracy) {
+ // nobody knows what happens here: step value might magically decrease
+ // when this method is called...
+ if (BuildConfig.DEBUG) Logger.log(sensor.getName() + " accuracy changed: " + accuracy);
+ }
+
+ @Override
+ public void onSensorChanged(final SensorEvent event) {
+ if (event.values[0] > Integer.MAX_VALUE) {
+ if (BuildConfig.DEBUG) Logger.log("probably not a real value: " + event.values[0]);
+ return;
+ } else {
+ steps = (int) event.values[0];
+ updateIfNecessary();
+ }
+ }
+
+ private void updateIfNecessary() {
+ if (steps > lastSaveSteps + SAVE_OFFSET_STEPS ||
+ (steps > 0 && System.currentTimeMillis() > lastSaveTime + SAVE_OFFSET_TIME)) {
+ if (BuildConfig.DEBUG) Logger.log(
+ "saving steps: steps=" + steps + " lastSave=" + lastSaveSteps +
+ " lastSaveTime=" + new Date(lastSaveTime));
+ Database db = Database.getInstance(this);
+ if (db.getSteps(Util.getToday()) == Integer.MIN_VALUE) {
+ int pauseDifference = steps -
+ getSharedPreferences("FabFit", Context.MODE_PRIVATE)
+ .getInt("pauseCount", steps);
+ db.insertNewDay(Util.getToday(), steps - pauseDifference);
+ if (pauseDifference > 0) {
+ // update pauseCount for the new day
+ getSharedPreferences("FabFit", Context.MODE_PRIVATE).edit()
+ .putInt("pauseCount", steps).apply();
+ }
+ }
+ db.saveCurrentSteps(steps);
+ db.close();
+ lastSaveSteps = steps;
+ lastSaveTime = System.currentTimeMillis();
+ updateNotificationState();
+ startService(new Intent(this, WidgetUpdateService.class));
+ }
+ }
+
+ @Override
+ public IBinder onBind(final Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(final Intent intent, int flags, int startId) {
+ if (intent != null && ACTION_PAUSE.equals(intent.getStringExtra("action"))) {
+ if (BuildConfig.DEBUG)
+ Logger.log("onStartCommand action: " + intent.getStringExtra("action"));
+ if (steps == 0) {
+ Database db = Database.getInstance(this);
+ steps = db.getCurrentSteps();
+ db.close();
+ }
+ SharedPreferences prefs = getSharedPreferences("FabFit", Context.MODE_PRIVATE);
+ if (prefs.contains("pauseCount")) { // resume counting
+ int difference = steps -
+ prefs.getInt("pauseCount", steps); // number of steps taken during the pause
+ Database db = Database.getInstance(this);
+ db.addToLastEntry(-difference);
+ db.close();
+ prefs.edit().remove("pauseCount").apply();
+ updateNotificationState();
+ } else { // pause counting
+ // cancel restart
+ ((AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE))
+ .cancel(PendingIntent.getService(getApplicationContext(), 2,
+ new Intent(this, SensorListener.class),
+ PendingIntent.FLAG_UPDATE_CURRENT));
+ prefs.edit().putInt("pauseCount", steps).apply();
+ updateNotificationState();
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+ }
+
+ if (intent != null && intent.getBooleanExtra(ACTION_UPDATE_NOTIFICATION, false)) {
+ updateNotificationState();
+ } else {
+ updateIfNecessary();
+ }
+
+ // restart service every hour to save the current step count
+ ((AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE))
+ .set(AlarmManager.RTC, Math.min(Util.getTomorrow(),
+ System.currentTimeMillis() + AlarmManager.INTERVAL_HOUR), PendingIntent
+ .getService(getApplicationContext(), 2,
+ new Intent(this, SensorListener.class),
+ PendingIntent.FLAG_UPDATE_CURRENT));
+
+ return START_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (BuildConfig.DEBUG) Logger.log("SensorListener onCreate");
+ reRegisterSensor();
+ updateNotificationState();
+ }
+
+ @Override
+ public void onTaskRemoved(final Intent rootIntent) {
+ super.onTaskRemoved(rootIntent);
+ if (BuildConfig.DEBUG) Logger.log("sensor service task removed");
+ // Restart service in 500 ms
+ ((AlarmManager) getSystemService(Context.ALARM_SERVICE))
+ .set(AlarmManager.RTC, System.currentTimeMillis() + 500, PendingIntent
+ .getService(this, 3, new Intent(this, SensorListener.class), 0));
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (BuildConfig.DEBUG) Logger.log("SensorListener onDestroy");
+ try {
+ SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
+ sm.unregisterListener(this);
+ } catch (Exception e) {
+ if (BuildConfig.DEBUG) Logger.log(e);
+ e.printStackTrace();
+ }
+ }
+
+ private void updateNotificationState() {
+ if (BuildConfig.DEBUG) Logger.log("SensorListener updateNotificationState");
+ SharedPreferences prefs = getSharedPreferences("FabFit", Context.MODE_PRIVATE);
+ NotificationManager nm =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ if (prefs.getBoolean("notification", true)) {
+ int goal = prefs.getInt("goal", 10000);
+ Database db = Database.getInstance(this);
+ int today_offset = db.getSteps(Util.getToday());
+ if (steps == 0)
+ steps = db.getCurrentSteps(); // use saved value if we haven't anything better
+ db.close();
+ Notification.Builder notificationBuilder = new Notification.Builder(this);
+ if (steps > 0) {
+ if (today_offset == Integer.MIN_VALUE) today_offset = -steps;
+ notificationBuilder.setProgress(goal, today_offset + steps, false).setContentText(
+ today_offset + steps >= goal ? getString(R.string.goal_reached_notification,
+ NumberFormat.getInstance(Locale.getDefault())
+ .format((today_offset + steps))) :
+ getString(R.string.notification_text,
+ NumberFormat.getInstance(Locale.getDefault())
+ .format((goal - today_offset - steps))));
+ } else { // still no step value?
+ notificationBuilder
+ .setContentText(getString(R.string.your_progress_will_be_shown_here_soon));
+ }
+ boolean isPaused = prefs.contains("pauseCount");
+ notificationBuilder.setPriority(Notification.PRIORITY_MIN).setShowWhen(false)
+ .setContentTitle(isPaused ? getString(R.string.ispaused) :
+ getString(R.string.notification_title)).setContentIntent(PendingIntent
+ .getActivity(this, 0, new Intent(this, MainActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .setSmallIcon(R.drawable.ic_notification)
+ .addAction(isPaused ? R.drawable.ic_resume : R.drawable.ic_pause,
+ isPaused ? getString(R.string.resume) : getString(R.string.pause),
+ PendingIntent.getService(this, 4, new Intent(this, SensorListener.class)
+ .putExtra("action", ACTION_PAUSE),
+ PendingIntent.FLAG_UPDATE_CURRENT)).setOngoing(true);
+ nm.notify(NOTIFICATION_ID, notificationBuilder.build());
+ } else {
+ nm.cancel(NOTIFICATION_ID);
+ }
+ }
+
+ private void reRegisterSensor() {
+ if (BuildConfig.DEBUG) Logger.log("re-register sensor listener");
+ SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
+ try {
+ sm.unregisterListener(this);
+ } catch (Exception e) {
+ if (BuildConfig.DEBUG) Logger.log(e);
+ e.printStackTrace();
+ }
+
+ if (BuildConfig.DEBUG) {
+ Logger.log("step sensors: " + sm.getSensorList(Sensor.TYPE_STEP_COUNTER).size());
+ if (sm.getSensorList(Sensor.TYPE_STEP_COUNTER).size() < 1) return; // emulator
+ Logger.log("default: " + sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER).getName());
+ }
+
+ // enable batching with delay of max 5 min
+ sm.registerListener(this, sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER),
+ SensorManager.SENSOR_DELAY_NORMAL, (int) (5 * MICROSECONDS_IN_ONE_MINUTE));
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/ShutdownRecevier.java b/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/ShutdownRecevier.java
new file mode 100644
index 0000000..b8d210b
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/sensors/ShutdownRecevier.java
@@ -0,0 +1,47 @@
+package lk.paradox.kekayan.fabfit.sensors;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import lk.paradox.kekayan.fabfit.BuildConfig;
+import lk.paradox.kekayan.fabfit.db.Database;
+import lk.paradox.kekayan.fabfit.helpers.Logger;
+import lk.paradox.kekayan.fabfit.helpers.Util;
+
+public class ShutdownRecevier extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (BuildConfig.DEBUG) Logger.log("shutting down");
+
+ context.startService(new Intent(context, SensorListener.class));
+
+ // if the user used a root script for shutdown, the DEVICE_SHUTDOWN
+ // broadcast might not be send. Therefore, the app will check this
+ // setting on the next boot and displays an error message if it's not
+ // set to true
+ context.getSharedPreferences("FabFit", Context.MODE_PRIVATE).edit()
+ .putBoolean("correctShutdown", true).apply();
+
+ Database db = Database.getInstance(context);
+ // if it's already a new day, add the temp. steps to the last one
+ if (db.getSteps(Util.getToday()) == Integer.MIN_VALUE) {
+ int steps = db.getCurrentSteps();
+ int pauseDifference = steps -
+ context.getSharedPreferences("FabFit", Context.MODE_PRIVATE)
+ .getInt("pauseCount", steps);
+ db.insertNewDay(Util.getToday(), steps - pauseDifference);
+ if (pauseDifference > 0) {
+ // update pauseCount for the new day
+ context.getSharedPreferences("FabFit", Context.MODE_PRIVATE).edit()
+ .putInt("pauseCount", steps).apply();
+ }
+ } else {
+ db.addToLastEntry(db.getCurrentSteps());
+ }
+ // current steps will be reset on boot @see BootReceiver
+ db.close();
+ }
+
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/widget/DashClock.java b/app/src/main/java/lk/paradox/kekayan/fabfit/widget/DashClock.java
new file mode 100644
index 0000000..3e2b66e
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/widget/DashClock.java
@@ -0,0 +1,32 @@
+package lk.paradox.kekayan.fabfit.widget;
+
+import android.content.Intent;
+
+import com.google.android.apps.dashclock.api.DashClockExtension;
+import com.google.android.apps.dashclock.api.ExtensionData;
+
+import lk.paradox.kekayan.fabfit.MainActivity;
+import lk.paradox.kekayan.fabfit.R;
+import lk.paradox.kekayan.fabfit.db.Database;
+import lk.paradox.kekayan.fabfit.fragments.StepsFragment;
+import lk.paradox.kekayan.fabfit.helpers.Util;
+
+/**
+ * Class for providing a DashClock (https://code.google.com/p/dashclock)
+ * extension
+ */
+public class DashClock extends DashClockExtension {
+
+ @Override
+ protected void onUpdateData(int reason) {
+ ExtensionData data = new ExtensionData();
+ Database db = Database.getInstance(this);
+ int steps = Math.max(db.getCurrentSteps() + db.getSteps(Util.getToday()), 0);
+ data.visible(true).status(StepsFragment.formatter.format(steps))
+ .icon(R.drawable.ic_dashclock)
+ .clickIntent(new Intent(DashClock.this, MainActivity.class));
+ db.close();
+ publishUpdate(data);
+ }
+
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/widget/Widget.java b/app/src/main/java/lk/paradox/kekayan/fabfit/widget/Widget.java
new file mode 100644
index 0000000..f53f843
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/widget/Widget.java
@@ -0,0 +1,38 @@
+package lk.paradox.kekayan.fabfit.widget;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.widget.RemoteViews;
+
+import lk.paradox.kekayan.fabfit.MainActivity;
+import lk.paradox.kekayan.fabfit.R;
+
+public class Widget extends AppWidgetProvider {
+
+ static RemoteViews updateWidget(final int appWidgetId, final Context context, final int steps) {
+ final SharedPreferences prefs = context.getSharedPreferences("Widgets",
+ Context.MODE_PRIVATE);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId,
+ new Intent(context, MainActivity.class), 0);
+
+ final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
+ views.setOnClickPendingIntent(R.id.widget, pendingIntent);
+ views.setTextColor(R.id.widgetsteps, prefs.getInt("color_" + appWidgetId, Color.WHITE));
+ views.setTextViewText(R.id.widgetsteps, String.valueOf(steps));
+ views.setTextColor(R.id.widgettext, prefs.getInt("color_" + appWidgetId, Color.WHITE));
+ views.setInt(R.id.widget, "setBackgroundColor",
+ prefs.getInt("background_" + appWidgetId, Color.TRANSPARENT));
+ return views;
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ context.startService(new Intent(context, WidgetUpdateService.class));
+ }
+
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/widget/WidgetConfig.java b/app/src/main/java/lk/paradox/kekayan/fabfit/widget/WidgetConfig.java
new file mode 100644
index 0000000..d146639
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/widget/WidgetConfig.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2014 Thomas Hoffmann
+ *
+ * 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 lk.paradox.kekayan.fabfit.widget;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import de.j4velin.lib.colorpicker.ColorPickerDialog;
+import de.j4velin.lib.colorpicker.ColorPreviewButton;
+import lk.paradox.kekayan.fabfit.R;
+
+public class WidgetConfig extends Activity implements OnClickListener {
+
+ private static int widgetId;
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ startService(new Intent(this, WidgetUpdateService.class));
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Intent intent = getIntent();
+ final Bundle extras = intent.getExtras();
+ if (extras != null) {
+ setContentView(R.layout.widgetconfig);
+
+ ColorPreviewButton textcolor = findViewById(R.id.textcolor);
+ textcolor.setOnClickListener(this);
+ textcolor.setColor(Color.WHITE);
+ ColorPreviewButton bgcolor = findViewById(R.id.bgcolor);
+ bgcolor.setOnClickListener(this);
+ bgcolor.setColor(Color.TRANSPARENT);
+
+ widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+
+ final Intent resultValue = new Intent();
+ resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+ setResult(RESULT_OK, resultValue);
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ public void onClick(final View v) {
+ ColorPickerDialog dialog = new ColorPickerDialog(this,
+ (findViewById(v.getId()).getTag() != null) ?
+ (Integer) findViewById(v.getId()).getTag() : -1);
+ dialog.setHexValueEnabled(true);
+ dialog.setAlphaSliderVisible(true);
+ dialog.setOnColorChangedListener(new ColorPickerDialog.OnColorChangedListener() {
+ @Override
+ public void onColorChanged(int color) {
+ ((ColorPreviewButton) v).setColor(color);
+ v.setTag(color);
+ getSharedPreferences("Widgets", Context.MODE_PRIVATE).edit()
+ .putInt((v.getId() == R.id.bgcolor ? "background_" : "color_") + widgetId,
+ color).apply();
+ }
+ });
+ dialog.show();
+ }
+
+}
diff --git a/app/src/main/java/lk/paradox/kekayan/fabfit/widget/WidgetUpdateService.java b/app/src/main/java/lk/paradox/kekayan/fabfit/widget/WidgetUpdateService.java
new file mode 100644
index 0000000..411baaa
--- /dev/null
+++ b/app/src/main/java/lk/paradox/kekayan/fabfit/widget/WidgetUpdateService.java
@@ -0,0 +1,33 @@
+package lk.paradox.kekayan.fabfit.widget;
+
+import android.app.Service;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.IBinder;
+
+import lk.paradox.kekayan.fabfit.db.Database;
+import lk.paradox.kekayan.fabfit.helpers.Util;
+
+public class WidgetUpdateService extends Service {
+
+ @Override
+ public IBinder onBind(final Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Database db = Database.getInstance(this);
+ int steps = Math.max(db.getCurrentSteps() + db.getSteps(Util.getToday()), 0);
+ db.close();
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(WidgetUpdateService.this);
+ int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(WidgetUpdateService.this, Widget.class));
+ for (int appWidgetId : appWidgetIds) {
+ appWidgetManager.updateAppWidget(appWidgetId, Widget.updateWidget(appWidgetId, WidgetUpdateService.this, steps));
+ }
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+}
diff --git a/app/src/main/res/drawable-hdpi/fabfitlogo.png b/app/src/main/res/drawable-hdpi/fabfitlogo.png
new file mode 100644
index 0000000..2b5301f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/fabfitlogo.png differ
diff --git a/app/src/main/res/drawable-hdpi/footicon.png b/app/src/main/res/drawable-hdpi/footicon.png
new file mode 100644
index 0000000..7ed3ed7
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/footicon.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_dashclock.png b/app/src/main/res/drawable-hdpi/ic_dashclock.png
new file mode 100644
index 0000000..2348803
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_dashclock.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notification.png b/app/src/main/res/drawable-hdpi/ic_notification.png
new file mode 100644
index 0000000..991832a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notification.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_pause.png b/app/src/main/res/drawable-hdpi/ic_pause.png
new file mode 100644
index 0000000..307bf26
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_pause.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_resume.png b/app/src/main/res/drawable-hdpi/ic_resume.png
new file mode 100644
index 0000000..6384a4e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_resume.png differ
diff --git a/app/src/main/res/drawable-mdpi/fabfitlogo.png b/app/src/main/res/drawable-mdpi/fabfitlogo.png
new file mode 100644
index 0000000..21f4da4
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/fabfitlogo.png differ
diff --git a/app/src/main/res/drawable-mdpi/footicon.png b/app/src/main/res/drawable-mdpi/footicon.png
new file mode 100644
index 0000000..4151439
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/footicon.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_dashclock.png b/app/src/main/res/drawable-mdpi/ic_dashclock.png
new file mode 100644
index 0000000..ae07882
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_dashclock.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification.png b/app/src/main/res/drawable-mdpi/ic_notification.png
new file mode 100644
index 0000000..85956e3
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notification.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_pause.png b/app/src/main/res/drawable-mdpi/ic_pause.png
new file mode 100644
index 0000000..89a6735
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_pause.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_resume.png b/app/src/main/res/drawable-mdpi/ic_resume.png
new file mode 100644
index 0000000..4552ae6
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_resume.png differ
diff --git a/app/src/main/res/drawable-v21/background.xml b/app/src/main/res/drawable-v21/background.xml
new file mode 100644
index 0000000..e00212f
--- /dev/null
+++ b/app/src/main/res/drawable-v21/background.xml
@@ -0,0 +1,24 @@
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..c7bd21d
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-xhdpi/fabfitlogo.png b/app/src/main/res/drawable-xhdpi/fabfitlogo.png
new file mode 100644
index 0000000..68e3562
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/fabfitlogo.png differ
diff --git a/app/src/main/res/drawable-xhdpi/footicon.png b/app/src/main/res/drawable-xhdpi/footicon.png
new file mode 100644
index 0000000..735ce2b
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/footicon.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_dashclock.png b/app/src/main/res/drawable-xhdpi/ic_dashclock.png
new file mode 100644
index 0000000..5f58071
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_dashclock.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification.png b/app/src/main/res/drawable-xhdpi/ic_notification.png
new file mode 100644
index 0000000..f5fa4cc
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notification.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_pause.png b/app/src/main/res/drawable-xhdpi/ic_pause.png
new file mode 100644
index 0000000..ab20740
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pause.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_resume.png b/app/src/main/res/drawable-xhdpi/ic_resume.png
new file mode 100644
index 0000000..652ac68
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_resume.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/fabfitlogo.png b/app/src/main/res/drawable-xxhdpi/fabfitlogo.png
new file mode 100644
index 0000000..3e0d830
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/fabfitlogo.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/footicon.png b/app/src/main/res/drawable-xxhdpi/footicon.png
new file mode 100644
index 0000000..ac5ee29
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/footicon.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_dashclock.png b/app/src/main/res/drawable-xxhdpi/ic_dashclock.png
new file mode 100644
index 0000000..318bb55
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_dashclock.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification.png b/app/src/main/res/drawable-xxhdpi/ic_notification.png
new file mode 100644
index 0000000..741e767
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notification.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_pause.png b/app/src/main/res/drawable-xxhdpi/ic_pause.png
new file mode 100644
index 0000000..c457d4e
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pause.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_resume.png b/app/src/main/res/drawable-xxhdpi/ic_resume.png
new file mode 100644
index 0000000..14293ac
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_resume.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/fabfitlogo.png b/app/src/main/res/drawable-xxxhdpi/fabfitlogo.png
new file mode 100644
index 0000000..ae5f60a
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/fabfitlogo.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/footicon.png b/app/src/main/res/drawable-xxxhdpi/footicon.png
new file mode 100644
index 0000000..ca2d9b5
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/footicon.png differ
diff --git a/app/src/main/res/drawable/background.xml b/app/src/main/res/drawable/background.xml
new file mode 100644
index 0000000..e00212f
--- /dev/null
+++ b/app/src/main/res/drawable/background.xml
@@ -0,0 +1,24 @@
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_gradient.xml b/app/src/main/res/drawable/bg_gradient.xml
new file mode 100644
index 0000000..2a94bc9
--- /dev/null
+++ b/app/src/main/res/drawable/bg_gradient.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml
new file mode 100644
index 0000000..ae6a446
--- /dev/null
+++ b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml
new file mode 100644
index 0000000..0c36320
--- /dev/null
+++ b/app/src/main/res/drawable/ic_home_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..d5fccc5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_person.xml b/app/src/main/res/drawable/ic_person.xml
new file mode 100644
index 0000000..94cae9c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_person.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..6d0c6e6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_walk.xml b/app/src/main/res/drawable/ic_walk.xml
new file mode 100644
index 0000000..cf8e38b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_walk.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/splash_bg.xml b/app/src/main/res/drawable/splash_bg.xml
new file mode 100644
index 0000000..70313f7
--- /dev/null
+++ b/app/src/main/res/drawable/splash_bg.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml
new file mode 100644
index 0000000..e8d9549
--- /dev/null
+++ b/app/src/main/res/layout/about.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
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..fe699d5
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml
new file mode 100644
index 0000000..dcce73f
--- /dev/null
+++ b/app/src/main/res/layout/activity_splash.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml
new file mode 100644
index 0000000..b158645
--- /dev/null
+++ b/app/src/main/res/layout/fragment_profile.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
new file mode 100644
index 0000000..453d34f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_steps.xml b/app/src/main/res/layout/fragment_steps.xml
new file mode 100644
index 0000000..027f87c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_steps.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_tweets.xml b/app/src/main/res/layout/fragment_tweets.xml
new file mode 100644
index 0000000..b2f41e1
--- /dev/null
+++ b/app/src/main/res/layout/fragment_tweets.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget.xml b/app/src/main/res/layout/widget.xml
new file mode 100644
index 0000000..c4a472c
--- /dev/null
+++ b/app/src/main/res/layout/widget.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widgetconfig.xml b/app/src/main/res/layout/widgetconfig.xml
new file mode 100644
index 0000000..1110105
--- /dev/null
+++ b/app/src/main/res/layout/widgetconfig.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/navigation.xml b/app/src/main/res/menu/navigation.xml
new file mode 100644
index 0000000..ead5198
--- /dev/null
+++ b/app/src/main/res/menu/navigation.xml
@@ -0,0 +1,23 @@
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
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..a2f5908
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1b52399
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.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..ff10afd
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..115a4c7
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.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..dcd3cd8
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..459ca60
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.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..8ca12fe
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..8e19b41
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b824ebd
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4c19a13
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c7cba40
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #141E30
+ #243B55
+ #FF4081
+ #ffffff
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..59f0483
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,35 @@
+
+ FabFit
+ Steps
+ Tweets
+ Settings
+ Sensor not found
+ This app requires a dedicated hardware step sensor - which your device does not have. This app won\'t run on your device.
+ No permission to access external storage
+
+ Hello blank fragment
+ Hello blank fragment1
+ Hello blank fragment2
+ Goal reached! %s steps and counting
+ FabFit is counting
+ %s steps to go
+ Resume
+ Pause
+ FabFit is paused
+ Your progress will be shown here soon
+ Text color:
+ Background color:
+ Profile
+
+ No55ivtC0J45mPdDjdxMcv82n
+ peLL4vdtUiWXiOKSDAnwqpCzBe3P2lxLr787sdO1ffxp2GXQwD
+ Calories
+ 10.000
+ stepicon
+ steps
+ total
+ average
+ Loading Tweets
+
+
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..356cdee
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/widget.xml b/app/src/main/res/xml/widget.xml
new file mode 100644
index 0000000..8ea9a07
--- /dev/null
+++ b/app/src/main/res/xml/widget.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/test/java/lk/paradox/kekayan/fabfit/ExampleUnitTest.java b/app/src/test/java/lk/paradox/kekayan/fabfit/ExampleUnitTest.java
new file mode 100644
index 0000000..878bfa8
--- /dev/null
+++ b/app/src/test/java/lk/paradox/kekayan/fabfit/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package lk.paradox.kekayan.fabfit;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..f140e1e
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.2.0-alpha15'
+
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..743d692
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,13 @@
+# 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.
+org.gradle.jvmargs=-Xmx1536m
+# 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
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7a3265e
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..2287b7b
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri May 18 19:33:08 IST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
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'