diff --git a/outlay/.gitignore b/outlay/.gitignore
index 5f94008..c0527a2 100644
--- a/outlay/.gitignore
+++ b/outlay/.gitignore
@@ -5,3 +5,7 @@
.DS_Store
/build
/captures
+/app/src/debug/google-services.json
+/app/src/release/google-services.json
+/app/src/staging/google-services.json
+/release.properties
\ No newline at end of file
diff --git a/outlay/app/build.gradle b/outlay/app/build.gradle
index 14b910d..3cb9b6f 100644
--- a/outlay/app/build.gradle
+++ b/outlay/app/build.gradle
@@ -8,7 +8,6 @@ buildscript {
}
}
apply plugin: 'com.android.application'
-apply plugin: 'io.fabric'
repositories {
maven { url 'https://maven.fabric.io/public' }
@@ -16,7 +15,6 @@ repositories {
apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'me.tatarka.retrolambda'
-apply plugin: 'com.android.databinding'
def cfg = rootProject.ext.configuration
def libs = rootProject.ext.libraries
@@ -26,15 +24,42 @@ android {
buildToolsVersion cfg.buildToolsVersion
defaultConfig {
- applicationId cfg.package
- minSdkVersion cfg.minSdk
- targetSdkVersion cfg.targetSdk
- versionCode cfg.version_code
- versionName cfg.version_name
+ applicationId cfg.package
+ minSdkVersion cfg.minSdk
+ targetSdkVersion cfg.targetSdk
+ versionCode cfg.version_code
+ versionName cfg.version_name
+ }
+
+ def propsFile = rootProject.file('release.properties')
+ def props = new Properties()
+ if (propsFile.exists()) {
+ props.load(new FileInputStream(propsFile))
+ }
+
+ signingConfigs {
+ release {
+ if (propsFile.exists()) {
+ storeFile file(props['RELEASE_STORE_FILE'])
+ storePassword props['RELEASE_STORE_PASSWORD']
+ keyAlias props['RELEASE_KEY_ALIAS']
+ keyPassword props['RELEASE_KEY_PASSWORD']
+ }
+ }
}
buildTypes {
+ debug {
+ buildConfigField "boolean", "USE_ANALYTICS", "false"
+ }
+ staging {
+ buildConfigField "boolean", "USE_ANALYTICS", "false"
+ }
release {
+ buildConfigField "boolean", "USE_ANALYTICS", "true"
+
+ signingConfig signingConfigs.release
+
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
@@ -46,6 +71,7 @@ android {
}
packagingOptions {
+ //
exclude 'META-INF/services/javax.annotation.processing.Processor'
exclude 'META-INF/DEPENDENCIES.txt'
exclude 'META-INF/DEPENDENCIES'
@@ -62,11 +88,10 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile project(':domain')
//Google
apt "com.google.dagger:dagger-compiler:${libs.dagger}"
- compile "com.google.dagger:dagger-compiler:${libs.dagger}"
- provided "javax.annotation:jsr250-api:1.0"
//Support
compile "com.android.support:support-v13:${libs.supportVersion}"
@@ -79,23 +104,27 @@ dependencies {
compile "io.reactivex:rxjava:${libs.rxjava}"
compile "io.reactivex:rxandroid:${libs.rxandroid}"
- //greenRobot
- compile "de.greenrobot:greendao:${libs.greenDao}"
-
//UI
- compile('com.mikepenz:materialdrawer:4.6.4@aar') {
+ compile('com.mikepenz:materialdrawer:5.8.1@aar') {
transitive = true
}
- compile 'com.mikepenz:google-material-typeface:1.2.0.1@aar'
+ compile 'com.mikepenz:material-design-iconic-typeface:+@aar'
+
compile 'com.jakewharton:butterknife:7.0.1'
+
compile "com.github.johnkil.print:print:1.3.1"
compile(group: 'uz.shift', name: 'colorpicker', version: '0.5', ext: 'aar')
- compile 'com.github.PhilJay:MPAndroidChart:v2.2.0'
+ compile 'com.github.PhilJay:MPAndroidChart:v3.0.1'
compile 'com.rengwuxian.materialedittext:library:2.1.4'
+ compile 'com.github.castorflex.smoothprogressbar:library-circular:1.2.0'
- compile 'joda-time:joda-time:2.9.2'
- compile('com.crashlytics.sdk.android:crashlytics:2.5.5@aar') {
- transitive = true;
- }
+ compile 'com.hannesdorfmann.mosby:mvp:2.0.1'
+ compile "com.google.firebase:firebase-core:${libs.firebase}"
+ compile "com.google.firebase:firebase-auth:${libs.firebase}"
+ compile "com.google.firebase:firebase-database:${libs.firebase}"
+ releaseCompile "com.google.firebase:firebase-crash:${libs.firebase}"
+ stagingCompile "com.google.firebase:firebase-crash:${libs.firebase}"
}
+
+apply plugin: 'com.google.gms.google-services'
diff --git a/outlay/app/src/main/AndroidManifest.xml b/outlay/app/src/main/AndroidManifest.xml
index b9fb971..a8ea1c7 100644
--- a/outlay/app/src/main/AndroidManifest.xml
+++ b/outlay/app/src/main/AndroidManifest.xml
@@ -1,29 +1,37 @@
+ package="app.outlay">
+
+
+
+
+
+
+
+
+
-
-
-
-
diff --git a/outlay/app/src/main/java/app/outlay/App.java b/outlay/app/src/main/java/app/outlay/App.java
new file mode 100644
index 0000000..229f726
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/App.java
@@ -0,0 +1,57 @@
+package app.outlay;
+
+import android.app.Application;
+
+import com.google.firebase.database.FirebaseDatabase;
+import app.outlay.core.logger.LoggerFactory;
+import app.outlay.di.component.AppComponent;
+import app.outlay.di.component.DaggerAppComponent;
+import app.outlay.di.component.UserComponent;
+import app.outlay.di.module.AppModule;
+import app.outlay.di.module.FirebaseModule;
+import app.outlay.di.module.UserModule;
+import app.outlay.domain.model.User;
+import app.outlay.impl.AndroidLogger;
+
+/**
+ * Created by Bogdan Melnychuk on 1/15/16.
+ */
+public class App extends Application {
+ private AppComponent appComponent;
+ private UserComponent userComponent;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ initializeInjector();
+ LoggerFactory.registerLogger(new AndroidLogger());
+
+ FirebaseDatabase.getInstance().setPersistenceEnabled(true);
+ }
+
+ public UserComponent createUserComponent(User user) {
+ if (userComponent == null) {
+ userComponent = appComponent.plus(new UserModule(user));
+ }
+ return userComponent;
+ }
+
+ public void releaseUserComponent() {
+ userComponent = null;
+ }
+
+ private void initializeInjector() {
+ appComponent = DaggerAppComponent.builder()
+ .appModule(new AppModule(this))
+ .firebaseModule(new FirebaseModule())
+ .build();
+ }
+
+ public AppComponent getAppComponent() {
+ return appComponent;
+ }
+
+ public UserComponent getUserComponent() {
+ return userComponent;
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/Constants.java b/outlay/app/src/main/java/app/outlay/Constants.java
similarity index 88%
rename from outlay/app/src/main/java/com/outlay/Constants.java
rename to outlay/app/src/main/java/app/outlay/Constants.java
index 30cdb10..f136c3f 100644
--- a/outlay/app/src/main/java/com/outlay/Constants.java
+++ b/outlay/app/src/main/java/app/outlay/Constants.java
@@ -1,4 +1,4 @@
-package com.outlay;
+package app.outlay;
/**
* Created by Bogdan Melnychuk on 1/30/16.
diff --git a/outlay/app/src/main/java/app/outlay/analytics/Analytics.java b/outlay/app/src/main/java/app/outlay/analytics/Analytics.java
new file mode 100644
index 0000000..7fe86eb
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/analytics/Analytics.java
@@ -0,0 +1,56 @@
+package app.outlay.analytics;
+
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+
+import java.util.Date;
+
+/**
+ * Created by bmelnychuk on 2/11/17.
+ */
+
+public interface Analytics {
+ void trackGuestSignIn();
+
+ void trackEmailSignIn();
+
+ void trackSignUp();
+
+ void trackSingOut();
+
+ void trackLinkAccount();
+
+ void trackExpenseCreated(Expense e);
+
+ void trackExpenseDeleted(Expense e);
+
+ void trackExpenseUpdated(Expense e);
+
+ void trackCategoryCreated(Category c);
+
+ void trackCategoryDeleted(Category c);
+
+ void trackCategoryUpdated(Category c);
+
+ void trackCategoryDragEvent();
+
+ void trackViewDailyExpenses();
+
+ void trackViewWeeklyExpenses();
+
+ void trackViewMonthlyExpenses();
+
+ void trackExpensesViewDateChange(Date from, Date to);
+
+ void trackViewExpensesList();
+
+ void trackViewCategoriesList();
+
+ void trackFeedbackClick();
+
+ void trackMainScreenDateChange(Date todayDate, Date changeTo);
+
+ void trackAnalysisView();
+
+ void trackAnalysisPerformed(Date from, Date to);
+}
diff --git a/outlay/app/src/main/java/app/outlay/di/component/AppComponent.java b/outlay/app/src/main/java/app/outlay/di/component/AppComponent.java
new file mode 100644
index 0000000..903c959
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/di/component/AppComponent.java
@@ -0,0 +1,32 @@
+package app.outlay.di.component;
+
+import android.content.Context;
+
+import app.outlay.analytics.Analytics;
+import app.outlay.di.module.AppModule;
+import app.outlay.di.module.FirebaseModule;
+import app.outlay.di.module.UserModule;
+import app.outlay.view.activity.base.ParentActivity;
+import app.outlay.view.activity.LoginActivity;
+import app.outlay.view.fragment.LoginFragment;
+import app.outlay.view.fragment.SyncGuestFragment;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+/**
+ * Created by Bogdan Melnychuk on 12/17/15.
+ */
+@Singleton
+@Component(modules = {AppModule.class, FirebaseModule.class})
+public interface AppComponent {
+ UserComponent plus(UserModule userModule);
+ Context getApplication();
+ Analytics analytics();
+
+ void inject(LoginActivity loginActivity);
+ void inject(ParentActivity staticContentActivity);
+ void inject(LoginFragment loginFragment);
+ void inject(SyncGuestFragment syncGuestFragment);
+}
diff --git a/outlay/app/src/main/java/app/outlay/di/component/UserComponent.java b/outlay/app/src/main/java/app/outlay/di/component/UserComponent.java
new file mode 100644
index 0000000..a6bf7a0
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/di/component/UserComponent.java
@@ -0,0 +1,38 @@
+package app.outlay.di.component;
+
+import app.outlay.di.module.UserModule;
+import app.outlay.di.scope.UserScope;
+import app.outlay.view.activity.MainActivity;
+import app.outlay.view.fragment.AnalysisFragment;
+import app.outlay.view.fragment.CategoriesFragment;
+import app.outlay.view.fragment.CategoryDetailsFragment;
+import app.outlay.view.fragment.ExpensesDetailsFragment;
+import app.outlay.view.fragment.ExpensesListFragment;
+import app.outlay.view.fragment.MainFragment;
+import app.outlay.view.fragment.ReportFragment;
+
+import dagger.Subcomponent;
+
+/**
+ * Created by bmelnychuk on 10/27/16.
+ */
+
+@UserScope
+@Subcomponent(modules = {UserModule.class})
+public interface UserComponent {
+ void inject(CategoriesFragment fragment);
+
+ void inject(CategoryDetailsFragment fragment);
+
+ void inject(ReportFragment fragment);
+
+ void inject(ExpensesListFragment fragment);
+
+ void inject(ExpensesDetailsFragment fragment);
+
+ void inject(MainActivity mainActivity);
+
+ void inject(MainFragment mainFragment2);
+
+ void inject(AnalysisFragment analysisFragment);
+}
diff --git a/outlay/app/src/main/java/app/outlay/di/module/AppModule.java b/outlay/app/src/main/java/app/outlay/di/module/AppModule.java
new file mode 100644
index 0000000..3beae9e
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/di/module/AppModule.java
@@ -0,0 +1,98 @@
+package app.outlay.di.module;
+
+import android.content.Context;
+import android.support.v4.content.ContextCompat;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import app.outlay.App;
+import app.outlay.analytics.Analytics;
+import app.outlay.core.data.AppPreferences;
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Category;
+import app.outlay.executor.JobExecutor;
+import app.outlay.executor.UIThread;
+import app.outlay.firebase.FirebaseAnalyticsImpl;
+import app.outlay.impl.AndroidPreferencesManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Created by Bogdan Melnychuk on 12/17/15.
+ */
+@Module
+public class AppModule {
+ private final App context;
+
+ public AppModule(App mApplication) {
+ this.context = mApplication;
+ }
+
+ @Provides
+ @Singleton
+ Context provideAppContext() {
+ return context;
+ }
+
+ @Provides
+ @Singleton
+ PostExecutionThread providePostExecutionThread(UIThread uiThread) {
+ return uiThread;
+ }
+
+ @Provides
+ @Singleton
+ ThreadExecutor provideThreadExecutor(JobExecutor jobExecutor) {
+ return jobExecutor;
+ }
+
+ @Provides
+ @Singleton
+ AppPreferences provideAppPreferences() {
+ return new AndroidPreferencesManager(context);
+ }
+
+ @Provides
+ @Singleton
+ Analytics provideAnalytics() {
+ return new FirebaseAnalyticsImpl(context);
+ }
+
+ @Provides
+ @Singleton
+ List defaultCategories() {
+ List result = new ArrayList<>();
+ result.add(category(context.getString(app.outlay.R.string.category_car), "ic_cars", ContextCompat.getColor(context, app.outlay.R.color.blue), 0));
+ result.add(category(context.getString(app.outlay.R.string.category_house), "ic_house", ContextCompat.getColor(context, app.outlay.R.color.red), 1));
+ result.add(category(context.getString(app.outlay.R.string.category_grocery), "ic_shopping", ContextCompat.getColor(context, app.outlay.R.color.green), 2));
+ result.add(category(context.getString(app.outlay.R.string.category_games), "ic_controller", ContextCompat.getColor(context, app.outlay.R.color.purple), 3));
+ result.add(category(context.getString(app.outlay.R.string.category_clothes), "ic_t_shirt", ContextCompat.getColor(context, app.outlay.R.color.teal), 4));
+ result.add(category(context.getString(app.outlay.R.string.category_tickets), "ic_tag", ContextCompat.getColor(context, app.outlay.R.color.amber), 5));
+ result.add(category(context.getString(app.outlay.R.string.category_sport), "ic_weightlifting", ContextCompat.getColor(context, app.outlay.R.color.brown), 6));
+ result.add(category(context.getString(app.outlay.R.string.category_travel), "ic_flight", ContextCompat.getColor(context, app.outlay.R.color.cyan), 7));
+ return result;
+ }
+
+ @Provides
+ @Singleton
+ Gson providerGson() {
+ Gson gson = new GsonBuilder().create();
+ return gson;
+ }
+
+ private static Category category(String title, String icon, int color, int order) {
+ Category c = new Category();
+ c.setTitle(title);
+ c.setIcon(icon);
+ c.setColor(color);
+ c.setOrder(order);
+ return c;
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/di/module/DaoModule.java b/outlay/app/src/main/java/app/outlay/di/module/DaoModule.java
new file mode 100644
index 0000000..f73e125
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/di/module/DaoModule.java
@@ -0,0 +1,53 @@
+package app.outlay.di.module;
+
+/**
+ * Created by Bogdan Melnychuk on 12/17/15.
+ */
+public class DaoModule {
+ private static final String DATABASE_NAME = "outlay_db";
+
+// @Provides
+// @Singleton
+// public DaoMaster.DevOpenHelper provideDatabaseHelper(Context application) {
+// return new DaoMaster.DevOpenHelper(application, DATABASE_NAME, null);
+// }
+//
+// @Provides
+// @Singleton
+// public SQLiteDatabase provideDatabase(DaoMaster.DevOpenHelper helper) {
+// return helper.getWritableDatabase();
+// }
+//
+// @Provides
+// @Singleton
+// public DaoMaster provideDaoMaster(SQLiteDatabase database) {
+// return new DaoMaster(database);
+// }
+//
+// @Provides
+// @Singleton
+// public DaoSession provideDaoSession(DaoMaster daoMaster) {
+// return daoMaster.newSession();
+// }
+//
+// @Provides
+// @Singleton
+// public CategoryDao provideCategoryDao(DaoSession session) {
+// return session.getCategoryDao();
+// }
+//
+// @Provides
+// @Singleton
+// public ExpenseDao provideExpenseDaoDao(DaoSession session) {
+// return session.getExpenseDao();
+// }
+//
+// @Provides
+// @Singleton
+// public CategoryDatabaseSource providerLocalCategoryDataSource(
+// CategoryDao categoryDao,
+// ExpenseDao expenseDao
+// ) {
+// return new CategoryDatabaseSource(categoryDao, expenseDao);
+// }
+}
diff --git a/outlay/app/src/main/java/app/outlay/di/module/FirebaseModule.java b/outlay/app/src/main/java/app/outlay/di/module/FirebaseModule.java
new file mode 100644
index 0000000..9f9dc5f
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/di/module/FirebaseModule.java
@@ -0,0 +1,32 @@
+package app.outlay.di.module;
+
+import com.google.firebase.auth.FirebaseAuth;
+import app.outlay.domain.repository.AuthService;
+import app.outlay.firebase.FirebaseAuthRxWrapper;
+import app.outlay.firebase.FirebaseAuthService;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Created by bmelnychuk on 2/8/17.
+ */
+@Module
+public class FirebaseModule {
+ @Provides
+ @Singleton
+ public AuthService provideOutlayAuth(
+ FirebaseAuthRxWrapper firebaseRxWrapper
+ ) {
+ return new FirebaseAuthService(firebaseRxWrapper);
+ }
+
+ @Provides
+ @Singleton
+ public FirebaseAuth provideFirebaseAuth(
+ ) {
+ return FirebaseAuth.getInstance();
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/di/module/UserModule.java b/outlay/app/src/main/java/app/outlay/di/module/UserModule.java
new file mode 100644
index 0000000..b9f580c
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/di/module/UserModule.java
@@ -0,0 +1,75 @@
+package app.outlay.di.module;
+
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+import app.outlay.data.repository.CategoryRepositoryImpl;
+import app.outlay.data.repository.ExpenseRepositoryImpl;
+import app.outlay.data.source.CategoryDataSource;
+import app.outlay.data.source.ExpenseDataSource;
+import app.outlay.di.scope.UserScope;
+import app.outlay.domain.model.User;
+import app.outlay.domain.repository.CategoryRepository;
+import app.outlay.domain.repository.ExpenseRepository;
+import app.outlay.firebase.CategoryFirebaseSource;
+import app.outlay.firebase.ExpenseFirebaseSource;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Created by bmelnychuk on 10/27/16.
+ */
+
+@Module
+public class UserModule {
+ private User user;
+
+ public UserModule(User user) {
+ this.user = user;
+ }
+
+ @Provides
+ @UserScope
+ public User provideCurrentUser(
+ ) {
+ return user;
+ }
+
+ @Provides
+ @UserScope
+ public DatabaseReference provideDatabseRef(
+ ) {
+ DatabaseReference database = FirebaseDatabase.getInstance().getReference();
+ database.child("users").child(user.getId()).keepSynced(true);
+ return database;
+ }
+
+ @Provides
+ @UserScope
+ public CategoryDataSource provideFirebaseCategoryDataSource(
+ DatabaseReference databaseReference
+ ) {
+ return new CategoryFirebaseSource(user, databaseReference);
+ }
+
+ @Provides
+ @UserScope
+ public ExpenseDataSource provideExpenseFirebaseSource(
+ DatabaseReference databaseReference,
+ CategoryDataSource categoryFirebaseSource
+ ) {
+ return new ExpenseFirebaseSource(user, databaseReference, categoryFirebaseSource);
+ }
+
+ @Provides
+ @UserScope
+ public CategoryRepository provideCategoryRepository(CategoryDataSource firebaseSource) {
+ return new CategoryRepositoryImpl(firebaseSource);
+ }
+
+ @Provides
+ @UserScope
+ public ExpenseRepository provideExpenseRepository(ExpenseDataSource expenseDataSource) {
+ return new ExpenseRepositoryImpl(expenseDataSource);
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/di/scope/UserScope.java b/outlay/app/src/main/java/app/outlay/di/scope/UserScope.java
new file mode 100644
index 0000000..a3e2682
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/di/scope/UserScope.java
@@ -0,0 +1,15 @@
+package app.outlay.di.scope;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Scope;
+
+/**
+ * Created by bmelnychuk on 10/27/16.
+ */
+
+@Scope
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UserScope {
+}
diff --git a/outlay/app/src/main/java/app/outlay/executor/JobExecutor.java b/outlay/app/src/main/java/app/outlay/executor/JobExecutor.java
new file mode 100644
index 0000000..1cf77b6
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/executor/JobExecutor.java
@@ -0,0 +1,58 @@
+package app.outlay.executor;
+
+
+import app.outlay.core.executor.ThreadExecutor;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
+public class JobExecutor implements ThreadExecutor {
+
+ private static final int INITIAL_POOL_SIZE = 3;
+ private static final int MAX_POOL_SIZE = 5;
+
+ // Sets the amount of time an idle thread waits before terminating
+ private static final int KEEP_ALIVE_TIME = 10;
+
+ // Sets the Time Unit to seconds
+ private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
+
+ private final BlockingQueue workQueue;
+
+ private final ThreadPoolExecutor threadPoolExecutor;
+
+ private final ThreadFactory threadFactory;
+
+ @Inject
+ public JobExecutor() {
+ this.workQueue = new LinkedBlockingQueue<>();
+ this.threadFactory = new JobThreadFactory();
+ this.threadPoolExecutor = new ThreadPoolExecutor(INITIAL_POOL_SIZE, MAX_POOL_SIZE,
+ KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, this.workQueue, this.threadFactory);
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (runnable == null) {
+ throw new IllegalArgumentException("Runnable to execute cannot be null");
+ }
+ this.threadPoolExecutor.execute(runnable);
+ }
+
+ private static class JobThreadFactory implements ThreadFactory {
+ private static final String THREAD_NAME = "android_";
+ private int counter = 0;
+
+ @Override
+ public Thread newThread(Runnable runnable) {
+ return new Thread(runnable, THREAD_NAME + counter++);
+ }
+ }
+}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/app/outlay/executor/UIThread.java b/outlay/app/src/main/java/app/outlay/executor/UIThread.java
new file mode 100644
index 0000000..8317f3f
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/executor/UIThread.java
@@ -0,0 +1,23 @@
+package app.outlay.executor;
+
+import app.outlay.core.executor.PostExecutionThread;
+
+import javax.inject.Inject;
+
+import rx.Scheduler;
+import rx.android.schedulers.AndroidSchedulers;
+
+/**
+ * Created by bmelnychuk on 5/6/16.
+ */
+public class UIThread implements PostExecutionThread {
+
+ @Inject
+ public UIThread() {
+ }
+
+ @Override
+ public Scheduler getScheduler() {
+ return AndroidSchedulers.mainThread();
+ }
+}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/app/outlay/firebase/CategoryFirebaseSource.java b/outlay/app/src/main/java/app/outlay/firebase/CategoryFirebaseSource.java
new file mode 100644
index 0000000..9966146
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/CategoryFirebaseSource.java
@@ -0,0 +1,180 @@
+package app.outlay.firebase;
+
+import android.text.TextUtils;
+
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.ValueEventListener;
+import app.outlay.data.source.CategoryDataSource;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.User;
+import app.outlay.firebase.dto.CategoryDto;
+import app.outlay.firebase.dto.adapter.CategoryAdapter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class CategoryFirebaseSource implements CategoryDataSource {
+ private DatabaseReference mDatabase;
+ private CategoryAdapter adapter;
+ private User currentUser;
+
+ public CategoryFirebaseSource(
+ User currentUser,
+ DatabaseReference databaseReference
+ ) {
+ this.currentUser = currentUser;
+ mDatabase = databaseReference;
+ adapter = new CategoryAdapter();
+ }
+
+ @Override
+ public Observable> getAll() {
+ return Observable.create(subscriber -> {
+ mDatabase.child("users").child(currentUser.getId()).child("categories").orderByChild("order")
+ .addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+ List categories = new ArrayList<>();
+ for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
+ CategoryDto categoryDto = postSnapshot.getValue(CategoryDto.class);
+ categories.add(adapter.toCategory(categoryDto));
+ }
+ subscriber.onNext(categories);
+ subscriber.onCompleted();
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ subscriber.onError(databaseError.toException());
+ }
+ });
+ });
+
+
+ }
+
+ @Override
+ public Observable getById(String id) {
+ return getDtoById(id).map(categoryDto -> adapter.toCategory(categoryDto));
+ }
+
+ protected Observable getDtoById(String id) {
+ return Observable.create(subscriber -> {
+ mDatabase.child("users").child(currentUser.getId()).child("categories").child(id)
+ .addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+ CategoryDto categoryDto = dataSnapshot.getValue(CategoryDto.class);
+ subscriber.onNext(categoryDto);
+ subscriber.onCompleted();
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ subscriber.onError(databaseError.toException());
+ }
+ });
+ });
+ }
+
+ @Override
+ public Observable> updateOrder(List categories) {
+ return Observable.create(subscriber -> {
+ Map childUpdates = new HashMap<>();
+ for (Category c : categories) {
+ childUpdates.put(c.getId() + "/order", c.getOrder());
+ }
+
+ DatabaseReference categoriesRef = mDatabase.child("users").child(currentUser.getId()).child("categories");
+ categoriesRef.addValueEventListener(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+ subscriber.onNext(categories);
+ subscriber.onCompleted();
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ subscriber.onError(databaseError.toException());
+ }
+ });
+ categoriesRef.updateChildren(childUpdates);
+ });
+ }
+
+ @Override
+ public Observable save(Category category) {
+ Observable saveCategory = Observable.create(subscriber -> {
+ String key = category.getId();
+ if (TextUtils.isEmpty(key)) {
+ key = mDatabase.child("users").child(currentUser.getId()).child("categories").push().getKey();
+ category.setId(key);
+ }
+
+ //TODO move this
+ Map childUpdates = new HashMap<>();
+ childUpdates.put("id", category.getId());
+ childUpdates.put("title", category.getTitle());
+ childUpdates.put("icon", category.getIcon());
+ childUpdates.put("color", category.getColor());
+ childUpdates.put("order", category.getOrder());
+
+ DatabaseReference dbRef = mDatabase
+ .child("users")
+ .child(currentUser.getId())
+ .child("categories").child(key);
+
+ dbRef.addValueEventListener(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+ CategoryDto categoryDto = dataSnapshot.getValue(CategoryDto.class);
+ if (categoryDto != null) {
+ subscriber.onNext(adapter.toCategory(categoryDto));
+ subscriber.onCompleted();
+ }
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ subscriber.onError(databaseError.toException());
+ }
+ });
+
+ dbRef.updateChildren(childUpdates);
+ });
+ return saveCategory;
+ }
+
+ @Override
+ public Observable remove(Category category) {
+ final Observable deleteCategory = Observable.create(subscriber -> {
+ DatabaseReference catReference = mDatabase.child("users").child(currentUser.getId())
+ .child("categories").child(category.getId());
+ catReference.addValueEventListener(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+ subscriber.onNext(category);
+ subscriber.onCompleted();
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ subscriber.onError(databaseError.toException());
+ }
+ });
+ catReference.removeValue();
+ });
+
+ return deleteCategory;
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/firebase/ExpenseFirebaseSource.java b/outlay/app/src/main/java/app/outlay/firebase/ExpenseFirebaseSource.java
new file mode 100644
index 0000000..63bb8d8
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/ExpenseFirebaseSource.java
@@ -0,0 +1,206 @@
+package app.outlay.firebase;
+
+import android.text.TextUtils;
+
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.Query;
+import com.google.firebase.database.ValueEventListener;
+import app.outlay.core.utils.DateUtils;
+import app.outlay.data.source.CategoryDataSource;
+import app.outlay.data.source.ExpenseDataSource;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+import app.outlay.domain.model.User;
+import app.outlay.firebase.dto.ExpenseDto;
+import app.outlay.firebase.dto.adapter.ExpenseAdapter;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/27/16.
+ */
+
+public class ExpenseFirebaseSource implements ExpenseDataSource {
+ private DatabaseReference mDatabase;
+ private CategoryDataSource categoryDataSource;
+ private ExpenseAdapter adapter;
+ private User currentUser;
+
+ @Inject
+ public ExpenseFirebaseSource(
+ User currentUser,
+ DatabaseReference databaseReference,
+ CategoryDataSource categoryDataSource
+ ) {
+ this.currentUser = currentUser;
+ this.categoryDataSource = categoryDataSource;
+ mDatabase = databaseReference;
+ adapter = new ExpenseAdapter();
+ }
+
+ @Override
+ public Observable saveExpense(Expense expense) {
+ return Observable.create(subscriber -> {
+ String key = expense.getId();
+ if (TextUtils.isEmpty(key)) {
+ key = mDatabase.child("users")
+ .child(currentUser.getId())
+ .child("expenses")
+ .child(DateUtils.toYearMonthString(expense.getReportedWhen()))
+ .push().getKey();
+ expense.setId(key);
+ }
+
+ ExpenseDto expenseDto = adapter.fromExpense(expense);
+
+ DatabaseReference databaseReference = mDatabase.child("users")
+ .child(currentUser.getId())
+ .child("expenses")
+ .child(DateUtils.toYearMonthString(expense.getReportedWhen()))
+ .child(key);
+ databaseReference.addValueEventListener(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+ subscriber.onNext(expense);
+ subscriber.onCompleted();
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ subscriber.onError(databaseError.toException());
+ }
+ });
+ databaseReference.setValue(expenseDto);
+ });
+ }
+
+ @Override
+ public Observable> getExpenses(Date startDate, Date endDate, String categoryId) {
+ final Observable> listObservable = Observable.create(subscriber -> {
+ DatabaseReference databaseReference = mDatabase.child("users").child(currentUser.getId()).child("expenses");
+ Query query = databaseReference.orderByKey();
+ query = query.startAt(DateUtils.toYearMonthString(startDate)).endAt(DateUtils.toYearMonthString(endDate));
+ query.addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+ List expenses = new ArrayList<>();
+ for (DataSnapshot monthSnapshot : dataSnapshot.getChildren()) {
+ for (DataSnapshot expensesSnapshot : monthSnapshot.getChildren()) {
+ ExpenseDto expenseDto = expensesSnapshot.getValue(ExpenseDto.class);
+ if (!TextUtils.isEmpty(categoryId) && !expenseDto.getCategoryId().equals(categoryId)) {
+ continue;
+ }
+ expenses.add(adapter.toExpense(expenseDto));
+ }
+ }
+ // at this point we ave monthly data
+
+ Iterator expenseIterator = expenses.iterator();
+ while (expenseIterator.hasNext()) {
+ Expense e = expenseIterator.next();
+ if (!DateUtils.isInPeriod(e.getReportedWhen(), startDate, endDate)) {
+ expenseIterator.remove();
+ }
+ }
+
+ subscriber.onNext(expenses);
+ subscriber.onCompleted();
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ subscriber.onError(databaseError.toException());
+ }
+ });
+ });
+
+ return categoryDataSource.getAll()
+ .map(categories -> {
+ Map categoryMap = new HashMap<>();
+ for (Category c : categories) {
+ categoryMap.put(c.getId(), c);
+ }
+ return categoryMap;
+ }).switchMap(categoryMap ->
+ listObservable.flatMap(expenses -> Observable.from(expenses))
+ .map(expense -> {
+ String currentCatId = expense.getCategory().getId();
+ return expense.setCategory(categoryMap.get(currentCatId));
+ })
+ .filter(expense -> expense.getCategory() != null)
+ .toSortedList((e1, e2) -> (int) (e1.getReportedWhen().getTime() - e2.getReportedWhen().getTime()))
+ );
+ }
+
+ @Override
+ public Observable> getExpenses(Date startDate, Date endDate) {
+ return getExpenses(startDate, endDate, null);
+ }
+
+ @Override
+ public Observable findExpense(String expenseId, Date date) {
+ Observable expenseObservable = Observable.create(subscriber -> {
+ mDatabase.child("users")
+ .child(currentUser.getId())
+ .child("expenses")
+ .child(DateUtils.toYearMonthString(date))
+ .child(expenseId)
+ .addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+ ExpenseDto expenseDto = dataSnapshot.getValue(ExpenseDto.class);
+ subscriber.onNext(adapter.toExpense(expenseDto));
+ subscriber.onCompleted();
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ subscriber.onError(databaseError.toException());
+ }
+ });
+ });
+
+ return expenseObservable
+ .switchMap(expense -> categoryDataSource.getById(expense.getCategory().getId())
+ .map(category ->
+ category == null ? null : expense.setCategory(category)
+ )
+ );
+ }
+
+ @Override
+ public Observable remove(Expense expense) {
+ return Observable.create(subscriber -> {
+
+ DatabaseReference expenseRef = mDatabase.child("users")
+ .child(currentUser.getId())
+ .child("expenses")
+ .child(DateUtils.toYearMonthString(expense.getReportedWhen()))
+ .child(expense.getId());
+ expenseRef.addValueEventListener(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+ subscriber.onNext(expense);
+ subscriber.onCompleted();
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ subscriber.onError(databaseError.toException());
+ }
+ });
+ expenseRef.removeValue();
+ });
+ }
+}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/app/outlay/firebase/FirebaseAnalyticsImpl.java b/outlay/app/src/main/java/app/outlay/firebase/FirebaseAnalyticsImpl.java
new file mode 100644
index 0000000..e4d3171
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/FirebaseAnalyticsImpl.java
@@ -0,0 +1,177 @@
+package app.outlay.firebase;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.google.firebase.analytics.FirebaseAnalytics;
+import app.outlay.BuildConfig;
+import app.outlay.analytics.Analytics;
+import app.outlay.core.utils.DateUtils;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+
+import java.util.Date;
+
+import javax.inject.Inject;
+
+/**
+ * Created by bmelnychuk on 2/11/17.
+ */
+
+public class FirebaseAnalyticsImpl implements Analytics {
+ private FirebaseAnalytics mFirebaseAnalytics;
+
+ @Inject
+ public FirebaseAnalyticsImpl(Context context) {
+ this.mFirebaseAnalytics = FirebaseAnalytics.getInstance(context);
+ }
+
+ private void trackEvent(String event, Bundle params) {
+ if (BuildConfig.USE_ANALYTICS) {
+ mFirebaseAnalytics.logEvent(event, params);
+ }
+ }
+
+ @Override
+ public void trackGuestSignIn() {
+ trackEvent("sign_in_guest", null);
+ }
+
+ @Override
+ public void trackEmailSignIn() {
+ trackEvent("sign_in_email", null);
+ }
+
+ @Override
+ public void trackSignUp() {
+ trackEvent("sign_up", null);
+ }
+
+ @Override
+ public void trackSingOut() {
+ trackEvent("sign_out", null);
+ }
+
+ @Override
+ public void trackLinkAccount() {
+ trackEvent("link_account", null);
+ }
+
+ @Override
+ public void trackExpenseCreated(Expense e) {
+ trackEvent("expense_created", expenseBundle(e));
+ }
+
+ @Override
+ public void trackExpenseDeleted(Expense e) {
+ trackEvent("expense_deleted", expenseBundle(e));
+ }
+
+ @Override
+ public void trackExpenseUpdated(Expense e) {
+ trackEvent("expense_updated", expenseBundle(e));
+ }
+
+ @Override
+ public void trackCategoryCreated(Category c) {
+ trackEvent("category_created", categoryBundle(c));
+ }
+
+ @Override
+ public void trackCategoryDeleted(Category c) {
+ trackEvent("category_deleted", categoryBundle(c));
+ }
+
+ @Override
+ public void trackCategoryUpdated(Category c) {
+ trackEvent("category_updated", categoryBundle(c));
+ }
+
+ @Override
+ public void trackViewDailyExpenses() {
+ trackEvent("expenses_daily", null);
+ }
+
+ @Override
+ public void trackViewWeeklyExpenses() {
+ trackEvent("expenses_weekly", null);
+ }
+
+ @Override
+ public void trackViewMonthlyExpenses() {
+ trackEvent("expenses_monthly", null);
+ }
+
+ @Override
+ public void trackExpensesViewDateChange(Date from, Date to) {
+ Bundle b = new Bundle();
+ b.putLong("dateFromLong", from.getTime());
+ b.putString("dateFrom", DateUtils.toShortString(from));
+
+ b.putLong("dateToLong", to.getTime());
+ b.putString("dateTo", DateUtils.toShortString(to));
+ trackEvent("expenses_view_date_changed", b);
+ }
+
+ @Override
+ public void trackViewExpensesList() {
+ trackEvent("expenses_list_view", null);
+ }
+
+ @Override
+ public void trackViewCategoriesList() {
+ trackEvent("categories_list_view", null);
+ }
+
+ @Override
+ public void trackCategoryDragEvent() {
+ trackEvent("categories_drag_event", null);
+ }
+
+ @Override
+ public void trackFeedbackClick() {
+ trackEvent("feedback_click", null);
+ }
+
+ @Override
+ public void trackMainScreenDateChange(Date todayDate, Date changeTo) {
+ Bundle b = new Bundle();
+ b.putLong("currentDateLong", todayDate.getTime());
+ b.putString("currentDate", DateUtils.toShortString(todayDate));
+
+ b.putLong("changeToLong", changeTo.getTime());
+ b.putString("changeTo", DateUtils.toShortString(changeTo));
+ trackEvent("main_screen_date_change", b);
+ }
+
+ @Override
+ public void trackAnalysisView() {
+ trackEvent("analysis_view", null);
+ }
+
+ @Override
+ public void trackAnalysisPerformed(Date from, Date to) {
+ Bundle b = new Bundle();
+ b.putLong("dateFromLong", from.getTime());
+ b.putString("dateFrom", DateUtils.toShortString(from));
+
+ b.putLong("dateToLong", to.getTime());
+ b.putString("dateTo", DateUtils.toShortString(to));
+ trackEvent("analysis_event", b);
+ }
+
+ private Bundle expenseBundle(Expense e) {
+ Bundle b = new Bundle();
+ b.putString("categoryIcon", e.getCategory().getIcon());
+ b.putString("categoryName", e.getCategory().getTitle());
+ b.putString("amount", e.getAmount().toString());
+ return b;
+ }
+
+ private Bundle categoryBundle(Category c) {
+ Bundle b = new Bundle();
+ b.putString("categoryIcon", c.getIcon());
+ b.putString("categoryName", c.getTitle());
+ return b;
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/firebase/FirebaseAuthRxWrapper.java b/outlay/app/src/main/java/app/outlay/firebase/FirebaseAuthRxWrapper.java
new file mode 100644
index 0000000..bab412b
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/FirebaseAuthRxWrapper.java
@@ -0,0 +1,122 @@
+package app.outlay.firebase;
+
+import com.google.android.gms.tasks.Task;
+import com.google.firebase.auth.AuthCredential;
+import com.google.firebase.auth.AuthResult;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.auth.GetTokenResult;
+import app.outlay.domain.model.User;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class FirebaseAuthRxWrapper {
+ private FirebaseAuth firebaseAuth;
+
+ @Inject
+ public FirebaseAuthRxWrapper(FirebaseAuth firebaseAuth) {
+ this.firebaseAuth = firebaseAuth;
+ }
+
+ public Observable getUserToken(FirebaseUser firebaseUser) {
+ return Observable.create(subscriber -> {
+ Task task = firebaseUser.getToken(true);
+
+ task.addOnCompleteListener(resultTask -> {
+ if (task.isSuccessful()) {
+ String token = task.getResult().getToken();
+ subscriber.onNext(token);
+ subscriber.onCompleted();
+ } else {
+ Exception e = task.getException();
+ subscriber.onError(e);
+ }
+ });
+ });
+ }
+
+ public Observable signUp(String email, String password) {
+ return Observable.create(subscriber -> {
+ Task task = firebaseAuth.createUserWithEmailAndPassword(email, password);
+ task.addOnCompleteListener(resultTask -> {
+ if (task.isSuccessful()) {
+ AuthResult authResult = task.getResult();
+ subscriber.onNext(authResult);
+ subscriber.onCompleted();
+ } else {
+ Exception e = task.getException();
+ subscriber.onError(e);
+ }
+ });
+ });
+ }
+
+ public Observable signIn(String email, String password) {
+ return Observable.create(subscriber -> {
+ Task task = firebaseAuth.signInWithEmailAndPassword(email, password);
+ task.addOnCompleteListener(resultTask -> {
+ if (task.isSuccessful()) {
+ AuthResult authResult = task.getResult();
+ subscriber.onNext(authResult);
+ subscriber.onCompleted();
+ } else {
+ Exception e = task.getException();
+ subscriber.onError(e);
+ }
+ });
+ });
+ }
+
+ public Observable signInAnonymously() {
+ return Observable.create(subscriber -> {
+ Task task = firebaseAuth.signInAnonymously();
+ task.addOnCompleteListener(resultTask -> {
+ if (task.isSuccessful()) {
+ AuthResult authResult = task.getResult();
+ subscriber.onNext(authResult);
+ subscriber.onCompleted();
+ } else {
+ Exception e = task.getException();
+ subscriber.onError(e);
+ }
+ });
+ });
+ }
+
+ public Observable linkAccount(AuthCredential credentials) {
+ return Observable.create(subscriber -> {
+ Task task = firebaseAuth.getCurrentUser().linkWithCredential(credentials);
+ task.addOnCompleteListener(resultTask -> {
+ if (task.isSuccessful()) {
+ AuthResult authResult = task.getResult();
+ subscriber.onNext(authResult);
+ subscriber.onCompleted();
+ } else {
+ Exception e = task.getException();
+ subscriber.onError(e);
+ }
+ });
+ });
+ }
+
+
+ public Observable resetPassword(User user) {
+ return Observable.create(subscriber -> {
+ Task task = firebaseAuth.sendPasswordResetEmail(user.getEmail());
+ task.addOnCompleteListener(resultTask -> {
+ if (task.isSuccessful()) {
+ subscriber.onCompleted();
+ } else {
+ Exception e = task.getException();
+ subscriber.onError(e);
+ }
+ });
+ });
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/firebase/FirebaseAuthService.java b/outlay/app/src/main/java/app/outlay/firebase/FirebaseAuthService.java
new file mode 100644
index 0000000..fadb0bf
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/FirebaseAuthService.java
@@ -0,0 +1,59 @@
+package app.outlay.firebase;
+
+import com.google.firebase.auth.AuthCredential;
+import com.google.firebase.auth.EmailAuthProvider;
+import app.outlay.domain.model.Credentials;
+import app.outlay.domain.model.User;
+import app.outlay.domain.repository.AuthService;
+import app.outlay.firebase.dto.adapter.UserAdapter;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class FirebaseAuthService implements AuthService {
+ private FirebaseAuthRxWrapper firebaseWrapper;
+
+ @Inject
+ public FirebaseAuthService(FirebaseAuthRxWrapper firebaseWrapper) {
+ this.firebaseWrapper = firebaseWrapper;
+ }
+
+ @Override
+ public Observable signIn(Credentials credentials) {
+ return firebaseWrapper.signIn(credentials.getEmail(), credentials.getPassword())
+ .map(authResult -> authResult.getUser())
+ .map(firebaseUser -> UserAdapter.fromFirebaseUser(firebaseUser));
+ }
+
+ @Override
+ public Observable signUp(Credentials credentials) {
+ return firebaseWrapper.signUp(credentials.getEmail(), credentials.getPassword())
+ .map(authResult -> authResult.getUser())
+ .map(firebaseUser -> UserAdapter.fromFirebaseUser(firebaseUser));
+ }
+
+ @Override
+ public Observable linkCredentials(Credentials credentials) {
+ AuthCredential emailCredentials = EmailAuthProvider.getCredential(credentials.getEmail(), credentials.getPassword());
+ return firebaseWrapper.linkAccount(emailCredentials)
+ .map(authResult -> authResult.getUser())
+ .map(firebaseUser -> UserAdapter.fromFirebaseUser(firebaseUser));
+ }
+
+ @Override
+ public Observable signInAnonymously() {
+ return firebaseWrapper.signInAnonymously()
+ .map(authResult -> authResult.getUser())
+ .map(firebaseUser -> UserAdapter.fromFirebaseUser(firebaseUser));
+ }
+
+ @Override
+ public Observable resetPassword(User user) {
+ return firebaseWrapper.resetPassword(user);
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/firebase/dto/CategoryDto.java b/outlay/app/src/main/java/app/outlay/firebase/dto/CategoryDto.java
new file mode 100644
index 0000000..37b1b3a
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/dto/CategoryDto.java
@@ -0,0 +1,79 @@
+package app.outlay.firebase.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class CategoryDto {
+ private String id;
+ private String title;
+ private String icon;
+ private int order;
+ private int color;
+ private List expenses;
+
+ public CategoryDto() {
+ expenses = new ArrayList<>();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+ public int getOrder() {
+ return order;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ public int getColor() {
+ return color;
+ }
+
+ public void setColor(int color) {
+ this.color = color;
+ }
+
+ public List getExpenses() {
+ return expenses;
+ }
+
+ public void setExpenses(List expenses) {
+ this.expenses = expenses;
+ }
+
+ public void addExpense(String id) {
+ if (!expenses.contains(id)) {
+ expenses.add(id);
+ }
+ }
+
+ public void removeExpense(String id) {
+ expenses.remove(id);
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/firebase/dto/ExpenseDto.java b/outlay/app/src/main/java/app/outlay/firebase/dto/ExpenseDto.java
new file mode 100644
index 0000000..55f1fe3
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/dto/ExpenseDto.java
@@ -0,0 +1,54 @@
+package app.outlay.firebase.dto;
+
+/**
+ * Created by bmelnychuk on 10/27/16.
+ */
+
+public class ExpenseDto {
+ private String id;
+ private String note;
+ private String amount;
+ private Long reportedWhen;
+ private String categoryId;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getNote() {
+ return note;
+ }
+
+ public void setNote(String note) {
+ this.note = note;
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public String getCategoryId() {
+ return categoryId;
+ }
+
+ public void setCategoryId(String categoryId) {
+ this.categoryId = categoryId;
+ }
+
+ public Long getReportedWhen() {
+ return reportedWhen;
+ }
+
+ public ExpenseDto setReportedWhen(Long reportedWhen) {
+ this.reportedWhen = reportedWhen;
+ return this;
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/firebase/dto/adapter/CategoryAdapter.java b/outlay/app/src/main/java/app/outlay/firebase/dto/adapter/CategoryAdapter.java
new file mode 100644
index 0000000..b818f77
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/dto/adapter/CategoryAdapter.java
@@ -0,0 +1,31 @@
+package app.outlay.firebase.dto.adapter;
+
+import app.outlay.domain.model.Category;
+import app.outlay.firebase.dto.CategoryDto;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class CategoryAdapter {
+ public Category toCategory(CategoryDto categoryDto) {
+ Category category = new Category();
+ category.setColor(categoryDto.getColor());
+ category.setIcon(categoryDto.getIcon());
+ category.setOrder(categoryDto.getOrder());
+ category.setId(categoryDto.getId());
+ category.setTitle(categoryDto.getTitle());
+ return category;
+ }
+
+ public CategoryDto fromCategory(Category category) {
+ CategoryDto categoryDto = new CategoryDto();
+ categoryDto.setColor(category.getColor());
+ categoryDto.setIcon(category.getIcon());
+ categoryDto.setOrder(category.getOrder());
+ categoryDto.setId(category.getId());
+ categoryDto.setTitle(category.getTitle());
+
+ return categoryDto;
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/firebase/dto/adapter/ExpenseAdapter.java b/outlay/app/src/main/java/app/outlay/firebase/dto/adapter/ExpenseAdapter.java
new file mode 100644
index 0000000..94010cb
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/dto/adapter/ExpenseAdapter.java
@@ -0,0 +1,40 @@
+package app.outlay.firebase.dto.adapter;
+
+import app.outlay.firebase.dto.ExpenseDto;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class ExpenseAdapter {
+
+ public Expense toExpense(ExpenseDto expenseDto) {
+ Expense expense = new Expense();
+ expense.setNote(expenseDto.getNote());
+ expense.setAmount(new BigDecimal(expenseDto.getAmount()));
+ expense.setId(expenseDto.getId());
+ expense.setReportedWhen(new Date(expenseDto.getReportedWhen()));
+
+ Category category = new Category();
+ category.setId(expenseDto.getCategoryId());
+ expense.setCategory(category);
+
+ return expense;
+ }
+
+ public ExpenseDto fromExpense(Expense expense) {
+ ExpenseDto expenseDto = new ExpenseDto();
+ expenseDto.setNote(expense.getNote());
+ expenseDto.setAmount(expense.getAmount().toString());
+ expenseDto.setId(expense.getId());
+ expenseDto.setReportedWhen(expense.getReportedWhen().getTime());
+ expenseDto.setCategoryId(expense.getCategory().getId());
+
+ return expenseDto;
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/firebase/dto/adapter/UserAdapter.java b/outlay/app/src/main/java/app/outlay/firebase/dto/adapter/UserAdapter.java
new file mode 100644
index 0000000..467b1a3
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/firebase/dto/adapter/UserAdapter.java
@@ -0,0 +1,19 @@
+package app.outlay.firebase.dto.adapter;
+
+import com.google.firebase.auth.FirebaseUser;
+import app.outlay.domain.model.User;
+
+/**
+ * Created by bmelnychuk on 2/9/17.
+ */
+
+public class UserAdapter {
+ public static User fromFirebaseUser(FirebaseUser fbUser) {
+ User result = new User();
+ result.setAnonymous(fbUser.isAnonymous());
+ result.setUserName(fbUser.getDisplayName());
+ result.setId(fbUser.getUid());
+ result.setEmail(fbUser.getEmail());
+ return result;
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/impl/AndroidLogger.java b/outlay/app/src/main/java/app/outlay/impl/AndroidLogger.java
new file mode 100644
index 0000000..7b09822
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/impl/AndroidLogger.java
@@ -0,0 +1,43 @@
+package app.outlay.impl;
+
+import android.util.Log;
+
+import app.outlay.core.logger.Logger;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class AndroidLogger implements Logger {
+ private static final String TAG = "Outlay";
+
+ @Override
+ public void info(String message) {
+ Log.i(TAG, message);
+ }
+
+ @Override
+ public void warn(String message) {
+ Log.w(TAG, message);
+ }
+
+ @Override
+ public void warn(String message, Throwable e) {
+ Log.w(TAG, message, e);
+ }
+
+ @Override
+ public void debug(String message) {
+ Log.d(TAG, message);
+ }
+
+ @Override
+ public void error(String message) {
+ Log.e(TAG, message);
+ }
+
+ @Override
+ public void error(String message, Throwable e) {
+ Log.e(TAG, message, e);
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/preferences/PreferencesManager.java b/outlay/app/src/main/java/app/outlay/impl/AndroidPreferencesManager.java
similarity index 68%
rename from outlay/app/src/main/java/com/outlay/preferences/PreferencesManager.java
rename to outlay/app/src/main/java/app/outlay/impl/AndroidPreferencesManager.java
index d1dda4b..1097725 100644
--- a/outlay/app/src/main/java/com/outlay/preferences/PreferencesManager.java
+++ b/outlay/app/src/main/java/app/outlay/impl/AndroidPreferencesManager.java
@@ -1,15 +1,18 @@
-package com.outlay.preferences;
+package app.outlay.impl;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
-public class PreferencesManager {
- private static final String PREF_FIRST_RUN = "_firstRun";
+import app.outlay.core.data.AppPreferences;
+
+public class AndroidPreferencesManager implements AppPreferences {
+ private static final String PREF_FIRST_RUN = "_firstRun_v2";
+ private static final String PREF_SESSION_ID = "_sessionId";
private Context context;
- public PreferencesManager(Context context) {
+ public AndroidPreferencesManager(Context context) {
this.context = context;
}
@@ -57,4 +60,19 @@ public void putFirstRun(boolean value) {
public boolean isFirstRun() {
return getBoolean(PREF_FIRST_RUN, true);
}
+
+ @Override
+ public void setFirstRun(boolean firstRun) {
+ putFirstRun(firstRun);
+ }
+
+ @Override
+ public String getSessionId() {
+ return getString(PREF_SESSION_ID);
+ }
+
+ @Override
+ public void setSessionId(String sessionId) {
+ putString(PREF_SESSION_ID, sessionId);
+ }
}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/presenter/AnalysisPresenter.java b/outlay/app/src/main/java/app/outlay/mvp/presenter/AnalysisPresenter.java
new file mode 100644
index 0000000..551e4cf
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/presenter/AnalysisPresenter.java
@@ -0,0 +1,57 @@
+package app.outlay.mvp.presenter;
+
+import com.hannesdorfmann.mosby.mvp.MvpBasePresenter;
+import app.outlay.core.executor.DefaultSubscriber;
+import app.outlay.domain.interactor.GetCategoriesUseCase;
+import app.outlay.domain.interactor.GetExpensesUseCase;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Report;
+import app.outlay.mvp.view.AnalysisView;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Created by bmelnychuk on 2/10/17.
+ */
+
+public class AnalysisPresenter extends MvpBasePresenter {
+ private GetCategoriesUseCase getCategoriesUseCase;
+ private GetExpensesUseCase getExpensesUseCase;
+
+ @Inject
+ public AnalysisPresenter(
+ GetCategoriesUseCase getCategoriesUseCase,
+ GetExpensesUseCase getExpensesUseCase
+ ) {
+ this.getCategoriesUseCase = getCategoriesUseCase;
+ this.getExpensesUseCase = getExpensesUseCase;
+ }
+
+ public void getCategories() {
+ getCategoriesUseCase.execute(new DefaultSubscriber>() {
+ @Override
+ public void onNext(List categories) {
+ super.onNext(categories);
+ getView().setCategories(categories);
+ }
+ });
+ }
+
+ public void getExpenses(Date startDate, Date endDate, Category category) {
+ getExpensesUseCase.execute(
+ new GetExpensesUseCase.Input(startDate, endDate, category.getId()),
+ new DefaultSubscriber() {
+ @Override
+ public void onNext(Report report) {
+ super.onNext(report);
+ if (isViewAttached()) {
+ getView().showAnalysis(report);
+ }
+ }
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/app/outlay/mvp/presenter/CategoriesPresenter.java b/outlay/app/src/main/java/app/outlay/mvp/presenter/CategoriesPresenter.java
new file mode 100644
index 0000000..cec746f
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/presenter/CategoriesPresenter.java
@@ -0,0 +1,42 @@
+package app.outlay.mvp.presenter;
+
+import com.hannesdorfmann.mosby.mvp.MvpBasePresenter;
+import app.outlay.core.executor.DefaultSubscriber;
+import app.outlay.domain.interactor.GetCategoriesUseCase;
+import app.outlay.domain.interactor.UpdateCategoriesUseCase;
+import app.outlay.domain.model.Category;
+import app.outlay.mvp.view.CategoriesView;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Created by Bogdan Melnychuk on 1/21/16.
+ */
+public class CategoriesPresenter extends MvpBasePresenter {
+ private GetCategoriesUseCase getCategoriesUseCase;
+ private UpdateCategoriesUseCase updateCategoriesUseCase;
+
+ @Inject
+ public CategoriesPresenter(
+ GetCategoriesUseCase getCategoriesUseCase,
+ UpdateCategoriesUseCase updateCategoriesUseCase
+ ) {
+ this.getCategoriesUseCase = getCategoriesUseCase;
+ this.updateCategoriesUseCase = updateCategoriesUseCase;
+ }
+
+ public void getCategories() {
+ getCategoriesUseCase.execute(new DefaultSubscriber>() {
+ @Override
+ public void onNext(List categories) {
+ getView().showCategories(categories);
+ }
+ });
+ }
+
+ public void updateOrder(List categories) {
+ updateCategoriesUseCase.execute(categories, new DefaultSubscriber());
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/presenter/CategoryDetailsPresenter.java b/outlay/app/src/main/java/app/outlay/mvp/presenter/CategoryDetailsPresenter.java
new file mode 100644
index 0000000..cabacd4
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/presenter/CategoryDetailsPresenter.java
@@ -0,0 +1,59 @@
+package app.outlay.mvp.presenter;
+
+import com.hannesdorfmann.mosby.mvp.MvpBasePresenter;
+import app.outlay.core.executor.DefaultSubscriber;
+import app.outlay.domain.interactor.DeleteCategoryUseCase;
+import app.outlay.domain.interactor.GetCategoryUseCase;
+import app.outlay.domain.interactor.SaveCategoryUseCase;
+import app.outlay.domain.model.Category;
+import app.outlay.mvp.view.CategoryDetailsView;
+
+import javax.inject.Inject;
+
+/**
+ * Created by Bogdan Melnychuk on 1/21/16.
+ */
+public class CategoryDetailsPresenter extends MvpBasePresenter {
+ private SaveCategoryUseCase updateCategoryUseCase;
+ private DeleteCategoryUseCase deleteCategoryUseCase;
+ private GetCategoryUseCase getCategoryUseCase;
+
+ @Inject
+ public CategoryDetailsPresenter(
+ SaveCategoryUseCase updateCategoryUseCase,
+ DeleteCategoryUseCase deleteCategoryUseCase,
+ GetCategoryUseCase getCategoryUseCase
+ ) {
+ this.updateCategoryUseCase = updateCategoryUseCase;
+ this.deleteCategoryUseCase = deleteCategoryUseCase;
+ this.getCategoryUseCase = getCategoryUseCase;
+ }
+
+ public void getCategory(String id) {
+ getCategoryUseCase.execute(id, new DefaultSubscriber() {
+ @Override
+ public void onNext(Category category) {
+ getView().showCategory(category);
+ }
+ });
+ }
+
+ public void updateCategory(Category category) {
+ updateCategoryUseCase.execute(category, new DefaultSubscriber() {
+ @Override
+ public void onCompleted() {
+ getView().finish();
+ }
+ });
+
+ }
+
+ public void deleteCategory(Category category) {
+ deleteCategoryUseCase.execute(category, new DefaultSubscriber() {
+ @Override
+ public void onCompleted() {
+ getView().finish();
+ }
+ });
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/presenter/EnterExpensePresenter.java b/outlay/app/src/main/java/app/outlay/mvp/presenter/EnterExpensePresenter.java
new file mode 100644
index 0000000..65337cb
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/presenter/EnterExpensePresenter.java
@@ -0,0 +1,68 @@
+package app.outlay.mvp.presenter;
+
+import com.hannesdorfmann.mosby.mvp.MvpBasePresenter;
+import app.outlay.core.data.AppPreferences;
+import app.outlay.core.executor.DefaultSubscriber;
+import app.outlay.domain.interactor.DeleteExpenseUseCase;
+import app.outlay.domain.interactor.GetCategoriesUseCase;
+import app.outlay.domain.interactor.SaveExpenseUseCase;
+import app.outlay.domain.model.Expense;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.repository.ExpenseRepository;
+import app.outlay.mvp.view.EnterExpenseView;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Created by Bogdan Melnychuk on 1/25/16.
+ */
+public class EnterExpensePresenter extends MvpBasePresenter {
+ private GetCategoriesUseCase getCategoriesUseCase;
+ private SaveExpenseUseCase createExpenseUseCase;
+ private DeleteExpenseUseCase deleteExpenseUseCase;
+ private AppPreferences appPreferences;
+
+ @Inject
+ public EnterExpensePresenter(
+ GetCategoriesUseCase getCategoriesUseCase,
+ SaveExpenseUseCase createExpenseUseCase,
+ DeleteExpenseUseCase deleteExpenseUseCase,
+ AppPreferences appPreferences,
+ ExpenseRepository repository
+ ) {
+ this.getCategoriesUseCase = getCategoriesUseCase;
+ this.createExpenseUseCase = createExpenseUseCase;
+ this.deleteExpenseUseCase = deleteExpenseUseCase;
+ this.appPreferences = appPreferences;
+ }
+
+ public void getCategories() {
+ getCategoriesUseCase.execute(new DefaultSubscriber>() {
+ @Override
+ public void onNext(List categories) {
+ getView().showCategories(categories);
+ }
+ });
+ }
+
+
+ public void createExpense(Expense expense) {
+ createExpenseUseCase.execute(expense, new DefaultSubscriber() {
+ @Override
+ public void onNext(Expense expense) {
+ getView().alertExpenseSuccess(expense);
+
+ }
+ });
+ }
+
+ public void deleteExpense(Expense expense) {
+ deleteExpenseUseCase.execute(expense, new DefaultSubscriber() {
+ @Override
+ public void onCompleted() {
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/app/outlay/mvp/presenter/ExpenseDetailsPresenter.java b/outlay/app/src/main/java/app/outlay/mvp/presenter/ExpenseDetailsPresenter.java
new file mode 100644
index 0000000..8b5929b
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/presenter/ExpenseDetailsPresenter.java
@@ -0,0 +1,67 @@
+package app.outlay.mvp.presenter;
+
+import com.hannesdorfmann.mosby.mvp.MvpBasePresenter;
+import app.outlay.core.executor.DefaultSubscriber;
+import app.outlay.domain.interactor.DeleteExpenseUseCase;
+import app.outlay.domain.interactor.GetCategoriesUseCase;
+import app.outlay.domain.interactor.GetExpenseUseCase;
+import app.outlay.domain.interactor.SaveExpenseUseCase;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+import app.outlay.mvp.view.ExpenseDetailsView;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Created by Bogdan Melnychuk on 1/21/16.
+ */
+public class ExpenseDetailsPresenter extends MvpBasePresenter {
+ private GetCategoriesUseCase getCategoriesUseCase;
+ private GetExpenseUseCase getExpenseUseCase;
+ private SaveExpenseUseCase saveExpenseUseCase;
+ private DeleteExpenseUseCase deleteExpenseUseCase;
+
+ @Inject
+ public ExpenseDetailsPresenter(
+ GetCategoriesUseCase getCategoriesUseCase,
+ GetExpenseUseCase getExpenseUseCase,
+ SaveExpenseUseCase saveExpenseUseCase,
+ DeleteExpenseUseCase deleteExpenseUseCase
+ ) {
+ this.getCategoriesUseCase = getCategoriesUseCase;
+ this.getExpenseUseCase = getExpenseUseCase;
+ this.saveExpenseUseCase = saveExpenseUseCase;
+ this.deleteExpenseUseCase = deleteExpenseUseCase;
+ }
+
+ public void findExpense(String expenseId, Date date) {
+ getExpenseUseCase.execute(new GetExpenseUseCase.Input(expenseId, date), new DefaultSubscriber() {
+ @Override
+ public void onNext(Expense expense) {
+ getView().showExpense(expense);
+ }
+ });
+
+ }
+
+ public void getCategories() {
+ getCategoriesUseCase.execute(new DefaultSubscriber>() {
+ @Override
+ public void onNext(List categories) {
+ super.onNext(categories);
+ getView().showCategories(categories);
+ }
+ });
+ }
+
+ public void updateExpense(Expense expense) {
+ saveExpenseUseCase.execute(expense, new DefaultSubscriber());
+ }
+
+ public void deleteExpense(Expense expense) {
+ deleteExpenseUseCase.execute(expense, new DefaultSubscriber());
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/presenter/ExpensesListPresenter.java b/outlay/app/src/main/java/app/outlay/mvp/presenter/ExpensesListPresenter.java
new file mode 100644
index 0000000..1369072
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/presenter/ExpensesListPresenter.java
@@ -0,0 +1,35 @@
+package app.outlay.mvp.presenter;
+
+import com.hannesdorfmann.mosby.mvp.MvpBasePresenter;
+import app.outlay.core.executor.DefaultSubscriber;
+import app.outlay.domain.interactor.GetExpensesUseCase;
+import app.outlay.domain.model.Report;
+import app.outlay.mvp.view.ExpensesView;
+
+import java.util.Date;
+
+import javax.inject.Inject;
+
+/**
+ * Created by Bogdan Melnychuk on 1/21/16.
+ */
+public class ExpensesListPresenter extends MvpBasePresenter {
+ private GetExpensesUseCase loadReportUseCase;
+
+ @Inject
+ public ExpensesListPresenter(
+ GetExpensesUseCase loadReportUseCase
+ ) {
+ this.loadReportUseCase = loadReportUseCase;
+ }
+
+ public void findExpenses(Date dateFrom, Date dateTo, String categoryId) {
+ GetExpensesUseCase.Input input = new GetExpensesUseCase.Input(dateFrom, dateTo, categoryId);
+ loadReportUseCase.execute(input, new DefaultSubscriber() {
+ @Override
+ public void onNext(Report report) {
+ getView().showReport(report);
+ }
+ });
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/presenter/LoginViewPresenter.java b/outlay/app/src/main/java/app/outlay/mvp/presenter/LoginViewPresenter.java
new file mode 100644
index 0000000..a25206c
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/presenter/LoginViewPresenter.java
@@ -0,0 +1,129 @@
+package app.outlay.mvp.presenter;
+
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.hannesdorfmann.mosby.mvp.*;
+
+import app.outlay.core.executor.DefaultSubscriber;
+import app.outlay.domain.interactor.LinkAccountUseCase;
+import app.outlay.domain.interactor.ResetPasswordUseCase;
+import app.outlay.domain.interactor.UserSignInUseCase;
+import app.outlay.domain.interactor.UserSignUpUseCase;
+import app.outlay.domain.model.Credentials;
+import app.outlay.domain.model.User;
+import app.outlay.mvp.view.LoginView;
+
+import javax.inject.Inject;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class LoginViewPresenter extends MvpBasePresenter {
+ private UserSignInUseCase userSignInUseCase;
+ private UserSignUpUseCase userSignUpUseCase;
+ private ResetPasswordUseCase resetPasswordUseCase;
+ private LinkAccountUseCase linkAccountUseCase;
+
+ @Inject
+ public LoginViewPresenter(
+ UserSignInUseCase userSignInUseCase,
+ UserSignUpUseCase userSignUpUseCase,
+ ResetPasswordUseCase resetPasswordUseCase,
+ LinkAccountUseCase linkAccountUseCase
+ ) {
+ this.userSignInUseCase = userSignInUseCase;
+ this.userSignUpUseCase = userSignUpUseCase;
+ this.resetPasswordUseCase = resetPasswordUseCase;
+ this.linkAccountUseCase = linkAccountUseCase;
+ }
+
+ public void signIn(String email, String password) {
+ Credentials credentials = new Credentials(email, password);
+ signIn(credentials);
+ }
+
+ public void signInGuest() {
+ signIn(Credentials.GUEST);
+ }
+
+ private void signIn(Credentials credentials) {
+ getView().setProgress(true);
+
+ userSignInUseCase.execute(credentials, new DefaultSubscriber() {
+ @Override
+ public void onNext(User user) {
+ onAuthSuccess(user);
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ super.onError(e);
+ getView().setProgress(false);
+ getView().error(e);
+ }
+ });
+ }
+
+ public void signUp(String email, String password) {
+ getView().setProgress(true);
+
+ Credentials credentials = new Credentials(email, password);
+
+ userSignUpUseCase.execute(credentials, new DefaultSubscriber() {
+ @Override
+ public void onNext(User user) {
+ onAuthSuccess(user);
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ super.onError(e);
+ getView().setProgress(false);
+ getView().error(e);
+ }
+ });
+ }
+
+ public void linkAccount(String email, String password) {
+ linkAccountUseCase.execute(new Credentials(email, password), new DefaultSubscriber() {
+ @Override
+ public void onNext(User user) {
+ getView().onSuccess(user);
+ }
+ });
+ }
+
+ public void trySignIn() {
+ FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
+ if (firebaseUser != null) {
+ User user = new User();
+ user.setId(firebaseUser.getUid());
+ user.setEmail(firebaseUser.getEmail());
+ user.setAnonymous(firebaseUser.isAnonymous());
+ user.setUserName(firebaseUser.getDisplayName());
+ getView().onSuccess(user);
+ }
+ }
+
+ private void onAuthSuccess(User user) {
+ getView().onSuccess(user);
+ }
+
+ public void resetPassword(String email) {
+ User user = new User();
+ user.setEmail(email);
+ resetPasswordUseCase.execute(user, new DefaultSubscriber() {
+ @Override
+ public void onCompleted() {
+ getView().info("Email with was sent");
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ super.onError(e);
+ getView().error(new Exception("Problem while resetting your password"));
+ }
+ });
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/presenter/ReportPresenter.java b/outlay/app/src/main/java/app/outlay/mvp/presenter/ReportPresenter.java
new file mode 100644
index 0000000..0e34332
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/presenter/ReportPresenter.java
@@ -0,0 +1,56 @@
+package app.outlay.mvp.presenter;
+
+import com.hannesdorfmann.mosby.mvp.MvpBasePresenter;
+import app.outlay.core.executor.DefaultSubscriber;
+import app.outlay.core.utils.DateUtils;
+import app.outlay.domain.interactor.GetExpensesUseCase;
+import app.outlay.domain.model.Report;
+import app.outlay.mvp.view.StatisticView;
+import app.outlay.view.fragment.ReportFragment;
+
+import java.util.Date;
+
+import javax.inject.Inject;
+
+/**
+ * Created by Bogdan Melnychuk on 1/21/16.
+ */
+public class ReportPresenter extends MvpBasePresenter {
+ private GetExpensesUseCase loadReportUseCase;
+
+ @Inject
+ public ReportPresenter(
+ GetExpensesUseCase loadReportUseCase
+ ) {
+ this.loadReportUseCase = loadReportUseCase;
+ }
+
+ public void getExpenses(Date date, int period) {
+ Date startDate = date;
+ Date endDate = date;
+
+ switch (period) {
+ case ReportFragment.PERIOD_DAY:
+ startDate = DateUtils.getDayStart(date);
+ endDate = DateUtils.getDayEnd(date);
+ break;
+ case ReportFragment.PERIOD_WEEK:
+ startDate = DateUtils.getWeekStart(date);
+ endDate = DateUtils.getWeekEnd(date);
+ break;
+ case ReportFragment.PERIOD_MONTH:
+ startDate = DateUtils.getMonthStart(date);
+ endDate = DateUtils.getMonthEnd(date);
+ break;
+ }
+
+
+ loadReportUseCase.execute(new GetExpensesUseCase.Input(startDate, endDate, null), new DefaultSubscriber() {
+ @Override
+ public void onNext(Report report) {
+ super.onNext(report);
+ getView().showReport(report);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/app/outlay/mvp/view/AnalysisView.java b/outlay/app/src/main/java/app/outlay/mvp/view/AnalysisView.java
new file mode 100644
index 0000000..a3fec5a
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/view/AnalysisView.java
@@ -0,0 +1,17 @@
+package app.outlay.mvp.view;
+
+import com.hannesdorfmann.mosby.mvp.MvpView;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Report;
+
+import java.util.List;
+
+/**
+ * Created by bmelnychuk on 2/10/17.
+ */
+
+public interface AnalysisView extends MvpView {
+ void showAnalysis(Report report);
+
+ void setCategories(List categories);
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/view/CategoriesView.java b/outlay/app/src/main/java/app/outlay/mvp/view/CategoriesView.java
new file mode 100644
index 0000000..588f23c
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/view/CategoriesView.java
@@ -0,0 +1,15 @@
+package app.outlay.mvp.view;
+
+import com.hannesdorfmann.mosby.mvp.MvpView;
+import app.outlay.domain.model.Category;
+
+import java.util.List;
+
+/**
+ * Created by bmelnychuk on 10/25/16.
+ */
+
+public interface CategoriesView extends MvpView {
+ void showCategories(List categoryList);
+
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/view/CategoryDetailsView.java b/outlay/app/src/main/java/app/outlay/mvp/view/CategoryDetailsView.java
new file mode 100644
index 0000000..d1496f5
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/view/CategoryDetailsView.java
@@ -0,0 +1,13 @@
+package app.outlay.mvp.view;
+
+import com.hannesdorfmann.mosby.mvp.MvpView;
+import app.outlay.domain.model.Category;
+
+/**
+ * Created by bmelnychuk on 10/25/16.
+ */
+
+public interface CategoryDetailsView extends MvpView {
+ void showCategory(Category category);
+ void finish();
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/view/EnterExpenseView.java b/outlay/app/src/main/java/app/outlay/mvp/view/EnterExpenseView.java
new file mode 100644
index 0000000..94957c0
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/view/EnterExpenseView.java
@@ -0,0 +1,18 @@
+package app.outlay.mvp.view;
+
+import com.hannesdorfmann.mosby.mvp.MvpView;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * Created by bmelnychuk on 10/25/16.
+ */
+
+public interface EnterExpenseView extends MvpView {
+ void showCategories(List categoryList);
+ void setAmount(BigDecimal amount);
+ void alertExpenseSuccess(Expense expense);
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/view/ExpenseDetailsView.java b/outlay/app/src/main/java/app/outlay/mvp/view/ExpenseDetailsView.java
new file mode 100644
index 0000000..d7a724a
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/view/ExpenseDetailsView.java
@@ -0,0 +1,16 @@
+package app.outlay.mvp.view;
+
+import com.hannesdorfmann.mosby.mvp.MvpView;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+
+import java.util.List;
+
+/**
+ * Created by bmelnychuk on 10/25/16.
+ */
+
+public interface ExpenseDetailsView extends MvpView {
+ void showExpense(Expense category);
+ void showCategories(List categoryList);
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/view/ExpensesView.java b/outlay/app/src/main/java/app/outlay/mvp/view/ExpensesView.java
new file mode 100644
index 0000000..4915c23
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/view/ExpensesView.java
@@ -0,0 +1,12 @@
+package app.outlay.mvp.view;
+
+import com.hannesdorfmann.mosby.mvp.MvpView;
+import app.outlay.domain.model.Report;
+
+/**
+ * Created by bmelnychuk on 10/25/16.
+ */
+
+public interface ExpensesView extends MvpView {
+ void showReport(Report report);
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/view/LoginView.java b/outlay/app/src/main/java/app/outlay/mvp/view/LoginView.java
new file mode 100644
index 0000000..e591549
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/view/LoginView.java
@@ -0,0 +1,15 @@
+package app.outlay.mvp.view;
+
+import com.hannesdorfmann.mosby.mvp.MvpView;
+import app.outlay.domain.model.User;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public interface LoginView extends MvpView {
+ void setProgress(boolean running);
+ void error(Throwable throwable);
+ void info(String message);
+ void onSuccess(User user);
+}
diff --git a/outlay/app/src/main/java/app/outlay/mvp/view/StatisticView.java b/outlay/app/src/main/java/app/outlay/mvp/view/StatisticView.java
new file mode 100644
index 0000000..f6165e2
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/mvp/view/StatisticView.java
@@ -0,0 +1,12 @@
+package app.outlay.mvp.view;
+
+import com.hannesdorfmann.mosby.mvp.MvpView;
+import app.outlay.domain.model.Report;
+
+/**
+ * Created by bmelnychuk on 10/25/16.
+ */
+
+public interface StatisticView extends MvpView {
+ void showReport(Report report);
+}
diff --git a/outlay/app/src/main/java/com/outlay/utils/DeviceUtils.java b/outlay/app/src/main/java/app/outlay/utils/DeviceUtils.java
similarity index 74%
rename from outlay/app/src/main/java/com/outlay/utils/DeviceUtils.java
rename to outlay/app/src/main/java/app/outlay/utils/DeviceUtils.java
index 1cbd6b3..4e0154f 100644
--- a/outlay/app/src/main/java/com/outlay/utils/DeviceUtils.java
+++ b/outlay/app/src/main/java/app/outlay/utils/DeviceUtils.java
@@ -1,10 +1,12 @@
-package com.outlay.utils;
+package app.outlay.utils;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.TypedValue;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
/**
* Created by Bogdan Melnychuk on 1/15/16.
@@ -38,4 +40,12 @@ public static int getActionBarHeight(Context context) {
return result;
}
+ public static void hideKeyboard(Activity activity) {
+ View view = activity.getCurrentFocus();
+ if (view != null) {
+ InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+
}
diff --git a/outlay/app/src/main/java/app/outlay/utils/IconUtils.java b/outlay/app/src/main/java/app/outlay/utils/IconUtils.java
new file mode 100644
index 0000000..eb33cdc
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/utils/IconUtils.java
@@ -0,0 +1,142 @@
+package app.outlay.utils;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+
+import com.github.johnkil.print.PrintDrawable;
+import com.github.johnkil.print.PrintView;
+import com.mikepenz.iconics.IconicsDrawable;
+import com.mikepenz.iconics.typeface.IIcon;
+import app.outlay.domain.model.Category;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Created by Bogdan Melnychuk on 2/2/16.
+ */
+public final class IconUtils {
+ private static final String[] all = {
+ "ic_coins",
+ "ic_flight",
+ "ic_airplane",
+ "ic_bakery",
+ "ic_beach",
+ "ic_basketball",
+ "ic_front",
+ "ic_flamenco",
+ "ic_fast_food",
+ "ic_subscribe",
+ "ic_t_shirt",
+ "ic_md",
+ "ic_map",
+ "ic_medical",
+ "ic_flower",
+ "ic_fuel",
+ "ic_money",
+ "ic_tea",
+ "ic_tag",
+ "ic_mobilephone",
+ "ic_telephone",
+ "ic_multiple",
+ "ic_hairsalon",
+ "ic_books",
+ "ic_bowling",
+ "ic_hand",
+ "ic_note",
+ "ic_tool",
+ "ic_tools",
+ "ic_people",
+ "ic_hardbound",
+ "ic_box",
+ "ic_cars",
+ "ic_healthcare",
+ "ic_photo",
+ "ic_two",
+ "ic_weightlifting",
+ "ic_poor",
+ "ic_heart",
+ "ic_chart",
+ "ic_christmas",
+ "ic_insurance",
+ "ic_rent",
+ "ic_restaurant",
+ "ic_horse",
+ "ic_cigarette",
+ "ic_cleaning",
+ "ic_house",
+ "ic_run",
+ "ic_scissors",
+ "ic_italian_food",
+ "ic_justice",
+ "ic_climbing",
+ "ic_screen",
+ "ic_shopping",
+ "ic_legal",
+ "ic_controller",
+ "ic_dog",
+ "ic_lifeline",
+ "ic_sofa",
+ "ic_sportive",
+ "ic_locked",
+ "ic_donation",
+ "ic_draw",
+ "ic_luggage",
+ "ic_makeup",
+ "ic_ducks"
+ };
+
+ public static List getAll() {
+ return Arrays.asList(all);
+ }
+
+ public static void loadCategoryIcon(Category category, PrintView printView) {
+ loadCategoryIcon(category.getIcon(), printView);
+ printView.setIconColor(category.getColor());
+ }
+
+ public static Drawable getToolbarIcon(Context context, IIcon icon) {
+ return new IconicsDrawable(context)
+ .icon(icon)
+ .color(Color.WHITE)
+ .sizeDp(24);
+ }
+
+ public static Drawable getIconMaterialIcon(Context context, IIcon icon, int color, int sizeRes) {
+ return new IconicsDrawable(context)
+ .icon(icon)
+ .color(color)
+ .sizeRes(sizeRes);
+ }
+
+ public static Drawable getIconMaterialIcon(Context context, IIcon icon, int color, int sizeRes, int paddingDp) {
+ return new IconicsDrawable(context)
+ .icon(icon)
+ .color(color)
+ .paddingDp(paddingDp)
+ .sizeRes(sizeRes);
+ }
+
+ public static Drawable getToolbarIcon(Context context, IIcon icon, int paddingDp) {
+ return new IconicsDrawable(context)
+ .icon(icon)
+ .paddingDp(paddingDp)
+ .color(Color.WHITE)
+ .sizeDp(24);
+ }
+
+ public static void loadCategoryIcon(String icon, PrintView printView) {
+ printView.setIconFont("fonts/font-outlay.ttf");
+ printView.setIconCodeRes(ResourceUtils.getIntegerResource(printView.getContext(), icon));
+ }
+
+ public static Drawable getCategoryIcon(Context context, int codeResId, int color, int sizeRes) {
+ return new PrintDrawable.Builder(context)
+ .iconCodeRes(codeResId)
+ .iconFont("fonts/font-outlay.ttf")
+ .iconColor(color)
+ .iconSizeRes(sizeRes)
+ .build();
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/utils/ResourceUtils.java b/outlay/app/src/main/java/app/outlay/utils/ResourceUtils.java
similarity index 86%
rename from outlay/app/src/main/java/com/outlay/utils/ResourceUtils.java
rename to outlay/app/src/main/java/app/outlay/utils/ResourceUtils.java
index 5f39d85..56b9f82 100644
--- a/outlay/app/src/main/java/com/outlay/utils/ResourceUtils.java
+++ b/outlay/app/src/main/java/app/outlay/utils/ResourceUtils.java
@@ -1,11 +1,9 @@
-package com.outlay.utils;
+package app.outlay.utils;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.drawable.Drawable;
import com.github.johnkil.print.PrintDrawable;
-import com.outlay.R;
import java.util.Random;
@@ -31,7 +29,7 @@ public static int getIntegerResource(Context context, String stringResName) {
}
public static int randomColor(Context context, int seed) {
- int[] colors = context.getResources().getIntArray(R.array.categoryColors);
+ int[] colors = context.getResources().getIntArray(app.outlay.R.array.categoryColors);
Random r = new Random();
r.setSeed(seed);
int colorIndex = r.nextInt(colors.length);
@@ -43,7 +41,7 @@ public static Drawable getMaterialToolbarIcon(Context context, int iconResId) {
.iconTextRes(iconResId)
.iconFont("fonts/material-icon-font.ttf")
.iconColorRes(android.R.color.white)
- .iconSizeRes(R.dimen.toolbar_icon_size)
+ .iconSizeRes(app.outlay.R.dimen.toolbar_icon_size)
.build();
}
@@ -52,7 +50,7 @@ public static Drawable getCustomToolbarIcon(Context context, int codeResId) {
.iconCodeRes(codeResId)
.iconFont("fonts/font-outlay.ttf")
.iconColorRes(android.R.color.white)
- .iconSizeRes(R.dimen.toolbar_icon_size)
+ .iconSizeRes(app.outlay.R.dimen.toolbar_icon_size)
.build();
}
}
diff --git a/outlay/app/src/main/java/app/outlay/view/LoginForm.java b/outlay/app/src/main/java/app/outlay/view/LoginForm.java
new file mode 100644
index 0000000..13de901
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/LoginForm.java
@@ -0,0 +1,258 @@
+package app.outlay.view;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Build;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.TextInputLayout;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.RelativeLayout;
+
+import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic;
+
+import app.outlay.utils.DeviceUtils;
+import app.outlay.utils.IconUtils;
+import app.outlay.view.helper.AnimationUtils;
+import app.outlay.view.helper.TextWatcherAdapter;
+import app.outlay.view.helper.ViewHelper;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class LoginForm extends RelativeLayout {
+ public static final int MODE_SIGN_IN = 0;
+ public static final int MODE_SIGN_UP = 1;
+
+ @Bind(app.outlay.R.id.signInForm)
+ View signInForm;
+
+ @Bind(app.outlay.R.id.signUpForm)
+ View signUpForm;
+
+ @Bind(app.outlay.R.id.fab)
+ FloatingActionButton fab;
+
+ @Bind(app.outlay.R.id.signUpButton)
+ Button signUpButton;
+
+ @Bind(app.outlay.R.id.signInButton)
+ Button signInButton;
+
+ @Bind(app.outlay.R.id.forgetPassword)
+ Button forgetPassword;
+
+ @Bind(app.outlay.R.id.skipButton)
+ Button skipButton;
+
+ @Bind(app.outlay.R.id.signInInputEmail)
+ TextInputLayout signInEmail;
+
+ @Bind(app.outlay.R.id.signInInputPassword)
+ TextInputLayout signInPassword;
+
+ @Bind(app.outlay.R.id.signUpInputEmail)
+ TextInputLayout signUpEmail;
+
+ @Bind(app.outlay.R.id.signUpInputPassword)
+ TextInputLayout signUpPassword;
+
+ @Bind(app.outlay.R.id.signUpInputRepeatPassword)
+ TextInputLayout signUpRepeatPassword;
+
+ private OnSubmitClickListener signInListener;
+ private OnSubmitClickListener signUpListener;
+ private OnPasswordForgetClick onPasswordForgetClick;
+
+ public LoginForm(Context context) {
+ super(context);
+ init(null);
+ }
+
+ public LoginForm(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(attrs);
+ }
+
+ public LoginForm(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(attrs);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public LoginForm(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(attrs);
+ }
+
+ private void init(AttributeSet attrs) {
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View parent = inflater.inflate(app.outlay.R.layout.view_login_form, this, true);
+ ButterKnife.bind(this, parent);
+
+ signInEmail.getEditText().addTextChangedListener(new ClearErrorTextWatcher(signInEmail));
+ signInPassword.getEditText().addTextChangedListener(new ClearErrorTextWatcher(signInPassword));
+
+ signUpEmail.getEditText().addTextChangedListener(new ClearErrorTextWatcher(signUpEmail));
+ signUpPassword.getEditText().addTextChangedListener(new ClearErrorTextWatcher(signUpPassword));
+ signUpRepeatPassword.getEditText().addTextChangedListener(new ClearErrorTextWatcher(signUpRepeatPassword));
+
+ fab.setImageDrawable(
+ IconUtils.getToolbarIcon(getContext(), MaterialDesignIconic.Icon.gmi_account_add)
+ );
+
+ fab.setOnClickListener(v -> {
+ DeviceUtils.hideKeyboard((Activity) getContext());
+ Point revealPoint = getViewCenter();
+ if (signUpForm.getVisibility() == View.VISIBLE) {
+ AnimationUtils.hideWithReveal(signUpForm, revealPoint);
+ fab.setImageDrawable(IconUtils.getToolbarIcon(getContext(), MaterialDesignIconic.Icon.gmi_account_add));
+ } else {
+ AnimationUtils.showWithReveal(signUpForm, revealPoint);
+ fab.setImageDrawable(IconUtils.getToolbarIcon(getContext(), MaterialDesignIconic.Icon.gmi_close, 4));
+ }
+ });
+
+ signInButton.setOnClickListener(v -> {
+ if (isSignInInputValid() && signInListener != null) {
+ signInListener.onSubmit(
+ signInEmail.getEditText().getText().toString(),
+ signInPassword.getEditText().getText().toString(),
+ v
+ );
+ }
+ });
+
+ signUpButton.setOnClickListener(v -> {
+ if (isSignUpInputValid() && signUpListener != null) {
+ signUpListener.onSubmit(
+ signUpEmail.getEditText().getText().toString(),
+ signUpPassword.getEditText().getText().toString(),
+ v
+ );
+ }
+ });
+
+ forgetPassword.setOnClickListener(v -> {
+ if (onPasswordForgetClick != null) {
+ onPasswordForgetClick.onPasswordForget();
+ }
+ });
+ }
+
+ private boolean isSignInInputValid() {
+ boolean result = true;
+ if (!isEmailValid(signInEmail.getEditText().getText())) {
+ signInEmail.setErrorEnabled(true);
+ signInEmail.setError(getContext().getString(app.outlay.R.string.error_signin_invalid_email));
+ result = false;
+ }
+ String password = signInPassword.getEditText().getText().toString();
+ if (TextUtils.isEmpty(password) || password.length() < 6) {
+ signInPassword.setErrorEnabled(true);
+ signInPassword.setError(getContext().getString(app.outlay.R.string.error_signin_invalid_password));
+ result = false;
+ }
+ return result;
+ }
+
+ private boolean isSignUpInputValid() {
+ boolean result = true;
+ if (!isEmailValid(signUpEmail.getEditText().getText())) {
+ signUpEmail.setErrorEnabled(true);
+ signUpEmail.setError(getContext().getString(app.outlay.R.string.error_signin_invalid_email));
+ result = false;
+ }
+
+ String password = signUpPassword.getEditText().getText().toString();
+
+ if (TextUtils.isEmpty(password) || password.length() < 6) {
+ signUpPassword.setErrorEnabled(true);
+ signUpPassword.setError(getContext().getString(app.outlay.R.string.error_signin_invalid_password));
+ result = false;
+ return result;
+ }
+
+ String repeatPassword = signUpRepeatPassword.getEditText().getText().toString();
+ if (!password.equals(repeatPassword)) {
+ signUpRepeatPassword.setErrorEnabled(true);
+ signUpRepeatPassword.setError(getContext().getString(app.outlay.R.string.error_signin_password_match));
+ result = false;
+ }
+
+ return result;
+ }
+
+ public void setOnSkipButtonClick(View.OnClickListener click) {
+ skipButton.setOnClickListener(click);
+ }
+
+ public void setOnSignInClickListener(OnSubmitClickListener signInListener) {
+ this.signInListener = signInListener;
+ }
+
+ public void setOnSignUpClickListener(OnSubmitClickListener signUpListener) {
+ this.signUpListener = signUpListener;
+ }
+
+ public void setOnPasswordForgetClick(OnPasswordForgetClick onPasswordForgetClick) {
+ this.onPasswordForgetClick = onPasswordForgetClick;
+ }
+
+ public void setMode(int mode) {
+ signUpForm.setVisibility(mode == MODE_SIGN_UP ? VISIBLE : INVISIBLE);
+ fab.setImageDrawable(mode == MODE_SIGN_UP ?
+ IconUtils.getToolbarIcon(getContext(), MaterialDesignIconic.Icon.gmi_account_add) :
+ IconUtils.getToolbarIcon(getContext(), MaterialDesignIconic.Icon.gmi_close)
+ );
+ }
+
+ public void setToggleModeButtonVisible(boolean visible) {
+ fab.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ private void toggleViewVisibility(View view) {
+ boolean visible = view.getVisibility() == View.VISIBLE;
+ view.setVisibility(visible ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ private Point getViewCenter() {
+ int x = signUpForm.getRight();
+ int y = signUpForm.getTop() + ViewHelper.dpToPx(56);
+ return new Point(x, y);
+ }
+
+ public interface OnSubmitClickListener {
+ void onSubmit(String email, String password, View src);
+ }
+
+ public interface OnPasswordForgetClick {
+ void onPasswordForget();
+ }
+
+ boolean isEmailValid(CharSequence email) {
+ return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches();
+ }
+
+ private static class ClearErrorTextWatcher extends TextWatcherAdapter {
+ TextInputLayout inputLayout;
+
+ public ClearErrorTextWatcher(TextInputLayout inputLayout) {
+ this.inputLayout = inputLayout;
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ inputLayout.setErrorEnabled(false);
+ }
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/Navigator.java b/outlay/app/src/main/java/app/outlay/view/Navigator.java
new file mode 100644
index 0000000..f50d689
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/Navigator.java
@@ -0,0 +1,98 @@
+package app.outlay.view;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+
+import app.outlay.domain.model.Expense;
+import app.outlay.view.activity.LoginActivity;
+import app.outlay.view.activity.MainActivity;
+import app.outlay.view.activity.SingleFragmentActivity;
+import app.outlay.view.activity.SyncGuestActivity;
+import app.outlay.view.fragment.AnalysisFragment;
+import app.outlay.view.fragment.CategoriesFragment;
+import app.outlay.view.fragment.CategoryDetailsFragment;
+import app.outlay.view.fragment.ExpensesDetailsFragment;
+import app.outlay.view.fragment.ExpensesListFragment;
+import app.outlay.view.fragment.ReportFragment;
+
+import java.util.Date;
+
+/**
+ * Created by Bogdan Melnychuk on 1/24/16.
+ */
+public final class Navigator {
+ public static void goToCategoryDetails(FragmentActivity activityFrom, String categoryId) {
+ Bundle b = new Bundle();
+ if (categoryId != null) {
+ b.putString(CategoryDetailsFragment.ARG_CATEGORY_PARAM, categoryId);
+ }
+ changeFragment(activityFrom, CategoryDetailsFragment.class, b);
+ }
+
+ public static void goToCategoriesList(FragmentActivity from) {
+ SingleFragmentActivity.start(from, CategoriesFragment.class);
+ }
+
+ public static void goToMainScreen(FragmentActivity activityFrom) {
+ Intent intent = new Intent(activityFrom, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+ activityFrom.startActivity(intent);
+ }
+
+ public static void goToLoginScreen(FragmentActivity activityFrom) {
+ Intent intent = new Intent(activityFrom, LoginActivity.class);
+ activityFrom.startActivity(intent);
+ }
+
+ public static void goToSyncGuestActivity(FragmentActivity activityFrom) {
+ Intent intent = new Intent(activityFrom, SyncGuestActivity.class);
+ activityFrom.startActivity(intent);
+ }
+
+ public static void goToReport(FragmentActivity activityFrom, Date date) {
+ Bundle b = new Bundle();
+ b.putLong(ReportFragment.ARG_DATE, date.getTime());
+ SingleFragmentActivity.start(activityFrom, ReportFragment.class, b);
+ }
+
+ public static void goToExpensesList(FragmentActivity activityFrom, Date dateFrom, Date dateTo, String categoryId) {
+ Bundle b = new Bundle();
+ if (categoryId != null) {
+ b.putString(ExpensesListFragment.ARG_CATEGORY_ID, categoryId);
+ }
+ if (dateFrom != null) {
+ b.putLong(ExpensesListFragment.ARG_DATE_FROM, dateFrom.getTime());
+ }
+ if (dateTo != null) {
+ b.putLong(ExpensesListFragment.ARG_DATE_TO, dateTo.getTime());
+ }
+ changeFragment(activityFrom, ExpensesListFragment.class, b);
+ }
+
+ public static void goToExpenseDetails(FragmentActivity activityFrom, Expense expense) {
+ Bundle b = new Bundle();
+ if (expense != null) {
+ b.putString(ExpensesDetailsFragment.ARG_EXPENSE_ID, expense.getId());
+ b.putLong(ExpensesDetailsFragment.ARG_DATE, expense.getReportedWhen().getTime());
+ }
+ changeFragment(activityFrom, ExpensesDetailsFragment.class, b);
+ }
+
+ public static void goToAnalysis(FragmentActivity activityFrom) {
+ Bundle b = new Bundle();
+ changeFragment(activityFrom, AnalysisFragment.class, b);
+ }
+
+ private static void changeFragment(FragmentActivity activityFrom, Class> clazz, Bundle b) {
+ String className = clazz.getName();
+ Fragment f = Fragment.instantiate(activityFrom, className);
+ f.setArguments(b);
+ activityFrom.getSupportFragmentManager()
+ .beginTransaction()
+ .replace(app.outlay.R.id.fragment, f, className)
+ .addToBackStack(className)
+ .commit();
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/activity/LoginActivity.java b/outlay/app/src/main/java/app/outlay/view/activity/LoginActivity.java
new file mode 100644
index 0000000..ed0f401
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/activity/LoginActivity.java
@@ -0,0 +1,23 @@
+package app.outlay.view.activity;
+
+import android.os.Bundle;
+
+import app.outlay.view.activity.base.ParentActivity;
+import app.outlay.view.fragment.LoginFragment;
+
+public class LoginActivity extends ParentActivity {
+ private LoginFragment loginFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(app.outlay.R.layout.activity_single_fragment);
+ this.initializeActivity(savedInstanceState);
+ }
+
+ private void initializeActivity(Bundle savedInstanceState) {
+ loginFragment = new LoginFragment();
+ loginFragment.setArguments(getIntent().getExtras());
+ addFragment(app.outlay.R.id.fragment, loginFragment);
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/activity/MainActivity.java b/outlay/app/src/main/java/app/outlay/view/activity/MainActivity.java
new file mode 100644
index 0000000..6089440
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/activity/MainActivity.java
@@ -0,0 +1,38 @@
+package app.outlay.view.activity;
+
+import android.os.Bundle;
+
+import com.google.firebase.auth.FirebaseAuth;
+
+import app.outlay.view.Navigator;
+import app.outlay.view.activity.base.DrawerActivity;
+import app.outlay.view.fragment.MainFragment;
+
+public class MainActivity extends DrawerActivity {
+
+ private MainFragment mainFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(app.outlay.R.layout.activity_single_fragment);
+
+ Bundle b = getIntent().getExtras();
+ mainFragment = new MainFragment();
+ mainFragment.setArguments(b);
+ addFragment(app.outlay.R.id.fragment, mainFragment);
+ }
+
+ @Override
+ protected void signOut() {
+ getApp().releaseUserComponent();
+ FirebaseAuth.getInstance().signOut();
+ Navigator.goToLoginScreen(this);
+ finish();
+ }
+
+ @Override
+ protected void createUser() {
+ Navigator.goToSyncGuestActivity(this);
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/view/activity/SingleFragmentActivity.java b/outlay/app/src/main/java/app/outlay/view/activity/SingleFragmentActivity.java
similarity index 73%
rename from outlay/app/src/main/java/com/outlay/view/activity/SingleFragmentActivity.java
rename to outlay/app/src/main/java/app/outlay/view/activity/SingleFragmentActivity.java
index a6cf9e0..708275c 100644
--- a/outlay/app/src/main/java/com/outlay/view/activity/SingleFragmentActivity.java
+++ b/outlay/app/src/main/java/app/outlay/view/activity/SingleFragmentActivity.java
@@ -1,14 +1,14 @@
-package com.outlay.view.activity;
+package app.outlay.view.activity;
-import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
-import com.outlay.R;
+import app.outlay.view.activity.base.ParentActivity;
-public class SingleFragmentActivity extends BaseActivity {
+public class SingleFragmentActivity extends ParentActivity {
public static void start(Context context, Class> fragmentClass, Bundle bundle) {
Intent intent = new Intent(context, SingleFragmentActivity.class);
@@ -29,7 +29,7 @@ public static void start(Context context, Class> fragmentClass) {
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
- setContentView(R.layout.activity_single_fragment);
+ setContentView(app.outlay.R.layout.activity_single_fragment);
setTitle(null);
Bundle b = getIntent().getExtras();
@@ -37,7 +37,7 @@ protected void onCreate(Bundle bundle) {
if (bundle == null) {
Fragment f = Fragment.instantiate(this, fragmentClass.getName());
f.setArguments(b);
- getFragmentManager().beginTransaction().replace(R.id.fragment, f, fragmentClass.getName()).commit();
+ getSupportFragmentManager().beginTransaction().replace(app.outlay.R.id.fragment, f, fragmentClass.getName()).commit();
}
}
@@ -49,9 +49,4 @@ public void onBackPressed() {
super.onBackPressed();
}
}
-
- @Override
- public boolean hasDrawer() {
- return false;
- }
}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/app/outlay/view/activity/SyncGuestActivity.java b/outlay/app/src/main/java/app/outlay/view/activity/SyncGuestActivity.java
new file mode 100644
index 0000000..65f0f4a
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/activity/SyncGuestActivity.java
@@ -0,0 +1,23 @@
+package app.outlay.view.activity;
+
+import android.os.Bundle;
+
+import app.outlay.view.activity.base.ParentActivity;
+import app.outlay.view.fragment.SyncGuestFragment;
+
+public class SyncGuestActivity extends ParentActivity {
+ private SyncGuestFragment syncGuestFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(app.outlay.R.layout.activity_single_fragment);
+ this.initializeActivity(savedInstanceState);
+ }
+
+ private void initializeActivity(Bundle savedInstanceState) {
+ syncGuestFragment = new SyncGuestFragment();
+ syncGuestFragment.setArguments(getIntent().getExtras());
+ addFragment(app.outlay.R.id.fragment, syncGuestFragment);
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/activity/base/BaseActivity.java b/outlay/app/src/main/java/app/outlay/view/activity/base/BaseActivity.java
new file mode 100644
index 0000000..0ae76eb
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/activity/base/BaseActivity.java
@@ -0,0 +1,18 @@
+package app.outlay.view.activity.base;
+
+import android.view.View;
+
+import app.outlay.App;
+import app.outlay.analytics.Analytics;
+import app.outlay.di.component.AppComponent;
+
+/**
+ * Created by bmelnychuk on 12/14/16.
+ */
+
+public interface BaseActivity {
+ App getApp();
+ View getRootView();
+ AppComponent getApplicationComponent();
+ Analytics analytics();
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/activity/base/DrawerActivity.java b/outlay/app/src/main/java/app/outlay/view/activity/base/DrawerActivity.java
new file mode 100644
index 0000000..ac101b4
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/activity/base/DrawerActivity.java
@@ -0,0 +1,133 @@
+package app.outlay.view.activity.base;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic;
+import com.mikepenz.materialdrawer.AccountHeader;
+import com.mikepenz.materialdrawer.AccountHeaderBuilder;
+import com.mikepenz.materialdrawer.Drawer;
+import com.mikepenz.materialdrawer.DrawerBuilder;
+import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
+import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
+import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
+import app.outlay.Constants;
+import app.outlay.domain.model.User;
+import app.outlay.view.activity.SingleFragmentActivity;
+import app.outlay.view.alert.Alert;
+import app.outlay.view.fragment.AboutFragment;
+import app.outlay.view.fragment.AnalysisFragment;
+import app.outlay.view.fragment.CategoriesFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by bmelnychuk on 12/14/16.
+ */
+
+public abstract class DrawerActivity extends ParentActivity {
+ private static final int ITEM_CATEGORIES = 1;
+ private static final int ITEM_FEEDBACK = 2;
+ private static final int ITEM_ABOUT = 3;
+ private static final int ITEM_SING_OUT = 4;
+ private static final int ITEM_CREATE_USER = 5;
+ private static final int ITEM_ANALYSIS = 6;
+
+ private Drawer mainDrawer;
+
+ public void setupDrawer(User currentUser) {
+ String email = TextUtils.isEmpty(currentUser.getEmail()) ? "Guest" : currentUser.getEmail();
+
+ AccountHeader headerResult = new AccountHeaderBuilder()
+ .withActivity(this)
+ .withHeaderBackground(app.outlay.R.drawable.drawer_image)
+ .withAlternativeProfileHeaderSwitching(false)
+ .withSelectionListEnabledForSingleProfile(false)
+ .withProfileImagesClickable(false)
+ .withCloseDrawerOnProfileListClick(false)
+ .addProfiles(
+ new ProfileDrawerItem().withEmail(email)
+ )
+ .build();
+
+ List items = new ArrayList<>();
+ if (currentUser.isAnonymous()) {
+ items.add(new PrimaryDrawerItem().withName(app.outlay.R.string.menu_item_create_user).withIcon(MaterialDesignIconic.Icon.gmi_account_add).withIdentifier(ITEM_CREATE_USER));
+ }
+
+ items.add(new PrimaryDrawerItem().withName(app.outlay.R.string.menu_item_analysis).withIcon(MaterialDesignIconic.Icon.gmi_trending_up).withIdentifier(ITEM_ANALYSIS));
+ items.add(new PrimaryDrawerItem().withName(app.outlay.R.string.menu_item_categories).withIcon(MaterialDesignIconic.Icon.gmi_apps).withIdentifier(ITEM_CATEGORIES));
+ items.add(new PrimaryDrawerItem().withName(app.outlay.R.string.menu_item_feedback).withIcon(MaterialDesignIconic.Icon.gmi_email).withIdentifier(ITEM_FEEDBACK));
+ //items.add(new PrimaryDrawerItem().withName(R.string.menu_item_about).withIcon(MaterialDesignIconic.Icon.gmi_info).withIdentifier(ITEM_ABOUT));
+ items.add(new PrimaryDrawerItem().withName(app.outlay.R.string.menu_item_signout).withIcon(MaterialDesignIconic.Icon.gmi_sign_in).withIdentifier(ITEM_SING_OUT));
+
+
+ mainDrawer = new DrawerBuilder()
+ .withFullscreen(true)
+ .withActivity(this)
+ .withAccountHeader(headerResult)
+ .withSelectedItem(-1)
+ .addDrawerItems(items.toArray(new IDrawerItem[items.size()]))
+ .withOnDrawerItemClickListener((view, i, iDrawerItem) -> {
+ if (iDrawerItem != null) {
+ int id = (int) iDrawerItem.getIdentifier();
+ switch (id) {
+ case ITEM_CATEGORIES:
+ analytics().trackViewCategoriesList();
+ SingleFragmentActivity.start(this, CategoriesFragment.class);
+ break;
+ case ITEM_ANALYSIS:
+ analytics().trackAnalysisView();
+ SingleFragmentActivity.start(this, AnalysisFragment.class);
+ break;
+ case ITEM_FEEDBACK:
+ analytics().trackFeedbackClick();
+ Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(
+ "mailto", Constants.CONTACT_EMAIL, null));
+ emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{Constants.CONTACT_EMAIL});
+ emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Outlay Feedback");
+ try {
+ startActivity(Intent.createChooser(emailIntent, getString(app.outlay.R.string.label_send_email)));
+ } catch (android.content.ActivityNotFoundException ex) {
+ Alert.error(getRootView(), getString(app.outlay.R.string.error_no_email_clients));
+ }
+ break;
+ case ITEM_ABOUT:
+ SingleFragmentActivity.start(this, AboutFragment.class);
+ break;
+ case ITEM_SING_OUT:
+ analytics().trackSingOut();
+ signOut();
+ break;
+ case ITEM_CREATE_USER:
+ createUser();
+ break;
+ }
+
+ mainDrawer.setSelection(-1);
+ mainDrawer.closeDrawer();
+ }
+ return false;
+ })
+ .build();
+ }
+
+ protected abstract void signOut();
+
+ protected abstract void createUser();
+
+ @Override
+ public void onBackPressed() {
+ if (mainDrawer != null && mainDrawer.isDrawerOpen()) {
+ mainDrawer.closeDrawer();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ public Drawer getMainDrawer() {
+ return mainDrawer;
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/activity/base/ParentActivity.java b/outlay/app/src/main/java/app/outlay/view/activity/base/ParentActivity.java
new file mode 100644
index 0000000..22f9822
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/activity/base/ParentActivity.java
@@ -0,0 +1,58 @@
+package app.outlay.view.activity.base;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+
+import app.outlay.App;
+import app.outlay.analytics.Analytics;
+import app.outlay.di.component.AppComponent;
+
+import butterknife.ButterKnife;
+
+/**
+ * Created by Bogdan Melnychuk on 1/15/16.
+ */
+public class ParentActivity extends AppCompatActivity implements BaseActivity {
+
+ @Override
+ public void setContentView(int layoutResID) {
+ super.setContentView(layoutResID);
+ ButterKnife.bind(this);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.getApplicationComponent().inject(this);
+ setTitle(null);
+ }
+
+ @Override
+ public View getRootView() {
+ return findViewById(android.R.id.content);
+ }
+
+ @Override
+ public App getApp() {
+ return (App) getApplication();
+ }
+
+ @Override
+ public AppComponent getApplicationComponent() {
+ return getApp().getAppComponent();
+ }
+
+ @Override
+ public Analytics analytics() {
+ return getApplicationComponent().analytics();
+ }
+
+ protected void addFragment(int containerViewId, Fragment fragment) {
+ FragmentTransaction fragmentTransaction = this.getSupportFragmentManager().beginTransaction();
+ fragmentTransaction.add(containerViewId, fragment);
+ fragmentTransaction.commit();
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/adapter/CategoriesDraggableGridAdapter.java b/outlay/app/src/main/java/app/outlay/view/adapter/CategoriesDraggableGridAdapter.java
similarity index 73%
rename from outlay/app/src/main/java/com/outlay/adapter/CategoriesDraggableGridAdapter.java
rename to outlay/app/src/main/java/app/outlay/view/adapter/CategoriesDraggableGridAdapter.java
index 317f590..0934a9b 100644
--- a/outlay/app/src/main/java/com/outlay/adapter/CategoriesDraggableGridAdapter.java
+++ b/outlay/app/src/main/java/app/outlay/view/adapter/CategoriesDraggableGridAdapter.java
@@ -1,20 +1,22 @@
-package com.outlay.adapter;
+package app.outlay.view.adapter;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
import android.widget.TextView;
-import com.github.johnkil.print.PrintView;
-import com.outlay.R;
-import com.outlay.adapter.listener.OnCategoryClickListener;
-import com.outlay.dao.Category;
-import com.outlay.utils.IconUtils;
-import com.outlay.view.helper.itemtouch.ItemTouchHelperAdapter;
-import com.outlay.view.helper.itemtouch.ItemTouchHelperViewHolder;
-import com.outlay.view.helper.itemtouch.OnDragListener;
+import app.outlay.domain.model.Category;
+import app.outlay.utils.IconUtils;
+import app.outlay.utils.ResourceUtils;
+import app.outlay.view.adapter.listener.OnCategoryClickListener;
+import app.outlay.view.helper.itemtouch.ItemTouchHelperAdapter;
+import app.outlay.view.helper.itemtouch.ItemTouchHelperViewHolder;
+import app.outlay.view.helper.itemtouch.OnDragListener;
import java.util.ArrayList;
import java.util.List;
@@ -67,7 +69,7 @@ public boolean onItemMove(int fromPosition, int toPosition) {
@Override
public CategoryDraggableViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- final View v = inflater.inflate(R.layout.item_category, parent, false);
+ final View v = inflater.inflate(app.outlay.R.layout.item_category, parent, false);
final CategoryDraggableViewHolder viewHolder = new CategoryDraggableViewHolder(v);
return viewHolder;
}
@@ -83,9 +85,12 @@ public int getItemCount() {
@Override
public void onBindViewHolder(CategoryDraggableViewHolder holder, int position) {
+ Context context = holder.categoryContainer.getContext();
Category currentOne = items.get(position);
+ int iconCodeRes = ResourceUtils.getIntegerResource(context, currentOne.getIcon());
+ Drawable categoryIcon = IconUtils.getCategoryIcon(context, iconCodeRes, currentOne.getColor(), app.outlay.R.dimen.category_icon);
+ holder.categoryIcon.setImageDrawable(categoryIcon);
holder.categoryTitle.setText(currentOne.getTitle());
- IconUtils.loadCategoryIcon(currentOne, holder.categoryIcon);
holder.categoryIcon.setOnClickListener(v -> {
if (clickListener != null) {
clickListener.onCategoryClicked(currentOne);
@@ -100,13 +105,13 @@ public void setDragListener(OnDragListener dragListener) {
public class CategoryDraggableViewHolder extends RecyclerView.ViewHolder implements
ItemTouchHelperViewHolder {
- @Bind(R.id.categoryContainer)
+ @Bind(app.outlay.R.id.categoryContainer)
View categoryContainer;
- @Bind(R.id.categoryIcon)
- PrintView categoryIcon;
+ @Bind(app.outlay.R.id.categoryIcon)
+ ImageView categoryIcon;
- @Bind(R.id.categoryTitle)
+ @Bind(app.outlay.R.id.categoryTitle)
TextView categoryTitle;
public CategoryDraggableViewHolder(View v) {
@@ -116,7 +121,7 @@ public CategoryDraggableViewHolder(View v) {
@Override
public void onItemSelected() {
- itemView.setBackgroundColor(ContextCompat.getColor(categoryIcon.getContext(), R.color.dark));
+ itemView.setBackgroundColor(ContextCompat.getColor(categoryIcon.getContext(), app.outlay.R.color.dark));
}
@Override
diff --git a/outlay/app/src/main/java/app/outlay/view/adapter/CategoriesGridAdapter.java b/outlay/app/src/main/java/app/outlay/view/adapter/CategoriesGridAdapter.java
new file mode 100644
index 0000000..97cad6b
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/adapter/CategoriesGridAdapter.java
@@ -0,0 +1,138 @@
+package app.outlay.view.adapter;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import app.outlay.domain.model.Category;
+import app.outlay.utils.IconUtils;
+import app.outlay.utils.ResourceUtils;
+import app.outlay.view.adapter.listener.OnCategoryClickListener;
+import app.outlay.view.numpad.NumpadEditable;
+import app.outlay.view.numpad.NumpadValidator;
+import app.outlay.view.numpad.NumpadView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * Created by Bogdan Melnychuk on 1/15/16.
+ */
+public class CategoriesGridAdapter extends RecyclerView.Adapter {
+ private static final int HEADER = 0;
+ private static final int CATEGORY = 1;
+
+ protected List items;
+ private OnCategoryClickListener clickListener;
+ private NumpadEditable numpadEditable;
+ private NumpadValidator numpadValidator;
+
+ public void setOnCategoryClickListener(OnCategoryClickListener listener) {
+ this.clickListener = listener;
+ }
+
+ public void attachNumpadEditable(NumpadEditable numpadEditable, NumpadValidator validator) {
+ this.numpadEditable = numpadEditable;
+ this.numpadValidator = validator;
+ }
+
+ public CategoriesGridAdapter(List categories) {
+ this.items = categories;
+ }
+
+ public CategoriesGridAdapter() {
+ this(new ArrayList<>());
+ }
+
+ public void setItems(List items) {
+ this.items = items;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position == 0 ? HEADER : CATEGORY;
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ Context context = parent.getContext();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ switch (viewType) {
+ case HEADER:
+ final View numpadView = inflater.inflate(app.outlay.R.layout.recycler_numpad, parent, false);
+ GridLayoutManager.LayoutParams params = (GridLayoutManager.LayoutParams) numpadView.getLayoutParams();
+ params.height = parent.getMeasuredHeight() - (context.getResources().getDimensionPixelSize(app.outlay.R.dimen.category_item_height) * 2);
+
+ final NumpadViewHolder viewHolder = new NumpadViewHolder(numpadView);
+ return viewHolder;
+ default:
+ final View catView = inflater.inflate(app.outlay.R.layout.item_category, parent, false);
+ final CategoryViewHolder categoryViewHolder = new CategoryViewHolder(catView);
+ return categoryViewHolder;
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ if (holder instanceof CategoryViewHolder) {
+ CategoryViewHolder categoryViewHolder = (CategoryViewHolder) holder;
+ Context context = categoryViewHolder.categoryContainer.getContext();
+
+ Category currentOne = items.get(position - 1);
+ int iconCodeRes = ResourceUtils.getIntegerResource(context, currentOne.getIcon());
+
+ categoryViewHolder.categoryTitle.setText(currentOne.getTitle());
+ Drawable categoryIcon = IconUtils.getCategoryIcon(context, iconCodeRes, currentOne.getColor(), app.outlay.R.dimen.category_icon);
+ categoryViewHolder.categoryIcon.setImageDrawable(categoryIcon);
+ categoryViewHolder.categoryIcon.setOnClickListener(v -> {
+ if (clickListener != null) {
+ clickListener.onCategoryClicked(currentOne);
+ }
+ });
+ } else if (holder instanceof NumpadViewHolder) {
+ NumpadViewHolder numpadViewHolder = (NumpadViewHolder) holder;
+ numpadViewHolder.numpadView.attachEditable(numpadEditable, numpadValidator);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return items.size() + 1;
+ }
+
+ public class CategoryViewHolder extends RecyclerView.ViewHolder {
+ @Bind(app.outlay.R.id.categoryContainer)
+ View categoryContainer;
+
+ @Bind(app.outlay.R.id.categoryIcon)
+ ImageView categoryIcon;
+
+ @Bind(app.outlay.R.id.categoryTitle)
+ TextView categoryTitle;
+
+ public CategoryViewHolder(View v) {
+ super(v);
+ ButterKnife.bind(this, v);
+ }
+ }
+
+ public class NumpadViewHolder extends RecyclerView.ViewHolder {
+ @Bind(app.outlay.R.id.numpadView)
+ NumpadView numpadView;
+
+ public NumpadViewHolder(View v) {
+ super(v);
+ ButterKnife.bind(this, v);
+ }
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/adapter/ExpenseAdapter.java b/outlay/app/src/main/java/app/outlay/view/adapter/ExpenseAdapter.java
new file mode 100644
index 0000000..c62989c
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/adapter/ExpenseAdapter.java
@@ -0,0 +1,42 @@
+package app.outlay.view.adapter;
+
+import android.support.v7.widget.RecyclerView;
+
+import app.outlay.domain.model.Expense;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Bogdan Melnychuk on 1/15/16.
+ */
+public abstract class ExpenseAdapter extends RecyclerView.Adapter {
+ protected List items;
+ protected OnExpenseClickListener onExpenseClickListener;
+
+ public ExpenseAdapter(List categories) {
+ this.items = categories;
+ }
+
+ public ExpenseAdapter() {
+ this(new ArrayList<>());
+ }
+
+ public void setItems(List items) {
+ this.items = items;
+ notifyDataSetChanged();
+ }
+
+ public void setOnExpenseClickListener(OnExpenseClickListener onExpenseClickListener) {
+ this.onExpenseClickListener = onExpenseClickListener;
+ }
+
+ @Override
+ public int getItemCount() {
+ return items.size();
+ }
+
+ public interface OnExpenseClickListener {
+ void onExpenseClicked(Expense e);
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/adapter/GridExpensesAdapter.java b/outlay/app/src/main/java/app/outlay/view/adapter/GridExpensesAdapter.java
new file mode 100644
index 0000000..74a23f4
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/adapter/GridExpensesAdapter.java
@@ -0,0 +1,71 @@
+package app.outlay.view.adapter;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.github.johnkil.print.PrintView;
+
+import app.outlay.core.utils.DateUtils;
+import app.outlay.core.utils.NumberUtils;
+import app.outlay.domain.model.Expense;
+import app.outlay.utils.IconUtils;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * Created by bmelnychuk on 2/10/17.
+ */
+
+public class GridExpensesAdapter extends ExpenseAdapter {
+ @Override
+ public ExpenseGridItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ final View v = inflater.inflate(app.outlay.R.layout.recycler_grid_expense, parent, false);
+ final ExpenseGridItemViewHolder viewHolder = new ExpenseGridItemViewHolder(v);
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(ExpenseGridItemViewHolder holder, int position) {
+ Expense expense = items.get(position);
+ holder.note.setText(expense.getNote());
+ holder.root.setOnClickListener(v -> {
+ if (onExpenseClickListener != null) {
+ onExpenseClickListener.onExpenseClicked(expense);
+ }
+ });
+ holder.categoryAmount.setText(NumberUtils.formatAmount(expense.getAmount()));
+ holder.categoryDate.setText(DateUtils.toShortString(expense.getReportedWhen()));
+ holder.categoryTitle.setText(expense.getCategory().getTitle());
+ IconUtils.loadCategoryIcon(expense.getCategory(), holder.categoryIcon);
+ }
+
+ public class ExpenseGridItemViewHolder extends RecyclerView.ViewHolder {
+ @Bind(app.outlay.R.id.categoryNote)
+ TextView note;
+
+ @Bind(app.outlay.R.id.expenseContainer)
+ View root;
+
+ @Bind(app.outlay.R.id.categoryIcon)
+ PrintView categoryIcon;
+
+ @Bind(app.outlay.R.id.categoryTitle)
+ TextView categoryTitle;
+
+ @Bind(app.outlay.R.id.categoryDate)
+ TextView categoryDate;
+
+ @Bind(app.outlay.R.id.categoryAmount)
+ TextView categoryAmount;
+
+ public ExpenseGridItemViewHolder(View v) {
+ super(v);
+ ButterKnife.bind(this, v);
+ }
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/adapter/IconsGridAdapter.java b/outlay/app/src/main/java/app/outlay/view/adapter/IconsGridAdapter.java
similarity index 89%
rename from outlay/app/src/main/java/com/outlay/adapter/IconsGridAdapter.java
rename to outlay/app/src/main/java/app/outlay/view/adapter/IconsGridAdapter.java
index 7c6ce31..0db9b88 100644
--- a/outlay/app/src/main/java/com/outlay/adapter/IconsGridAdapter.java
+++ b/outlay/app/src/main/java/app/outlay/view/adapter/IconsGridAdapter.java
@@ -1,4 +1,4 @@
-package com.outlay.adapter;
+package app.outlay.view.adapter;
import android.content.Context;
import android.support.v4.content.ContextCompat;
@@ -8,9 +8,8 @@
import android.view.ViewGroup;
import com.github.johnkil.print.PrintView;
-import com.outlay.R;
-import com.outlay.utils.IconUtils;
-import com.outlay.utils.ResourceUtils;
+
+import app.outlay.utils.IconUtils;
import java.util.List;
@@ -61,7 +60,7 @@ public void setActiveItem(String icon, int color) {
@Override
public CategoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- final View v = inflater.inflate(R.layout.item_icon, parent, false);
+ final View v = inflater.inflate(app.outlay.R.layout.item_icon, parent, false);
final CategoryViewHolder viewHolder = new CategoryViewHolder(v);
return viewHolder;
}
@@ -75,7 +74,7 @@ public void onBindViewHolder(CategoryViewHolder holder, int position) {
if (currentOne.equals(selectedIcon)) {
holder.categoryIcon.setIconColor(activeColor);
} else {
- holder.categoryIcon.setIconColor(ContextCompat.getColor(context, R.color.icon_inactive));
+ holder.categoryIcon.setIconColor(ContextCompat.getColor(context, app.outlay.R.color.icon_inactive));
}
holder.categoryIcon.setOnClickListener(v -> {
@@ -94,10 +93,10 @@ public int getItemCount() {
}
public class CategoryViewHolder extends RecyclerView.ViewHolder {
- @Bind(R.id.categoryContainer)
+ @Bind(app.outlay.R.id.categoryContainer)
View categoryContainer;
- @Bind(R.id.categoryIcon)
+ @Bind(app.outlay.R.id.categoryIcon)
PrintView categoryIcon;
public CategoryViewHolder(View v) {
diff --git a/outlay/app/src/main/java/app/outlay/view/adapter/ListExpensesAdapter.java b/outlay/app/src/main/java/app/outlay/view/adapter/ListExpensesAdapter.java
new file mode 100644
index 0000000..0b1835f
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/adapter/ListExpensesAdapter.java
@@ -0,0 +1,77 @@
+package app.outlay.view.adapter;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import app.outlay.core.utils.DateUtils;
+import app.outlay.core.utils.NumberUtils;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+import app.outlay.utils.IconUtils;
+import app.outlay.utils.ResourceUtils;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * Created by bmelnychuk on 2/10/17.
+ */
+
+public class ListExpensesAdapter extends ExpenseAdapter {
+ @Override
+ public ExpenseListItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ final View v = inflater.inflate(app.outlay.R.layout.recycler_list_expense, parent, false);
+ final ExpenseListItemViewHolder viewHolder = new ExpenseListItemViewHolder(v);
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(ExpenseListItemViewHolder holder, int position) {
+ Expense expense = items.get(position);
+ Context context = holder.container.getContext();
+ holder.container.setOnClickListener(v -> {
+ if (onExpenseClickListener != null) {
+ onExpenseClickListener.onExpenseClicked(expense);
+ }
+ });
+
+ holder.amount.setText(NumberUtils.formatAmount(expense.getAmount()));
+ holder.date.setText(DateUtils.toShortString(expense.getReportedWhen()));
+
+ Category currentOne = expense.getCategory();
+ int iconCodeRes = ResourceUtils.getIntegerResource(context, currentOne.getIcon());
+ Drawable categoryIcon = IconUtils.getCategoryIcon(context, iconCodeRes, currentOne.getColor(), app.outlay.R.dimen.report_category_icon);
+ holder.icon.setImageDrawable(categoryIcon);
+
+ holder.title.setText(currentOne.getTitle());
+ }
+
+ public class ExpenseListItemViewHolder extends RecyclerView.ViewHolder {
+ @Bind(app.outlay.R.id.icon)
+ ImageView icon;
+
+ @Bind(app.outlay.R.id.amount)
+ TextView amount;
+
+ @Bind(app.outlay.R.id.date)
+ TextView date;
+
+ @Bind(app.outlay.R.id.title)
+ TextView title;
+
+ @Bind(app.outlay.R.id.container)
+ View container;
+
+ public ExpenseListItemViewHolder(View v) {
+ super(v);
+ ButterKnife.bind(this, v);
+ }
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/adapter/ReportAdapter.java b/outlay/app/src/main/java/app/outlay/view/adapter/ReportAdapter.java
new file mode 100644
index 0000000..6e66121
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/adapter/ReportAdapter.java
@@ -0,0 +1,245 @@
+package app.outlay.view.adapter;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.github.johnkil.print.PrintView;
+import com.github.mikephil.charting.charts.PieChart;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.PieData;
+import com.github.mikephil.charting.data.PieDataSet;
+import com.github.mikephil.charting.data.PieEntry;
+import com.github.mikephil.charting.highlight.Highlight;
+import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
+import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic;
+
+import app.outlay.core.utils.NumberUtils;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Report;
+import app.outlay.utils.IconUtils;
+import app.outlay.view.model.CategorizedExpenses;
+import app.outlay.view.progress.ProgressLayout;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * Created by Bogdan Melnychuk on 1/27/16.
+ */
+public class ReportAdapter extends RecyclerView.Adapter {
+ private static final int TYPE_CHART = 0;
+ private static final int TYPE_REPORT_ITEM = 1;
+
+ private CategorizedExpenses categorizedExpenses;
+ private double maxProgress;
+ private ItemClickListener onItemClickListener;
+
+ public ReportAdapter(CategorizedExpenses categorizedExpenses) {
+ this.categorizedExpenses = categorizedExpenses;
+ maxProgress = getMaxProgress();
+ }
+
+ public void setOnItemClickListener(ItemClickListener onItemClickListener) {
+ this.onItemClickListener = onItemClickListener;
+ }
+
+ public ReportAdapter() {
+ this(new CategorizedExpenses());
+ }
+
+ public void setItems(CategorizedExpenses categorizedExpenses) {
+ this.categorizedExpenses = categorizedExpenses;
+ maxProgress = getMaxProgress();
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position == 0 ? TYPE_CHART : TYPE_REPORT_ITEM;
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ RecyclerView.ViewHolder result;
+ if (viewType == TYPE_CHART) {
+ final View v = inflater.inflate(app.outlay.R.layout.layout_chart, parent, false);
+ result = new ChartViewHolder(v);
+
+ } else {
+ final View v = inflater.inflate(app.outlay.R.layout.item_report, parent, false);
+ result = new ReportViewHolder(v);
+ }
+ return result;
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ if (holder instanceof ReportViewHolder) {
+ ReportViewHolder reportHolder = (ReportViewHolder) holder;
+
+ Report currentReport = categorizedExpenses.getReport(position - 1);
+ Category currentCategory = categorizedExpenses.getCategory(position - 1);
+
+
+ reportHolder.amountText.setText(NumberUtils.formatAmount(currentReport.getTotalAmount()));
+ reportHolder.titleText.setText(currentCategory.getTitle());
+ IconUtils.loadCategoryIcon(currentCategory.getIcon(), reportHolder.icon);
+ reportHolder.progressLayout.setMaxProgress((int) (maxProgress * 10));
+ reportHolder.progressLayout.setCurrentProgress(currentReport.getTotalAmount().multiply(new BigDecimal(10)).intValue());
+ reportHolder.icon.setIconColor(currentCategory.getColor());
+ reportHolder.reportContainer.setOnClickListener(v -> {
+ if (onItemClickListener != null) {
+ onItemClickListener.onItemClicked(currentCategory, currentReport);
+ }
+ });
+ //reportHolder.progressLayout.setLoadedColor(currentReport.getColor());
+ } else if (holder instanceof ChartViewHolder) {
+ ChartViewHolder charViewHolder = (ChartViewHolder) holder;
+ Context context = charViewHolder.chart.getContext();
+
+ updateChartData(charViewHolder.chart);
+
+ charViewHolder.prev.setImageDrawable(IconUtils.getToolbarIcon(context, MaterialDesignIconic.Icon.gmi_arrow_left));
+ charViewHolder.next.setImageDrawable(IconUtils.getToolbarIcon(context, MaterialDesignIconic.Icon.gmi_arrow_right));
+
+ if (charViewHolder.chart.isDrawEntryLabelsEnabled()) {
+ charViewHolder.hideLabels.setImageDrawable(IconUtils.getToolbarIcon(context, MaterialDesignIconic.Icon.gmi_format_size));
+ } else {
+ charViewHolder.hideLabels.setImageDrawable(IconUtils.getToolbarIcon(context, MaterialDesignIconic.Icon.gmi_format_clear));
+ }
+
+ charViewHolder.hideLabels.setOnClickListener(view -> {
+ charViewHolder.chart.setDrawEntryLabels(!charViewHolder.chart.isDrawEntryLabelsEnabled());
+ if (charViewHolder.chart.isDrawEntryLabelsEnabled()) {
+ charViewHolder.hideLabels.setImageDrawable(IconUtils.getToolbarIcon(context, MaterialDesignIconic.Icon.gmi_format_size));
+ } else {
+ charViewHolder.hideLabels.setImageDrawable(IconUtils.getToolbarIcon(context, MaterialDesignIconic.Icon.gmi_format_clear));
+ }
+
+ charViewHolder.chart.invalidate();
+ });
+
+ //charViewHolder.chart.animateY(1000, Easing.EasingOption.EaseInOutQuad);
+ charViewHolder.chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
+ @Override
+ public void onValueSelected(Entry e, Highlight h) {
+ if (onItemClickListener != null) {
+ //onItemClickListener.onItemClicked(reports.get(h.getXIndex()));
+ }
+ }
+
+ @Override
+ public void onNothingSelected() {
+ }
+ });
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return categorizedExpenses == null ? 1 : categorizedExpenses.getCategoriesSize() + 1;
+ }
+
+ public class ReportViewHolder extends RecyclerView.ViewHolder {
+ @Bind(app.outlay.R.id.amount)
+ TextView amountText;
+ @Bind(app.outlay.R.id.title)
+ TextView titleText;
+ @Bind(app.outlay.R.id.progressLayout)
+ ProgressLayout progressLayout;
+ @Bind(app.outlay.R.id.icon)
+ PrintView icon;
+ @Bind(app.outlay.R.id.reportContainer)
+ View reportContainer;
+
+ public ReportViewHolder(View v) {
+ super(v);
+ ButterKnife.bind(this, v);
+ }
+ }
+
+ public class ChartViewHolder extends RecyclerView.ViewHolder {
+ @Bind(app.outlay.R.id.chart)
+ PieChart chart;
+
+ @Bind(app.outlay.R.id.hideLabels)
+ ImageView hideLabels;
+
+ @Bind(app.outlay.R.id.previous)
+ ImageView prev;
+
+ @Bind(app.outlay.R.id.next)
+ ImageView next;
+
+ public ChartViewHolder(View v) {
+ super(v);
+ ButterKnife.bind(this, v);
+ chart.getLegend().setEnabled(false);
+ chart.setUsePercentValues(false);
+ chart.getDescription().setEnabled(false);
+ chart.setDragDecelerationFrictionCoef(0.95f);
+ chart.setDrawHoleEnabled(true);
+ chart.setHoleColor(Color.TRANSPARENT);
+ chart.setTransparentCircleColor(Color.WHITE);
+ chart.setTransparentCircleAlpha(110);
+ chart.setHoleRadius(35f);
+ chart.setTransparentCircleRadius(38f);
+ chart.setRotationEnabled(false);
+ chart.setCenterTextColor(Color.WHITE);
+ chart.setCenterTextSize(16);
+ }
+ }
+
+ private void updateChartData(PieChart chart) {
+ ArrayList entries = new ArrayList<>();
+ ArrayList colors = new ArrayList<>();
+
+ double sum = 0;
+ for (int i = 0; i < categorizedExpenses.getCategories().size(); i++) {
+ Category c = categorizedExpenses.getCategory(i);
+ Report r = categorizedExpenses.getReport(c);
+ sum += r.getTotalAmount().doubleValue();
+ entries.add(new PieEntry((int) (r.getTotalAmount().doubleValue() * 1000), c.getTitle()));
+ colors.add(c.getColor());
+ }
+
+ PieDataSet dataSet = new PieDataSet(entries, "Outlay");
+ dataSet.setSliceSpace(2f);
+ dataSet.setSelectionShift(10f);
+ dataSet.setColors(colors);
+
+ PieData data = new PieData(dataSet);
+ data.setValueFormatter((value, entry, dataSetIndex, viewPortHandler) -> NumberUtils.formatAmount((double) value / 1000));
+ data.setValueTextSize(11f);
+ data.setValueTextColor(Color.WHITE);
+ chart.setData(data);
+ chart.setCenterText(NumberUtils.formatAmount(sum));
+ chart.highlightValues(null);
+ chart.invalidate();
+ }
+
+ private double getMaxProgress() {
+ double max = -1;
+ for (Category c : categorizedExpenses.getCategories()) {
+ Report r = categorizedExpenses.getReport(c);
+ if (max < r.getTotalAmount().doubleValue()) {
+ max = r.getTotalAmount().doubleValue();
+ }
+ }
+ return max;
+ }
+
+ public interface ItemClickListener {
+ void onItemClicked(Category category, Report report);
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/adapter/listener/OnCategoryClickListener.java b/outlay/app/src/main/java/app/outlay/view/adapter/listener/OnCategoryClickListener.java
similarity index 62%
rename from outlay/app/src/main/java/com/outlay/adapter/listener/OnCategoryClickListener.java
rename to outlay/app/src/main/java/app/outlay/view/adapter/listener/OnCategoryClickListener.java
index 83324d7..95e8028 100644
--- a/outlay/app/src/main/java/com/outlay/adapter/listener/OnCategoryClickListener.java
+++ b/outlay/app/src/main/java/app/outlay/view/adapter/listener/OnCategoryClickListener.java
@@ -1,7 +1,7 @@
-package com.outlay.adapter.listener;
+package app.outlay.view.adapter.listener;
-import com.outlay.dao.Category;
+import app.outlay.domain.model.Category;
/**
* Created by Bogdan Melnychuk on 1/27/16.
diff --git a/outlay/app/src/main/java/com/outlay/view/alert/Alert.java b/outlay/app/src/main/java/app/outlay/view/alert/Alert.java
similarity index 84%
rename from outlay/app/src/main/java/com/outlay/view/alert/Alert.java
rename to outlay/app/src/main/java/app/outlay/view/alert/Alert.java
index 27074cc..27794b3 100644
--- a/outlay/app/src/main/java/com/outlay/view/alert/Alert.java
+++ b/outlay/app/src/main/java/app/outlay/view/alert/Alert.java
@@ -1,12 +1,10 @@
-package com.outlay.view.alert;
+package app.outlay.view.alert;
import android.content.Context;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.view.View;
-import com.outlay.R;
-
/**
* Created by Bogdan Melnychuk on 2/6/16.
*/
@@ -19,8 +17,8 @@ public static void info(View view, String message, View.OnClickListener clickLis
Context context = view.getContext();
Snackbar bar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
if (clickListener != null) {
- bar.setAction(context.getString(R.string.label_undo), clickListener);
- bar.setActionTextColor(ContextCompat.getColor(context, R.color.red));
+ bar.setAction(context.getString(app.outlay.R.string.label_undo), clickListener);
+ bar.setActionTextColor(ContextCompat.getColor(context, app.outlay.R.color.red));
}
bar.show();
}
diff --git a/outlay/app/src/main/java/com/outlay/view/autocomplete/CategoryAutoCompleteAdapter.java b/outlay/app/src/main/java/app/outlay/view/autocomplete/CategoryAutoCompleteAdapter.java
similarity index 89%
rename from outlay/app/src/main/java/com/outlay/view/autocomplete/CategoryAutoCompleteAdapter.java
rename to outlay/app/src/main/java/app/outlay/view/autocomplete/CategoryAutoCompleteAdapter.java
index f6ddec6..6633145 100644
--- a/outlay/app/src/main/java/com/outlay/view/autocomplete/CategoryAutoCompleteAdapter.java
+++ b/outlay/app/src/main/java/app/outlay/view/autocomplete/CategoryAutoCompleteAdapter.java
@@ -1,4 +1,4 @@
-package com.outlay.view.autocomplete;
+package app.outlay.view.autocomplete;
import android.content.Context;
import android.view.LayoutInflater;
@@ -10,9 +10,9 @@
import android.widget.TextView;
import com.github.johnkil.print.PrintView;
-import com.outlay.R;
-import com.outlay.dao.Category;
-import com.outlay.utils.IconUtils;
+
+import app.outlay.domain.model.Category;
+import app.outlay.utils.IconUtils;
import java.util.ArrayList;
import java.util.List;
@@ -60,10 +60,10 @@ public View getView(int position, View convertView, ViewGroup parent) {
if (inflater == null) {
inflater = LayoutInflater.from(context);
}
- view = inflater.inflate(R.layout.item_autocomplete_category, null);
+ view = inflater.inflate(app.outlay.R.layout.item_autocomplete_category, null);
vh = new ViewHolder();
- vh.categoryIcon = (PrintView) view.findViewById(R.id.categoryIcon);
- vh.categoryTitle = (TextView) view.findViewById(R.id.categoryTitle);
+ vh.categoryIcon = (PrintView) view.findViewById(app.outlay.R.id.categoryIcon);
+ vh.categoryTitle = (TextView) view.findViewById(app.outlay.R.id.categoryTitle);
view.setTag(vh);
} else {
diff --git a/outlay/app/src/main/java/com/outlay/view/dialog/DatePickerFragment.java b/outlay/app/src/main/java/app/outlay/view/dialog/DatePickerFragment.java
similarity index 87%
rename from outlay/app/src/main/java/com/outlay/view/dialog/DatePickerFragment.java
rename to outlay/app/src/main/java/app/outlay/view/dialog/DatePickerFragment.java
index b73e787..84309cc 100644
--- a/outlay/app/src/main/java/com/outlay/view/dialog/DatePickerFragment.java
+++ b/outlay/app/src/main/java/app/outlay/view/dialog/DatePickerFragment.java
@@ -1,14 +1,12 @@
-package com.outlay.view.dialog;
+package app.outlay.view.dialog;
import android.app.DatePickerDialog;
import android.app.Dialog;
-import android.app.DialogFragment;
-import android.os.Build;
import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
import android.widget.DatePicker;
-import com.outlay.R;
-import com.outlay.utils.DeviceUtils;
+import app.outlay.utils.DeviceUtils;
import java.util.Calendar;
@@ -28,7 +26,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
- DatePickerDialog dialog = new DatePickerDialog(getActivity(), R.style.DatePicker, this, year, month, day);
+ DatePickerDialog dialog = new DatePickerDialog(getActivity(), app.outlay.R.style.DatePicker, this, year, month, day);
if (DeviceUtils.supportV5()) {
dialog.getDatePicker().setFirstDayOfWeek(Calendar.MONDAY);
} else {
diff --git a/outlay/app/src/main/java/com/outlay/view/fragment/AboutFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/AboutFragment.java
similarity index 65%
rename from outlay/app/src/main/java/com/outlay/view/fragment/AboutFragment.java
rename to outlay/app/src/main/java/app/outlay/view/fragment/AboutFragment.java
index 91be2c4..ce2f50f 100644
--- a/outlay/app/src/main/java/com/outlay/view/fragment/AboutFragment.java
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/AboutFragment.java
@@ -1,4 +1,4 @@
-package com.outlay.view.fragment;
+package app.outlay.view.fragment;
import android.content.Intent;
import android.net.Uri;
@@ -12,7 +12,7 @@
import android.view.ViewGroup;
import android.widget.TextView;
-import com.outlay.R;
+import app.outlay.view.fragment.base.StaticContentFragment;
import butterknife.Bind;
import butterknife.ButterKnife;
@@ -20,25 +20,25 @@
/**
* Created by Bogdan Melnychuk on 1/20/16.
*/
-public class AboutFragment extends BaseFragment {
+public class AboutFragment extends StaticContentFragment {
- @Bind(R.id.toolbar)
+ @Bind(app.outlay.R.id.toolbar)
Toolbar toolbar;
- @Bind(R.id.githubContainer)
+ @Bind(app.outlay.R.id.githubContainer)
View githubContainer;
- @Bind(R.id.aboutText)
+ @Bind(app.outlay.R.id.aboutText)
TextView aboutText;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_about, null, false);
+ View view = inflater.inflate(app.outlay.R.layout.fragment_about, null, false);
ButterKnife.bind(this, view);
- enableToolbar(toolbar);
+ setToolbar(toolbar);
setDisplayHomeAsUpEnabled(true);
- getActivity().setTitle(getString(R.string.caption_about));
+ getActivity().setTitle(getString(app.outlay.R.string.caption_about));
return view;
}
@@ -46,12 +46,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
githubContainer.setOnClickListener(v -> {
- String url = getString(R.string.text_github_url);
+ String url = getString(app.outlay.R.string.text_github_url);
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
});
- aboutText.setText(Html.fromHtml(getString(R.string.text_about)));
+ aboutText.setText(Html.fromHtml(getString(app.outlay.R.string.text_about)));
aboutText.setMovementMethod(LinkMovementMethod.getInstance());
}
}
diff --git a/outlay/app/src/main/java/app/outlay/view/fragment/AnalysisFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/AnalysisFragment.java
new file mode 100644
index 0000000..e0faeef
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/AnalysisFragment.java
@@ -0,0 +1,278 @@
+package app.outlay.view.fragment;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import com.github.mikephil.charting.charts.BarChart;
+import com.github.mikephil.charting.components.AxisBase;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+import com.github.mikephil.charting.formatter.IAxisValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
+import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic;
+
+import app.outlay.core.utils.DateUtils;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+import app.outlay.domain.model.Report;
+import app.outlay.mvp.presenter.AnalysisPresenter;
+import app.outlay.mvp.view.AnalysisView;
+import app.outlay.utils.DeviceUtils;
+import app.outlay.utils.IconUtils;
+import app.outlay.utils.ResourceUtils;
+import app.outlay.view.autocomplete.CategoryAutoCompleteAdapter;
+import app.outlay.view.dialog.DatePickerFragment;
+import app.outlay.view.fragment.base.BaseMvpFragment;
+import com.rengwuxian.materialedittext.MaterialAutoCompleteTextView;
+
+import org.joda.time.DateTime;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import butterknife.Bind;
+
+/**
+ * Created by bmelnychuk on 2/10/17.
+ */
+
+public class AnalysisFragment extends BaseMvpFragment implements AnalysisView {
+ private static final int REF_TIMESTAMP = 1451660400;
+
+ @Bind(app.outlay.R.id.categoryTitle)
+ MaterialAutoCompleteTextView categoryTitle;
+
+ @Bind(app.outlay.R.id.barChart)
+ BarChart barChart;
+
+ @Bind(app.outlay.R.id.toolbar)
+ Toolbar toolbar;
+
+ @Bind(app.outlay.R.id.categoryIcon)
+ ImageView categoryIcon;
+
+ @Bind(app.outlay.R.id.startDate)
+ EditText startDateEdit;
+
+ @Bind(app.outlay.R.id.endDate)
+ EditText endDateEdit;
+
+ @Inject
+ AnalysisPresenter presenter;
+
+ private CategoryAutoCompleteAdapter autoCompleteAdapter;
+ private DayAxisValueFormatter dayAxisValueFormatter;
+
+ private Date startDate;
+ private Date endDate;
+ private Category selectedCategory;
+
+ @Override
+ public AnalysisPresenter createPresenter() {
+ return presenter;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getApp().getUserComponent().inject(this);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(app.outlay.R.layout.fragment_analysis, null, false);
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ setToolbar(toolbar);
+ setTitle(getResources().getString(app.outlay.R.string.menu_item_analysis));
+ setDisplayHomeAsUpEnabled(true);
+
+ Drawable noCategoryIcon = IconUtils.getIconMaterialIcon(
+ getContext(),
+ MaterialDesignIconic.Icon.gmi_label,
+ ContextCompat.getColor(getActivity(), app.outlay.R.color.icon_inactive),
+ app.outlay.R.dimen.report_category_icon,
+ 4
+ );
+ categoryIcon.setImageDrawable(noCategoryIcon);
+ startDateEdit.setOnClickListener(v -> showDatePickerDialog(0));
+ endDateEdit.setOnClickListener(v -> showDatePickerDialog(1));
+
+ autoCompleteAdapter = new CategoryAutoCompleteAdapter();
+ categoryTitle.setAdapter(autoCompleteAdapter);
+ categoryTitle.setOnItemClickListener((parent, view1, position, id) -> {
+ DeviceUtils.hideKeyboard(getActivity());
+ Category category = autoCompleteAdapter.getItem(position);
+ selectedCategory = category;
+ loadCategoryIcon(category);
+ onDataChanged();
+ });
+
+ initChart();
+
+ getPresenter().getCategories();
+ }
+
+ private void loadCategoryIcon(Category category) {
+ int iconCodeRes = ResourceUtils.getIntegerResource(getContext(), category.getIcon());
+ Drawable categoryIconDrawable = IconUtils.getCategoryIcon(getContext(),
+ iconCodeRes,
+ category.getColor(),
+ app.outlay.R.dimen.report_category_icon
+ );
+ categoryIcon.setImageDrawable(categoryIconDrawable);
+ }
+
+ private void onDataChanged() {
+ if (selectedCategory == null) {
+ return;
+ }
+ if (startDate == null || endDate == null) {
+ return;
+ }
+ DateTime startDateTime = new DateTime(startDate);
+ DateTime endDateTime = new DateTime(endDate);
+
+ if (startDateTime.isAfter(endDateTime)) {
+ return;
+ }
+
+ analytics().trackAnalysisPerformed(startDate, endDate);
+ getPresenter().getExpenses(startDate, endDate, selectedCategory);
+ }
+
+ private void initChart() {
+ barChart.setDrawBarShadow(false);
+ barChart.setDrawValueAboveBar(true);
+ barChart.getDescription().setEnabled(false);
+ barChart.setPinchZoom(false);
+ barChart.setMaxVisibleValueCount(60);
+ barChart.getLegend().setEnabled(false);
+ barChart.setDrawGridBackground(false);
+ barChart.getAxisRight().setEnabled(false);
+ barChart.setScaleYEnabled(false);
+
+ dayAxisValueFormatter = new DayAxisValueFormatter();
+ XAxis xAxis = barChart.getXAxis();
+ xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
+ xAxis.setLabelRotationAngle(270);
+ xAxis.setDrawGridLines(false);
+ xAxis.setGranularity(1f);
+ xAxis.setTextColor(ContextCompat.getColor(getActivity(), app.outlay.R.color.icon_active));
+ xAxis.setValueFormatter(dayAxisValueFormatter);
+
+
+ YAxis leftAxis = barChart.getAxisLeft();
+ leftAxis.setValueFormatter(new AmountValueFormatter());
+ leftAxis.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART);
+ leftAxis.setTextColor(ContextCompat.getColor(getActivity(), app.outlay.R.color.icon_active));
+ leftAxis.setSpaceTop(15f);
+ leftAxis.setAxisMinimum(0f);
+ }
+
+ private void showDatePickerDialog(int dateType) {
+ DatePickerFragment datePickerFragment = new DatePickerFragment();
+ datePickerFragment.setOnDateSetListener((parent, year, monthOfYear, dayOfMonth) -> {
+ Calendar c = Calendar.getInstance();
+ c.set(year, monthOfYear, dayOfMonth);
+ Date selected = c.getTime();
+ if (dateType == 0) {
+ startDate = selected;
+ startDateEdit.setText(DateUtils.toLongString(selected));
+ } else {
+ endDate = selected;
+ endDateEdit.setText(DateUtils.toLongString(selected));
+ }
+ onDataChanged();
+ });
+ datePickerFragment.show(getChildFragmentManager(), "datePicker");
+ }
+
+ @Override
+ public void showAnalysis(Report report) {
+ List barEntries = new ArrayList<>();
+ List expenses = report.getExpenses();
+
+ for (int i = 0; i < expenses.size(); i++) {
+ Expense expense = expenses.get(i);
+ barEntries.add(new BarEntry(
+ i,
+ expense.getAmount().floatValue())
+ );
+ }
+ BarDataSet barSet = new BarDataSet(barEntries, "");
+ barSet.setColor(selectedCategory.getColor());
+
+ List dataSets = new ArrayList<>();
+ dataSets.add(barSet);
+
+ BarData data = new BarData(dataSets);
+ data.setValueTextSize(10f);
+ data.setValueTextColor(ContextCompat.getColor(getActivity(), app.outlay.R.color.icon_active));
+ data.setBarWidth(0.9f);
+
+ dayAxisValueFormatter.setExpenses(expenses);
+ barChart.setData(data);
+ barChart.invalidate();
+ barChart.animateY(500);
+ }
+
+ @Override
+ public void setCategories(List categories) {
+ autoCompleteAdapter.setItems(categories);
+ }
+
+ public static class DayAxisValueFormatter implements IAxisValueFormatter {
+ private List expenses;
+
+ public DayAxisValueFormatter setExpenses(List expenses) {
+ this.expenses = expenses;
+ return this;
+ }
+
+ @Override
+ public String getFormattedValue(float value, AxisBase axis) {
+ if (expenses != null && value < expenses.size()) {
+ Expense expense = expenses.get((int) value);
+ return DateUtils.toShortString(expense.getReportedWhen());
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public static class AmountValueFormatter implements IAxisValueFormatter {
+ private DecimalFormat mFormat;
+
+ public AmountValueFormatter() {
+ mFormat = new DecimalFormat("###,###,###,##0.0");
+ }
+
+ @Override
+ public String getFormattedValue(float value, AxisBase axis) {
+ return mFormat.format(value);
+ }
+
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/view/fragment/CategoriesFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/CategoriesFragment.java
similarity index 50%
rename from outlay/app/src/main/java/com/outlay/view/fragment/CategoriesFragment.java
rename to outlay/app/src/main/java/app/outlay/view/fragment/CategoriesFragment.java
index 93c2714..f7fbd9f 100644
--- a/outlay/app/src/main/java/com/outlay/view/fragment/CategoriesFragment.java
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/CategoriesFragment.java
@@ -1,8 +1,10 @@
-package com.outlay.view.fragment;
+package app.outlay.view.fragment;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
+import android.support.v4.content.ContextCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
@@ -10,16 +12,20 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
-import com.outlay.App;
-import com.outlay.R;
-import com.outlay.adapter.CategoriesDraggableGridAdapter;
-import com.outlay.dao.Category;
-import com.outlay.presenter.CategoriesPresenter;
-import com.outlay.utils.ResourceUtils;
-import com.outlay.view.Page;
-import com.outlay.view.helper.itemtouch.OnDragListener;
-import com.outlay.view.helper.itemtouch.SimpleItemTouchHelperCallback;
+import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic;
+
+import app.outlay.utils.IconUtils;
+import app.outlay.view.adapter.CategoriesDraggableGridAdapter;
+import app.outlay.domain.model.Category;
+import app.outlay.mvp.presenter.CategoriesPresenter;
+import app.outlay.mvp.view.CategoriesView;
+import app.outlay.utils.ResourceUtils;
+import app.outlay.view.Navigator;
+import app.outlay.view.fragment.base.BaseMvpFragment;
+import app.outlay.view.helper.itemtouch.OnDragListener;
+import app.outlay.view.helper.itemtouch.SimpleItemTouchHelperCallback;
import java.util.List;
@@ -31,15 +37,21 @@
/**
* Created by Bogdan Melnychuk on 1/20/16.
*/
-public class CategoriesFragment extends BaseFragment implements OnDragListener {
+public class CategoriesFragment extends BaseMvpFragment implements OnDragListener, CategoriesView {
- @Bind(R.id.toolbar)
+ @Bind(app.outlay.R.id.toolbar)
Toolbar toolbar;
- @Bind(R.id.categoriesGrid)
+ @Bind(app.outlay.R.id.categoriesGrid)
RecyclerView categoriesGrid;
- @Bind(R.id.fab)
+ @Bind(app.outlay.R.id.noContent)
+ View noContent;
+
+ @Bind(app.outlay.R.id.noContentImage)
+ ImageView noContentImage;
+
+ @Bind(app.outlay.R.id.fab)
FloatingActionButton fab;
@Inject
@@ -48,52 +60,67 @@ public class CategoriesFragment extends BaseFragment implements OnDragListener {
private ItemTouchHelper mItemTouchHelper;
private CategoriesDraggableGridAdapter adapter;
+ @Override
+ public CategoriesPresenter createPresenter() {
+ return presenter;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- App.getComponent(getActivity()).inject(this);
- presenter.attachView(this);
+ getApp().getUserComponent().inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_categories, null, false);
- ButterKnife.bind(this, view);
- enableToolbar(toolbar);
- setDisplayHomeAsUpEnabled(true);
- getActivity().setTitle(getString(R.string.caption_categories));
+ View view = inflater.inflate(app.outlay.R.layout.fragment_categories, null, false);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+ ButterKnife.bind(this, view);
+
+ setToolbar(toolbar);
+ setDisplayHomeAsUpEnabled(true);
+ getActivity().setTitle(getString(app.outlay.R.string.caption_categories));
GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 4);
categoriesGrid.setLayoutManager(gridLayoutManager);
adapter = new CategoriesDraggableGridAdapter();
adapter.setDragListener(this);
- adapter.setOnCategoryClickListener(c -> Page.goToCategoryDetails(getActivity(), c.getId()));
+ adapter.setOnCategoryClickListener(c -> Navigator.goToCategoryDetails(getActivity(), c.getId()));
categoriesGrid.setAdapter(adapter);
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(adapter);
mItemTouchHelper = new ItemTouchHelper(callback);
mItemTouchHelper.attachToRecyclerView(categoriesGrid);
- fab.setImageDrawable(ResourceUtils.getMaterialToolbarIcon(getActivity(), R.string.ic_material_add));
- fab.setOnClickListener(v -> Page.goToCategoryDetails(getActivity(), null));
+ fab.setImageDrawable(ResourceUtils.getMaterialToolbarIcon(getActivity(), app.outlay.R.string.ic_material_add));
+ fab.setOnClickListener(v -> Navigator.goToCategoryDetails(getActivity(), null));
+
+ Drawable noCategoryIcon = IconUtils.getIconMaterialIcon(
+ getContext(),
+ MaterialDesignIconic.Icon.gmi_label,
+ ContextCompat.getColor(getActivity(), app.outlay.R.color.icon_inactive),
+ app.outlay.R.dimen.icon_no_results,
+ 16
+ );
+ noContentImage.setImageDrawable(noCategoryIcon);
}
@Override
public void onResume() {
super.onResume();
- presenter.loadCategories();
+ presenter.getCategories();
}
@Override
public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
+ analytics().trackCategoryDragEvent();
mItemTouchHelper.startDrag(viewHolder);
}
@@ -103,10 +130,12 @@ public void onEndDrag(RecyclerView.ViewHolder viewHolder) {
for (int i = 0; i < items.size(); i++) {
items.get(i).setOrder(i);
}
- presenter.updateCategories(items);
+ presenter.updateOrder(items);
}
- public void displayCategories(List categoryList) {
+ @Override
+ public void showCategories(List categoryList) {
adapter.setItems(categoryList);
+ noContent.setVisibility(categoryList.isEmpty() ? View.VISIBLE : View.GONE);
}
}
diff --git a/outlay/app/src/main/java/com/outlay/view/fragment/CategoryDetailsFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/CategoryDetailsFragment.java
similarity index 60%
rename from outlay/app/src/main/java/com/outlay/view/fragment/CategoryDetailsFragment.java
rename to outlay/app/src/main/java/app/outlay/view/fragment/CategoryDetailsFragment.java
index 714fba8..66ca4d0 100644
--- a/outlay/app/src/main/java/com/outlay/view/fragment/CategoryDetailsFragment.java
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/CategoryDetailsFragment.java
@@ -1,4 +1,4 @@
-package com.outlay.view.fragment;
+package app.outlay.view.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -15,43 +15,42 @@
import android.view.ViewGroup;
import android.widget.EditText;
-import com.outlay.App;
-import com.outlay.R;
-import com.outlay.adapter.IconsGridAdapter;
-import com.outlay.dao.Category;
-import com.outlay.helper.TextWatcherAdapter;
-import com.outlay.model.Icon;
-import com.outlay.presenter.CategoryDetailsPresenter;
-import com.outlay.utils.ResourceUtils;
-import com.outlay.view.alert.Alert;
+import app.outlay.domain.model.Category;
+import app.outlay.mvp.presenter.CategoryDetailsPresenter;
+import app.outlay.mvp.view.CategoryDetailsView;
+import app.outlay.utils.IconUtils;
+import app.outlay.utils.ResourceUtils;
+import app.outlay.view.adapter.IconsGridAdapter;
+import app.outlay.view.alert.Alert;
+import app.outlay.view.fragment.base.BaseMvpFragment;
+import app.outlay.view.helper.TextWatcherAdapter;
import java.util.Random;
import javax.inject.Inject;
import butterknife.Bind;
-import butterknife.ButterKnife;
import uz.shift.colorpicker.LineColorPicker;
/**
* Created by Bogdan Melnychuk on 1/20/16.
*/
-public class CategoryDetailsFragment extends BaseFragment {
+public class CategoryDetailsFragment extends BaseMvpFragment implements CategoryDetailsView {
public static final String ARG_CATEGORY_PARAM = "_argCategoryId";
- @Bind(R.id.toolbar)
+ @Bind(app.outlay.R.id.toolbar)
Toolbar toolbar;
- @Bind(R.id.iconsGrid)
+ @Bind(app.outlay.R.id.iconsGrid)
RecyclerView iconsGrid;
- @Bind(R.id.colorPicker)
+ @Bind(app.outlay.R.id.colorPicker)
LineColorPicker colorPicker;
- @Bind(R.id.categoryName)
+ @Bind(app.outlay.R.id.categoryName)
EditText categoryName;
- @Bind(R.id.categoryInputLayout)
+ @Bind(app.outlay.R.id.categoryInputLayout)
TextInputLayout categoryInputLayout;
@Inject
@@ -60,31 +59,31 @@ public class CategoryDetailsFragment extends BaseFragment {
private IconsGridAdapter adapter;
private Category category;
+ @Override
+ public CategoryDetailsPresenter createPresenter() {
+ return presenter;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- App.getComponent(getActivity()).inject(this);
- presenter.attachView(this);
+ getApp().getUserComponent().inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_category_details, null, false);
- ButterKnife.bind(this, view);
- enableToolbar(toolbar);
- setDisplayHomeAsUpEnabled(true);
-
+ View view = inflater.inflate(app.outlay.R.layout.fragment_category_details, null, false);
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- inflater.inflate(R.menu.menu_category_details, menu);
- MenuItem saveItem = menu.findItem(R.id.action_save);
- saveItem.setIcon(ResourceUtils.getMaterialToolbarIcon(getActivity(), R.string.ic_material_done));
+ inflater.inflate(app.outlay.R.menu.menu_category_details, menu);
+ MenuItem saveItem = menu.findItem(app.outlay.R.id.action_save);
+ saveItem.setIcon(ResourceUtils.getMaterialToolbarIcon(getActivity(), app.outlay.R.string.ic_material_done));
if (category != null && category.getId() == null) {
- MenuItem deleteItem = menu.findItem(R.id.action_delete);
+ MenuItem deleteItem = menu.findItem(app.outlay.R.id.action_delete);
deleteItem.setVisible(false);
}
super.onCreateOptionsMenu(menu, inflater);
@@ -93,17 +92,21 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.action_save:
+ case app.outlay.R.id.action_save:
Category category = getCategory();
if (validateCategory(category)) {
+ if (TextUtils.isEmpty(category.getId())) {
+ analytics().trackCategoryCreated(category);
+ } else {
+ analytics().trackCategoryUpdated(category);
+ }
presenter.updateCategory(getCategory());
- getActivity().onBackPressed();
}
break;
- case R.id.action_delete:
- presenter.deleteCategory(getCategory());
- this.category.setId(null);
- getActivity().onBackPressed();
+ case app.outlay.R.id.action_delete:
+ category = getCategory();
+ analytics().trackCategoryDeleted(category);
+ presenter.deleteCategory(category);
break;
}
return super.onOptionsItemSelected(item);
@@ -112,19 +115,22 @@ public boolean onOptionsItemSelected(MenuItem item) {
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+ setToolbar(toolbar);
+ setDisplayHomeAsUpEnabled(true);
+
GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 4);
iconsGrid.setLayoutManager(gridLayoutManager);
- adapter = new IconsGridAdapter(Icon.getAll());
+ adapter = new IconsGridAdapter(IconUtils.getAll());
iconsGrid.setAdapter(adapter);
colorPicker.setOnColorChangedListener(i -> adapter.setActiveColor(i));
if (getArguments().containsKey(ARG_CATEGORY_PARAM)) {
- Long categoryId = getArguments().getLong(ARG_CATEGORY_PARAM);
- getActivity().setTitle(getString(R.string.caption_edit_category));
- presenter.loadCategory(categoryId);
+ String categoryId = getArguments().getString(ARG_CATEGORY_PARAM);
+ getActivity().setTitle(getString(app.outlay.R.string.caption_edit_category));
+ presenter.getCategory(categoryId);
} else {
- getActivity().setTitle(getString(R.string.caption_edit_category));
- displayCategory(new Category());
+ getActivity().setTitle(getString(app.outlay.R.string.caption_edit_category));
+ showCategory(new Category());
}
categoryName.addTextChangedListener(new TextWatcherAdapter() {
@@ -136,14 +142,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
- public void onDestroy() {
- if(category.getId() != null) {
- category.refresh();
- }
- super.onDestroy();
- }
-
- public void displayCategory(Category category) {
+ public void showCategory(Category category) {
this.category = category;
if (category.getId() == null) {
@@ -158,6 +157,11 @@ public void displayCategory(Category category) {
}
}
+ @Override
+ public void finish() {
+ getActivity().onBackPressed();
+ }
+
public Category getCategory() {
category.setTitle(categoryName.getText().toString());
category.setColor(colorPicker.getColor());
@@ -169,11 +173,11 @@ private boolean validateCategory(Category category) {
boolean result = true;
if (TextUtils.isEmpty(category.getTitle())) {
categoryInputLayout.setErrorEnabled(true);
- categoryInputLayout.setError(getString(R.string.error_category_name_empty));
+ categoryInputLayout.setError(getString(app.outlay.R.string.error_category_name_empty));
categoryName.requestFocus();
result = false;
- } else if(TextUtils.isEmpty(category.getIcon())) {
- Alert.error(getRootView(), getString(R.string.error_category_icon));
+ } else if (TextUtils.isEmpty(category.getIcon())) {
+ Alert.error(getBaseActivity().getRootView(), getString(app.outlay.R.string.error_category_icon));
result = false;
}
diff --git a/outlay/app/src/main/java/com/outlay/view/fragment/ExpensesDetailsFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/ExpensesDetailsFragment.java
similarity index 53%
rename from outlay/app/src/main/java/com/outlay/view/fragment/ExpensesDetailsFragment.java
rename to outlay/app/src/main/java/app/outlay/view/fragment/ExpensesDetailsFragment.java
index bd9beda..6b8f722 100644
--- a/outlay/app/src/main/java/com/outlay/view/fragment/ExpensesDetailsFragment.java
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/ExpensesDetailsFragment.java
@@ -1,8 +1,10 @@
-package com.outlay.view.fragment;
+package app.outlay.view.fragment;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
+import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -12,22 +14,25 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
+import android.widget.ImageView;
-import com.github.johnkil.print.PrintView;
-import com.outlay.App;
-import com.outlay.R;
-import com.outlay.dao.Category;
-import com.outlay.dao.Expense;
-import com.outlay.helper.TextWatcherAdapter;
-import com.outlay.presenter.ExpensesDetailsPresenter;
-import com.outlay.utils.DateUtils;
-import com.outlay.utils.FormatUtils;
-import com.outlay.utils.IconUtils;
-import com.outlay.utils.ResourceUtils;
-import com.outlay.view.autocomplete.CategoryAutoCompleteAdapter;
-import com.outlay.view.dialog.DatePickerFragment;
+import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic;
+
+import app.outlay.core.utils.DateUtils;
+import app.outlay.core.utils.NumberUtils;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+import app.outlay.mvp.presenter.ExpenseDetailsPresenter;
+import app.outlay.mvp.view.ExpenseDetailsView;
+import app.outlay.utils.IconUtils;
+import app.outlay.utils.ResourceUtils;
+import app.outlay.view.autocomplete.CategoryAutoCompleteAdapter;
+import app.outlay.view.dialog.DatePickerFragment;
+import app.outlay.view.fragment.base.BaseMvpFragment;
+import app.outlay.view.helper.TextWatcherAdapter;
import com.rengwuxian.materialedittext.MaterialAutoCompleteTextView;
+import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@@ -40,64 +45,79 @@
/**
* Created by Bogdan Melnychuk on 1/20/16.
*/
-public class ExpensesDetailsFragment extends BaseFragment {
+public class ExpensesDetailsFragment extends BaseMvpFragment implements ExpenseDetailsView {
public static final String ARG_EXPENSE_ID = "_argExpenseId";
public static final String ARG_DATE = "_argDate";
- @Bind(R.id.toolbar)
+ @Bind(app.outlay.R.id.toolbar)
Toolbar toolbar;
- @Bind(R.id.categoryTitle)
+ @Bind(app.outlay.R.id.categoryTitle)
MaterialAutoCompleteTextView categoryTitle;
- @Bind(R.id.categoryIcon)
- PrintView categoryIcon;
+ @Bind(app.outlay.R.id.categoryIcon)
+ ImageView categoryIcon;
- @Bind(R.id.amount)
+ @Bind(app.outlay.R.id.amount)
EditText amount;
- @Bind(R.id.note)
+ @Bind(app.outlay.R.id.note)
EditText note;
- @Bind(R.id.date)
+ @Bind(app.outlay.R.id.date)
EditText dateEdit;
- @Bind(R.id.amountInputLayout)
+ @Bind(app.outlay.R.id.amountInputLayout)
TextInputLayout amountInputLayout;
@Inject
- ExpensesDetailsPresenter presenter;
+ ExpenseDetailsPresenter presenter;
private CategoryAutoCompleteAdapter autoCompleteAdapter;
private Expense expense;
private Category selectedCategory;
private Date defaultDate;
+ @Override
+ public ExpenseDetailsPresenter createPresenter() {
+ return presenter;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- App.getComponent(getActivity()).inject(this);
- presenter.attachView(this);
+ getApp().getUserComponent().inject(this);
defaultDate = new Date(getArguments().getLong(ARG_DATE, new Date().getTime()));
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_expense_details, null, false);
+ View view = inflater.inflate(app.outlay.R.layout.fragment_expense_details, null, false);
ButterKnife.bind(this, view);
- enableToolbar(toolbar);
+ setToolbar(toolbar);
setDisplayHomeAsUpEnabled(true);
+
+ Drawable noCategoryIcon = IconUtils.getIconMaterialIcon(
+ getContext(),
+ MaterialDesignIconic.Icon.gmi_label,
+ ContextCompat.getColor(getActivity(), app.outlay.R.color.icon_inactive),
+ app.outlay.R.dimen.report_category_icon,
+ 4
+ );
+ categoryIcon.setImageDrawable(noCategoryIcon);
+
+
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- inflater.inflate(R.menu.menu_category_details, menu);
- MenuItem saveItem = menu.findItem(R.id.action_save);
- saveItem.setIcon(ResourceUtils.getMaterialToolbarIcon(getActivity(), R.string.ic_material_done));
+ inflater.inflate(app.outlay.R.menu.menu_category_details, menu);
+ MenuItem saveItem = menu.findItem(app.outlay.R.id.action_save);
+ saveItem.setIcon(ResourceUtils.getMaterialToolbarIcon(getActivity(), app.outlay.R.string.ic_material_done));
if (expense != null && expense.getId() == null) {
- MenuItem deleteItem = menu.findItem(R.id.action_delete);
+ MenuItem deleteItem = menu.findItem(app.outlay.R.id.action_delete);
deleteItem.setVisible(false);
}
super.onCreateOptionsMenu(menu, inflater);
@@ -106,32 +126,51 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.action_save:
+ case app.outlay.R.id.action_save:
if (validateInput()) {
- presenter.updateExpense(getExpense());
+ Expense expense = getExpense();
+ if (TextUtils.isEmpty(expense.getId())) {
+ analytics().trackExpenseCreated(expense);
+ } else {
+ analytics().trackExpenseUpdated(expense);
+ }
+ presenter.updateExpense(expense);
getActivity().onBackPressed();
}
break;
- case R.id.action_delete:
- presenter.deleteExpense(getExpense());
- expense.setId(null);
+ case app.outlay.R.id.action_delete:
+ Expense expense = getExpense();
+ analytics().trackExpenseDeleted(expense);
+ presenter.deleteExpense(expense);
getActivity().onBackPressed();
break;
}
return super.onOptionsItemSelected(item);
}
+ private void loadCategoryIcon(Category category) {
+ int iconCodeRes = ResourceUtils.getIntegerResource(getContext(), category.getIcon());
+ Drawable categoryIconDrawable = IconUtils.getCategoryIcon(getContext(),
+ iconCodeRes,
+ category.getColor(),
+ app.outlay.R.dimen.report_category_icon
+ );
+ categoryIcon.setImageDrawable(categoryIconDrawable);
+ }
+
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
autoCompleteAdapter = new CategoryAutoCompleteAdapter();
categoryTitle.setAdapter(autoCompleteAdapter);
categoryTitle.setOnItemClickListener((parent, view1, position, id) -> {
+
Category category = autoCompleteAdapter.getItem(position);
selectedCategory = category;
- IconUtils.loadCategoryIcon(selectedCategory, categoryIcon);
+ loadCategoryIcon(category);
+
});
- presenter.loadCategories();
+ presenter.getCategories();
dateEdit.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
showDatePickerDialog();
@@ -140,12 +179,12 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
dateEdit.setOnClickListener(v -> showDatePickerDialog());
if (getArguments().containsKey(ARG_EXPENSE_ID)) {
- Long expenseId = getArguments().getLong(ARG_EXPENSE_ID);
- getActivity().setTitle(getString(R.string.caption_edit_expense));
- presenter.loadExpense(expenseId);
+ String expenseId = getArguments().getString(ARG_EXPENSE_ID);
+ getActivity().setTitle(getString(app.outlay.R.string.caption_edit_expense));
+ presenter.findExpense(expenseId, defaultDate);
} else {
- getActivity().setTitle(getString(R.string.caption_new_expense));
- displayExpense(new Expense());
+ getActivity().setTitle(getString(app.outlay.R.string.caption_new_expense));
+ showExpense(new Expense());
}
amount.addTextChangedListener(new TextWatcherAdapter() {
@Override
@@ -162,27 +201,30 @@ private void showDatePickerDialog() {
c.set(year, monthOfYear, dayOfMonth);
Date selected = c.getTime();
setDateStr(selected);
- expense.setReportedAt(selected);
+ expense.setReportedWhen(selected);
});
datePickerFragment.show(getChildFragmentManager(), "datePicker");
}
- public void setCategories(List categories) {
+ @Override
+ public void showCategories(List categories) {
autoCompleteAdapter.setItems(categories);
}
- public void displayExpense(Expense e) {
+ @Override
+ public void showExpense(Expense e) {
this.expense = e;
if (e.getId() != null) {
selectedCategory = e.getCategory();
- IconUtils.loadCategoryIcon(e.getCategory(), categoryIcon);
+ loadCategoryIcon(selectedCategory);
+
categoryTitle.setText(e.getCategory().getTitle());
- amount.setText(FormatUtils.formatAmount(e.getAmount()));
+ amount.setText(NumberUtils.formatAmount(e.getAmount()));
note.setText(e.getNote());
- setDateStr(expense.getReportedAt());
+ setDateStr(expense.getReportedWhen());
} else {
- this.expense.setReportedAt(defaultDate);
- setDateStr(expense.getReportedAt());
+ this.expense.setReportedWhen(defaultDate);
+ setDateStr(expense.getReportedWhen());
}
}
@@ -191,7 +233,7 @@ public Expense getExpense() {
expense.setCategory(selectedCategory);
}
if (!TextUtils.isEmpty(amount.getText().toString())) {
- expense.setAmount(Double.valueOf(amount.getText().toString()));
+ expense.setAmount(new BigDecimal(amount.getText().toString()));
}
expense.setNote(note.getText().toString());
return expense;
@@ -204,28 +246,20 @@ private void setDateStr(Date date) {
private boolean validateInput() {
boolean result = true;
if (selectedCategory == null) {
- categoryTitle.setError(getString(R.string.error_category_name_empty));
+ categoryTitle.setError(getString(app.outlay.R.string.error_category_name_empty));
categoryTitle.requestFocus();
result = false;
} else if (!selectedCategory.getTitle().equals(categoryTitle.getText().toString())) {
- categoryTitle.setError(getString(R.string.error_category_name_invalid));
+ categoryTitle.setError(getString(app.outlay.R.string.error_category_name_invalid));
categoryTitle.requestFocus();
result = false;
} else if (TextUtils.isEmpty(amount.getText())) {
//TODO validate number
- amountInputLayout.setError(getString(R.string.error_amount_invalid));
+ amountInputLayout.setError(getString(app.outlay.R.string.error_amount_invalid));
amountInputLayout.requestFocus();
result = false;
}
return result;
}
-
- @Override
- public void onDestroy() {
- if (expense.getId() != null) {
- expense.refresh();
- }
- super.onDestroy();
- }
}
diff --git a/outlay/app/src/main/java/com/outlay/view/fragment/ExpensesListFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/ExpensesListFragment.java
similarity index 50%
rename from outlay/app/src/main/java/com/outlay/view/fragment/ExpensesListFragment.java
rename to outlay/app/src/main/java/app/outlay/view/fragment/ExpensesListFragment.java
index 8abc804..98d1fda 100644
--- a/outlay/app/src/main/java/com/outlay/view/fragment/ExpensesListFragment.java
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/ExpensesListFragment.java
@@ -1,10 +1,10 @@
-package com.outlay.view.fragment;
+package app.outlay.view.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
+import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.StaggeredGridLayoutManager;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
@@ -12,53 +12,58 @@
import android.widget.TextView;
import com.github.johnkil.print.PrintView;
-import com.outlay.App;
-import com.outlay.R;
-import com.outlay.adapter.ExpenseAdapter;
-import com.outlay.dao.Category;
-import com.outlay.dao.Expense;
-import com.outlay.presenter.ExpensesListPresenter;
-import com.outlay.utils.DateUtils;
-import com.outlay.utils.IconUtils;
-import com.outlay.utils.ResourceUtils;
-import com.outlay.view.Page;
+
+import app.outlay.core.utils.DateUtils;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+import app.outlay.domain.model.Report;
+import app.outlay.mvp.presenter.ExpensesListPresenter;
+import app.outlay.mvp.view.ExpensesView;
+import app.outlay.utils.IconUtils;
+import app.outlay.utils.ResourceUtils;
+import app.outlay.view.Navigator;
+import app.outlay.view.adapter.ExpenseAdapter;
+import app.outlay.view.adapter.ListExpensesAdapter;
+import app.outlay.view.fragment.base.BaseMvpFragment;
import java.util.Date;
import java.util.List;
+import java.util.Map;
import javax.inject.Inject;
import butterknife.Bind;
-import butterknife.ButterKnife;
/**
* Created by Bogdan Melnychuk on 1/20/16.
*/
-public class ExpensesListFragment extends BaseFragment {
+public class ExpensesListFragment extends BaseMvpFragment implements ExpensesView {
public static final String ARG_CATEGORY_ID = "_argCategoryId";
public static final String ARG_DATE_FROM = "_argDateFrom";
public static final String ARG_DATE_TO = "_argDateTo";
+ private static final int MODE_LIST = 0;
+ private static final int MODE_GRID = 1;
- @Bind(R.id.recyclerView)
+ @Bind(app.outlay.R.id.recyclerView)
RecyclerView recyclerView;
- @Bind(R.id.toolbar)
+ @Bind(app.outlay.R.id.toolbar)
Toolbar toolbar;
- @Bind(R.id.fab)
+ @Bind(app.outlay.R.id.fab)
FloatingActionButton fab;
- @Bind(R.id.noResults)
+ @Bind(app.outlay.R.id.noResults)
View noResults;
- @Bind(R.id.filterCategoryIcon)
+ @Bind(app.outlay.R.id.filterCategoryIcon)
PrintView filterCategoryIcon;
- @Bind(R.id.filterCategoryName)
+ @Bind(app.outlay.R.id.filterCategoryName)
TextView filterCategoryName;
- @Bind(R.id.filterDateLabel)
+ @Bind(app.outlay.R.id.filterDateLabel)
TextView filterDateLabel;
@Inject
@@ -67,13 +72,19 @@ public class ExpensesListFragment extends BaseFragment {
private ExpenseAdapter adapter;
private Date dateFrom;
private Date dateTo;
- private Long categoryId;
+ private String categoryId;
+
+ private int mode = MODE_LIST;
+
+ @Override
+ public ExpensesListPresenter createPresenter() {
+ return presenter;
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- App.getComponent(getActivity()).inject(this);
- presenter.attachView(this);
+ getApp().getUserComponent().inject(this);
long dateFromMillis = getArguments().getLong(ARG_DATE_FROM);
long dateToMillis = getArguments().getLong(ARG_DATE_TO);
@@ -81,51 +92,48 @@ public void onCreate(Bundle savedInstanceState) {
dateFrom = new Date(dateFromMillis);
dateTo = new Date(dateToMillis);
if (getArguments().containsKey(ARG_CATEGORY_ID)) {
- categoryId = getArguments().getLong(ARG_CATEGORY_ID);
+ categoryId = getArguments().getString(ARG_CATEGORY_ID);
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_expenses_list, null, false);
- ButterKnife.bind(this, view);
- enableToolbar(toolbar);
- setDisplayHomeAsUpEnabled(true);
- setTitle(getString(R.string.caption_expenses));
+ View view = inflater.inflate(app.outlay.R.layout.fragment_expenses_list, null, false);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- recyclerView.setHasFixedSize(true);
- StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
- recyclerView.setLayoutManager(staggeredGridLayoutManager);
- adapter = new ExpenseAdapter();
- adapter.setOnExpenseClickListener(expense -> Page.goToExpenseDetails(getActivity(), expense.getId()));
- recyclerView.setAdapter(adapter);
- fab.setImageDrawable(ResourceUtils.getMaterialToolbarIcon(getActivity(), R.string.ic_material_add));
- fab.setOnClickListener(v -> Page.goToExpenseDetails(getActivity(), null));
- if(categoryId != null) {
- Category category = presenter.getCategoryById(categoryId);
- IconUtils.loadCategoryIcon(category, filterCategoryIcon);
- filterCategoryName.setText(category.getTitle());
- } else {
- filterCategoryName.setVisibility(View.GONE);
- filterCategoryIcon.setVisibility(View.GONE);
- }
+ setToolbar(toolbar);
+ setDisplayHomeAsUpEnabled(true);
+ setTitle(getString(app.outlay.R.string.caption_expenses));
+
filterDateLabel.setText(getDateLabel());
+ fab.setImageDrawable(ResourceUtils.getMaterialToolbarIcon(getActivity(), app.outlay.R.string.ic_material_add));
+ fab.setOnClickListener(v -> Navigator.goToExpenseDetails(getActivity(), null));
+ recyclerView.setHasFixedSize(true);
+
+// adapter = new GridExpensesAdapter();
+// StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
+// recyclerView.setLayoutManager(staggeredGridLayoutManager);
+
+ adapter = new ListExpensesAdapter();
+ LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
+ recyclerView.setLayoutManager(linearLayoutManager);
+ recyclerView.setAdapter(adapter);
+ adapter.setOnExpenseClickListener(expense -> Navigator.goToExpenseDetails(getActivity(), expense));
}
@Override
public void onResume() {
super.onResume();
- presenter.loadExpenses(dateFrom, dateTo, categoryId);
+ presenter.findExpenses(dateFrom, dateTo, categoryId);
}
- public void displayExpenses(List expenses) {
+ private void displayExpenses(List expenses) {
if (expenses.isEmpty()) {
noResults.setVisibility(View.VISIBLE);
} else {
@@ -134,11 +142,30 @@ public void displayExpenses(List expenses) {
}
}
+ private void displayCategory(Category category) {
+ if (category != null) {
+ IconUtils.loadCategoryIcon(category, filterCategoryIcon);
+ filterCategoryName.setText(category.getTitle());
+ } else {
+ filterCategoryName.setVisibility(View.GONE);
+ filterCategoryIcon.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void showReport(Report report) {
+ displayExpenses(report.getExpenses());
+ Map grouped = report.groupByCategory();
+ if (grouped.size() == 1) {
+ displayCategory(report.getExpenses().get(0).getCategory());
+ }
+ }
+
private String getDateLabel() {
String dateFromStr = DateUtils.toShortString(dateFrom);
String dateToStr = DateUtils.toShortString(dateTo);
String result = dateFromStr;
- if(!dateFromStr.equals(dateToStr)) {
+ if (!dateFromStr.equals(dateToStr)) {
result += " - " + dateToStr;
}
return result;
diff --git a/outlay/app/src/main/java/app/outlay/view/fragment/LoginFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/LoginFragment.java
new file mode 100644
index 0000000..17eace5
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/LoginFragment.java
@@ -0,0 +1,88 @@
+package app.outlay.view.fragment;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import app.outlay.domain.model.User;
+import app.outlay.mvp.presenter.LoginViewPresenter;
+import app.outlay.mvp.view.LoginView;
+import app.outlay.view.LoginForm;
+import app.outlay.view.Navigator;
+import app.outlay.view.fragment.base.BaseMvpFragment;
+
+import javax.inject.Inject;
+
+import butterknife.Bind;
+
+/**
+ * Created by bmelnychuk on 12/14/16.
+ */
+
+public class LoginFragment extends BaseMvpFragment implements LoginView {
+ @Inject
+ LoginViewPresenter presenter;
+
+ @Bind(app.outlay.R.id.loginForm)
+ LoginForm loginForm;
+
+ @Bind(app.outlay.R.id.progressLayout)
+ View progressLayout;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getApp().getAppComponent().inject(this);
+ }
+
+ @Override
+ public LoginViewPresenter createPresenter() {
+ return presenter;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(app.outlay.R.layout.fragment_login, null, false);
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ loginForm.setOnSignUpClickListener((email, password, src) -> {
+ analytics().trackSignUp();
+ presenter.signUp(email, password);
+ });
+ loginForm.setOnSignInClickListener((email, password, src) -> {
+ analytics().trackEmailSignIn();
+ presenter.signIn(email, password);
+ });
+ loginForm.setOnPasswordForgetClick(() -> presenter.resetPassword("melnychuk.bogdan@gmail.com"));
+ loginForm.setOnSkipButtonClick(v -> {
+ analytics().trackGuestSignIn();
+ presenter.signInGuest();
+ });
+
+ presenter.trySignIn();
+ }
+
+ @Override
+ public void setProgress(boolean running) {
+ progressLayout.setVisibility(running ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void info(String message) {
+ Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onSuccess(User user) {
+ getApp().createUserComponent(user);
+ Navigator.goToMainScreen(getActivity());
+ getActivity().finish();
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/fragment/MainFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/MainFragment.java
new file mode 100644
index 0000000..e37ad07
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/MainFragment.java
@@ -0,0 +1,209 @@
+package app.outlay.view.fragment;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import app.outlay.core.utils.DateUtils;
+import app.outlay.core.utils.NumberUtils;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Expense;
+import app.outlay.domain.model.User;
+import app.outlay.mvp.presenter.EnterExpensePresenter;
+import app.outlay.mvp.view.EnterExpenseView;
+import app.outlay.utils.ResourceUtils;
+import app.outlay.view.Navigator;
+import app.outlay.view.activity.base.DrawerActivity;
+import app.outlay.view.adapter.CategoriesGridAdapter;
+import app.outlay.view.alert.Alert;
+import app.outlay.view.dialog.DatePickerFragment;
+import app.outlay.view.fragment.base.BaseMvpFragment;
+import app.outlay.view.numpad.NumpadEditable;
+import app.outlay.view.numpad.SimpleNumpadValidator;
+
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import butterknife.Bind;
+
+public class MainFragment extends BaseMvpFragment
+ implements EnterExpenseView {
+ @Bind(app.outlay.R.id.chartIcon)
+ ImageView chartIcon;
+
+ @Bind(app.outlay.R.id.drawerIcon)
+ ImageView drawerIcon;
+
+ @Bind(app.outlay.R.id.categoriesGrid)
+ RecyclerView categoriesGrid;
+
+ @Bind(app.outlay.R.id.amountEditable)
+ EditText amountText;
+
+ @Bind(app.outlay.R.id.addCategory)
+ Button addCategory;
+
+ @Bind(app.outlay.R.id.dateLabel)
+ TextView dateLabel;
+
+ @Inject
+ EnterExpensePresenter presenter;
+
+ @Inject
+ User currentUser;
+
+ private CategoriesGridAdapter adapter;
+ private Date selectedDate = new Date();
+
+ private SimpleNumpadValidator validator = new SimpleNumpadValidator() {
+ @Override
+ public void onInvalidInput(String value) {
+ super.onInvalidInput(value);
+ inputError();
+ }
+ };
+
+ private GridLayoutManager.SpanSizeLookup onSpanSizeLookup = new GridLayoutManager.SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ return position == 0 ? 4 : 1;
+ }
+ };
+
+ @Override
+ public EnterExpensePresenter createPresenter() {
+ return presenter;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getApp().getUserComponent().inject(this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ presenter.getCategories();
+ cleanAmountInput();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(app.outlay.R.layout.fragment_main, null, false);
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ ((DrawerActivity) getActivity()).setupDrawer(currentUser);
+
+ initStaticContent();
+
+ GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 4);
+ gridLayoutManager.setSpanSizeLookup(onSpanSizeLookup);
+ categoriesGrid.setLayoutManager(gridLayoutManager);
+ adapter = new CategoriesGridAdapter();
+ categoriesGrid.setAdapter(adapter);
+
+ adapter.attachNumpadEditable(new NumpadEditable() {
+ @Override
+ public String getText() {
+ return amountText.getText().toString();
+ }
+
+ @Override
+ public void setText(String text) {
+ amountText.setText(text);
+ }
+ }, validator);
+ adapter.setOnCategoryClickListener(category -> {
+ if (validator.valid(amountText.getText().toString())) {
+
+ Expense e = new Expense();
+ e.setCategory(category);
+ e.setAmount(new BigDecimal(amountText.getText().toString()));
+ e.setReportedWhen(selectedDate);
+ analytics().trackExpenseCreated(e);
+ presenter.createExpense(e);
+ cleanAmountInput();
+ } else {
+ validator.onInvalidInput(amountText.getText().toString());
+ }
+ });
+ }
+
+ private void initStaticContent() {
+ chartIcon.setImageDrawable(ResourceUtils.getCustomToolbarIcon(getActivity(), app.outlay.R.integer.ic_chart));
+ drawerIcon.setImageDrawable(ResourceUtils.getMaterialToolbarIcon(getActivity(), app.outlay.R.string.ic_material_menu));
+
+ drawerIcon.setOnClickListener(v -> ((DrawerActivity) getActivity()).getMainDrawer().openDrawer());
+ chartIcon.setOnClickListener(v -> {
+ analytics().trackViewDailyExpenses();
+ Navigator.goToReport(getActivity(), selectedDate);
+ });
+
+ dateLabel.setOnClickListener(v -> {
+ DatePickerFragment datePickerFragment = new DatePickerFragment();
+ datePickerFragment.setOnDateSetListener((parent, year, monthOfYear, dayOfMonth) -> {
+ Calendar c = Calendar.getInstance();
+ c.set(year, monthOfYear, dayOfMonth);
+ Date selected = c.getTime();
+ analytics().trackMainScreenDateChange(new Date(), selected);
+ selectedDate = selected;
+ dateLabel.setText(DateUtils.toLongString(selected));
+ });
+ datePickerFragment.show(getChildFragmentManager(), "datePicker");
+ });
+
+ addCategory.setOnClickListener(view -> Navigator.goToCategoriesList(getActivity()));
+ }
+
+ @Override
+ public void showCategories(List categoryList) {
+ adapter.setItems(categoryList);
+ addCategory.setVisibility(categoryList.isEmpty() ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void setAmount(BigDecimal amount) {
+ amountText.setText(NumberUtils.formatAmount(amount));
+ }
+
+ @Override
+ public void alertExpenseSuccess(Expense expense) {
+ String message = getString(app.outlay.R.string.info_expense_created);
+ message = String.format(message, expense.getAmount(), expense.getCategory().getTitle());
+ Alert.info(getBaseActivity().getRootView(), message,
+ v -> {
+ presenter.deleteExpense(expense);
+ amountText.setText(NumberUtils.formatAmount(expense.getAmount()));
+ }
+ );
+ }
+
+ public void inputError() {
+ Animation shakeAnimation = AnimationUtils.loadAnimation(getActivity(), app.outlay.R.anim.shake);
+ amountText.startAnimation(shakeAnimation);
+ }
+
+ private void cleanAmountInput() {
+ amountText.setText("");
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/view/fragment/ReportFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/ReportFragment.java
similarity index 51%
rename from outlay/app/src/main/java/com/outlay/view/fragment/ReportFragment.java
rename to outlay/app/src/main/java/app/outlay/view/fragment/ReportFragment.java
index f0332b2..78ff4c2 100644
--- a/outlay/app/src/main/java/com/outlay/view/fragment/ReportFragment.java
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/ReportFragment.java
@@ -1,4 +1,4 @@
-package com.outlay.view.fragment;
+package app.outlay.view.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -13,45 +13,45 @@
import android.view.View;
import android.view.ViewGroup;
-import com.outlay.App;
-import com.outlay.R;
-import com.outlay.adapter.ReportAdapter;
-import com.outlay.helper.OnTabSelectedListenerAdapter;
-import com.outlay.model.Report;
-import com.outlay.presenter.ReportPresenter;
-import com.outlay.utils.DateUtils;
-import com.outlay.utils.ResourceUtils;
-import com.outlay.view.dialog.DatePickerFragment;
+import app.outlay.core.utils.DateUtils;
+import app.outlay.domain.model.Report;
+import app.outlay.mvp.presenter.ReportPresenter;
+import app.outlay.mvp.view.StatisticView;
+import app.outlay.utils.ResourceUtils;
+import app.outlay.view.Navigator;
+import app.outlay.view.adapter.ReportAdapter;
+import app.outlay.view.dialog.DatePickerFragment;
+import app.outlay.view.fragment.base.BaseMvpFragment;
+import app.outlay.view.helper.OnTabSelectedListenerAdapter;
+import app.outlay.view.model.CategorizedExpenses;
import java.util.Calendar;
import java.util.Date;
-import java.util.List;
import javax.inject.Inject;
import butterknife.Bind;
-import butterknife.ButterKnife;
/**
* Created by Bogdan Melnychuk on 1/20/16.
*/
-public class ReportFragment extends BaseFragment {
+public class ReportFragment extends BaseMvpFragment implements StatisticView {
public static final String ARG_DATE = "_argDate";
public static final int PERIOD_DAY = 0;
public static final int PERIOD_WEEK = 1;
public static final int PERIOD_MONTH = 2;
- @Bind(R.id.recyclerView)
+ @Bind(app.outlay.R.id.recyclerView)
RecyclerView recyclerView;
- @Bind(R.id.tabs)
+ @Bind(app.outlay.R.id.tabs)
TabLayout tabLayout;
- @Bind(R.id.toolbar)
+ @Bind(app.outlay.R.id.toolbar)
Toolbar toolbar;
- @Bind(R.id.noResults)
+ @Bind(app.outlay.R.id.noResults)
View noResults;
@Inject
@@ -61,54 +61,57 @@ public class ReportFragment extends BaseFragment {
private Date selectedDate;
private ReportAdapter adapter;
+ @Override
+ public ReportPresenter createPresenter() {
+ return presenter;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- App.getComponent(getActivity()).inject(this);
- presenter.attachView(this);
+ getApp().getUserComponent().inject(this);
selectedDate = new Date(getArguments().getLong(ARG_DATE, new Date().getTime()));
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_report, null, false);
- ButterKnife.bind(this, view);
- enableToolbar(toolbar);
- setDisplayHomeAsUpEnabled(true);
- updateTitle();
+ View view = inflater.inflate(app.outlay.R.layout.fragment_report, null, false);
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- inflater.inflate(R.menu.menu_report, menu);
- MenuItem dateItem = menu.findItem(R.id.action_date);
- dateItem.setIcon(ResourceUtils.getMaterialToolbarIcon(getActivity(), R.string.ic_material_today));
- MenuItem listItem = menu.findItem(R.id.action_list);
- listItem.setIcon(ResourceUtils.getMaterialToolbarIcon(getActivity(), R.string.ic_material_list));
+ inflater.inflate(app.outlay.R.menu.menu_report, menu);
+ MenuItem dateItem = menu.findItem(app.outlay.R.id.action_date);
+ dateItem.setIcon(ResourceUtils.getMaterialToolbarIcon(getActivity(), app.outlay.R.string.ic_material_today));
+
+ MenuItem listItem = menu.findItem(app.outlay.R.id.action_list);
+ listItem.setIcon(ResourceUtils.getMaterialToolbarIcon(getActivity(), app.outlay.R.string.ic_material_list));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.action_date:
+ case app.outlay.R.id.action_date:
DatePickerFragment datePickerFragment = new DatePickerFragment();
datePickerFragment.setOnDateSetListener((view, year, monthOfYear, dayOfMonth) -> {
Calendar c = Calendar.getInstance();
c.set(year, monthOfYear, dayOfMonth);
Date selected = c.getTime();
+ analytics().trackExpensesViewDateChange(selectedDate, selected);
selectedDate = selected;
ReportFragment.this.setTitle(DateUtils.toShortString(selected));
updateTitle();
- presenter.loadReport(selectedDate, selectedPeriod);
+ presenter.getExpenses(selectedDate, selectedPeriod);
});
datePickerFragment.show(getChildFragmentManager(), "datePicker");
break;
- case R.id.action_list:
- presenter.goToExpensesList(selectedDate, selectedPeriod);
+ case app.outlay.R.id.action_list:
+ analytics().trackViewExpensesList();
+ goToExpensesList(selectedDate, selectedPeriod);
}
return super.onOptionsItemSelected(item);
}
@@ -116,35 +119,52 @@ public boolean onOptionsItemSelected(MenuItem item) {
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+
+ setToolbar(toolbar);
+ setDisplayHomeAsUpEnabled(true);
+ updateTitle();
+
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
- tabLayout.addTab(tabLayout.newTab().setText(R.string.label_day));
- tabLayout.addTab(tabLayout.newTab().setText(R.string.label_week));
- tabLayout.addTab(tabLayout.newTab().setText(R.string.label_month));
+ tabLayout.addTab(tabLayout.newTab().setText(app.outlay.R.string.label_day));
+ tabLayout.addTab(tabLayout.newTab().setText(app.outlay.R.string.label_week));
+ tabLayout.addTab(tabLayout.newTab().setText(app.outlay.R.string.label_month));
tabLayout.getTabAt(selectedPeriod).select();
tabLayout.setOnTabSelectedListener(new OnTabSelectedListenerAdapter() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
selectedPeriod = tab.getPosition();
+ switch (selectedPeriod) {
+ case ReportFragment.PERIOD_DAY:
+ analytics().trackViewDailyExpenses();
+ break;
+ case ReportFragment.PERIOD_WEEK:
+ analytics().trackViewWeeklyExpenses();
+ break;
+ case ReportFragment.PERIOD_MONTH:
+ analytics().trackViewMonthlyExpenses();
+ break;
+ }
updateTitle();
- presenter.loadReport(selectedDate, selectedPeriod);
+ presenter.getExpenses(selectedDate, selectedPeriod);
}
});
recyclerView.setLayoutManager(layoutManager);
adapter = new ReportAdapter();
recyclerView.setAdapter(adapter);
- presenter.loadReport(selectedDate, selectedPeriod);
+ presenter.getExpenses(selectedDate, selectedPeriod);
- adapter.setOnItemClickListener(report -> presenter.goToExpensesList(selectedDate, selectedPeriod, report.getCategory().getId()));
+ adapter.setOnItemClickListener((category, report) -> goToExpensesList(selectedDate, selectedPeriod, category.getId()));
}
- public void displayReports(List reportList) {
- if (reportList.isEmpty()) {
+ @Override
+ public void showReport(Report report) {
+ if (report.isEmpty()) {
noResults.setVisibility(View.VISIBLE);
} else {
noResults.setVisibility(View.GONE);
- adapter.setItems(reportList);
+ adapter.setItems(new CategorizedExpenses(report));
}
}
@@ -165,4 +185,30 @@ private void updateTitle() {
break;
}
}
+
+ public void goToExpensesList(Date date, int selectedPeriod) {
+ this.goToExpensesList(date, selectedPeriod, null);
+ }
+
+ public void goToExpensesList(Date date, int selectedPeriod, String category) {
+ date = DateUtils.fillCurrentTime(date);
+ Date startDate = date;
+ Date endDate = date;
+
+ switch (selectedPeriod) {
+ case ReportFragment.PERIOD_DAY:
+ startDate = DateUtils.getDayStart(date);
+ endDate = DateUtils.getDayEnd(date);
+ break;
+ case ReportFragment.PERIOD_WEEK:
+ startDate = DateUtils.getWeekStart(date);
+ endDate = DateUtils.getWeekEnd(date);
+ break;
+ case ReportFragment.PERIOD_MONTH:
+ startDate = DateUtils.getMonthStart(date);
+ endDate = DateUtils.getMonthEnd(date);
+ break;
+ }
+ Navigator.goToExpensesList(getActivity(), startDate, endDate, category);
+ }
}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/app/outlay/view/fragment/SyncGuestFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/SyncGuestFragment.java
new file mode 100644
index 0000000..af2c576
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/SyncGuestFragment.java
@@ -0,0 +1,80 @@
+package app.outlay.view.fragment;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import app.outlay.domain.model.User;
+import app.outlay.mvp.presenter.LoginViewPresenter;
+import app.outlay.mvp.view.LoginView;
+import app.outlay.view.LoginForm;
+import app.outlay.view.Navigator;
+import app.outlay.view.fragment.base.BaseMvpFragment;
+
+import javax.inject.Inject;
+
+import butterknife.Bind;
+
+/**
+ * Created by bmelnychuk on 12/14/16.
+ */
+
+public class SyncGuestFragment extends BaseMvpFragment implements LoginView {
+ @Inject
+ LoginViewPresenter presenter;
+
+ @Bind(app.outlay.R.id.loginForm)
+ LoginForm loginForm;
+
+ @Bind(app.outlay.R.id.progressLayout)
+ View progressLayout;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getApp().getAppComponent().inject(this);
+ }
+
+ @Override
+ public LoginViewPresenter createPresenter() {
+ return presenter;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(app.outlay.R.layout.fragment_sync_guest, null, false);
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ loginForm.setMode(LoginForm.MODE_SIGN_UP);
+ loginForm.setToggleModeButtonVisible(false);
+ loginForm.setOnSignUpClickListener((email, password, src) -> {
+ analytics().trackLinkAccount();
+ presenter.linkAccount(email, password);
+ });
+ }
+
+ @Override
+ public void setProgress(boolean running) {
+ progressLayout.setVisibility(running ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void info(String message) {
+ Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onSuccess(User user) {
+ getApp().releaseUserComponent();
+ getApp().createUserComponent(user);
+ Navigator.goToMainScreen(getActivity());
+ getActivity().finish();
+ }
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/fragment/base/BaseFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/base/BaseFragment.java
new file mode 100644
index 0000000..47ab6f0
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/base/BaseFragment.java
@@ -0,0 +1,20 @@
+package app.outlay.view.fragment.base;
+
+import android.support.v7.widget.Toolbar;
+
+import app.outlay.App;
+import app.outlay.analytics.Analytics;
+import app.outlay.di.component.AppComponent;
+import app.outlay.view.activity.base.BaseActivity;
+
+/**
+ * Created by bmelnychuk on 12/14/16.
+ */
+
+public interface BaseFragment {
+ BaseActivity getBaseActivity();
+ App getApp();
+ AppComponent getAppComponent();
+ void setToolbar(Toolbar toolbar);
+ Analytics analytics();
+}
diff --git a/outlay/app/src/main/java/app/outlay/view/fragment/base/BaseMvpFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/base/BaseMvpFragment.java
new file mode 100644
index 0000000..83386f9
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/base/BaseMvpFragment.java
@@ -0,0 +1,86 @@
+package app.outlay.view.fragment.base;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Toast;
+
+import com.hannesdorfmann.mosby.mvp.MvpFragment;
+import com.hannesdorfmann.mosby.mvp.MvpPresenter;
+import com.hannesdorfmann.mosby.mvp.MvpView;
+import app.outlay.App;
+import app.outlay.analytics.Analytics;
+import app.outlay.di.component.AppComponent;
+import app.outlay.view.activity.base.BaseActivity;
+
+import butterknife.ButterKnife;
+
+/**
+ * Created by bmelnychuk on 12/14/16.
+ */
+
+public abstract class BaseMvpFragment> extends MvpFragment implements BaseFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ ButterKnife.bind(this, view);
+ }
+
+ public void setDisplayHomeAsUpEnabled(boolean value) {
+ ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(value);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ getActivity().onBackPressed();
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public void setTitle(String value) {
+ getActivity().setTitle(value);
+ }
+
+ @Override
+ public void setToolbar(Toolbar toolbar) {
+ AppCompatActivity activity = (AppCompatActivity) getActivity();
+ activity.setSupportActionBar(toolbar);
+ }
+
+ @Override
+ public BaseActivity getBaseActivity() {
+ return (BaseActivity) getActivity();
+ }
+
+ @Override
+ public App getApp() {
+ return getBaseActivity().getApp();
+ }
+
+ @Override
+ public AppComponent getAppComponent() {
+ return getBaseActivity().getApplicationComponent();
+ }
+
+ public void error(Throwable throwable) {
+ Toast.makeText(getActivity(), throwable.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public Analytics analytics() {
+ return getAppComponent().analytics();
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/view/fragment/BaseFragment.java b/outlay/app/src/main/java/app/outlay/view/fragment/base/StaticContentFragment.java
similarity index 54%
rename from outlay/app/src/main/java/com/outlay/view/fragment/BaseFragment.java
rename to outlay/app/src/main/java/app/outlay/view/fragment/base/StaticContentFragment.java
index c6dae52..04eedc9 100644
--- a/outlay/app/src/main/java/com/outlay/view/fragment/BaseFragment.java
+++ b/outlay/app/src/main/java/app/outlay/view/fragment/base/StaticContentFragment.java
@@ -1,29 +1,39 @@
-package com.outlay.view.fragment;
+package app.outlay.view.fragment.base;
-import android.app.Fragment;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
-import com.outlay.view.activity.BaseActivity;
+import app.outlay.App;
+import app.outlay.analytics.Analytics;
+import app.outlay.di.component.AppComponent;
+import app.outlay.view.activity.base.BaseActivity;
+import app.outlay.view.activity.base.ParentActivity;
/**
* Created by Bogdan Melnychuk on 1/20/16.
*/
-public class BaseFragment extends Fragment {
+public class StaticContentFragment extends Fragment implements BaseFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
- public void enableToolbar(Toolbar toolbar) {
+ @Override
+ public void setToolbar(Toolbar toolbar) {
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.setSupportActionBar(toolbar);
}
+ @Override
+ public Analytics analytics() {
+ return getAppComponent().analytics();
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@@ -43,6 +53,21 @@ public void setTitle(String value) {
}
public View getRootView() {
- return ((BaseActivity)getActivity()).getRootView();
+ return ((ParentActivity) getActivity()).getRootView();
+ }
+
+ @Override
+ public BaseActivity getBaseActivity() {
+ return (BaseActivity) getActivity();
+ }
+
+ @Override
+ public App getApp() {
+ return getBaseActivity().getApp();
+ }
+
+ @Override
+ public AppComponent getAppComponent() {
+ return getBaseActivity().getApplicationComponent();
}
}
diff --git a/outlay/app/src/main/java/app/outlay/view/helper/AnimationUtils.java b/outlay/app/src/main/java/app/outlay/view/helper/AnimationUtils.java
new file mode 100644
index 0000000..feca956
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/helper/AnimationUtils.java
@@ -0,0 +1,142 @@
+package app.outlay.view.helper;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.graphics.Point;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+/**
+ * Created by bogdan.melnychuk on 09.12.2014.
+ */
+public final class AnimationUtils {
+ public static void animateBounceIn(final View target) {
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(
+ ObjectAnimator.ofFloat(target, "alpha", 0, 1, 1, 1),
+ ObjectAnimator.ofFloat(target, "scaleX", 0.3f, 1.05f, 0.9f, 1),
+ ObjectAnimator.ofFloat(target, "scaleY", 0.3f, 1.05f, 0.9f, 1)
+ );
+ set.setDuration(500);
+ target.setVisibility(View.VISIBLE);
+ set.start();
+ }
+
+ public static void animatePulse(final View target) {
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(
+ ObjectAnimator.ofFloat(target, "scaleY", 1, 1.1f, 1),
+ ObjectAnimator.ofFloat(target, "scaleX", 1, 1.1f, 1)
+ );
+ set.setDuration(1000);
+ set.start();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static void showWithReveal(View view) {
+ int x = (view.getRight() - view.getLeft()) / 2;
+ int y = view.getBottom();
+ showWithReveal(view, new Point(x, y));
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static void showWithReveal(View view, Point point) {
+ view.setVisibility(View.VISIBLE);
+
+ // get the final radius for the clipping circle
+ int endRadius = (int) Math.hypot(view.getWidth(), view.getHeight());
+
+ Animator animator =
+ ViewAnimationUtils.createCircularReveal(view, point.x, point.y, 0, endRadius);
+ animator.setInterpolator(new AccelerateDecelerateInterpolator());
+ animator.setDuration(500);
+ animator.start();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static void hideWithReveal(View view) {
+ int cx = (view.getRight() - view.getLeft()) / 2;
+ int cy = view.getBottom();
+ hideWithReveal(view, new Point(cx, cy));
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static void hideWithReveal(View view, Point point) {
+ // get the initial radius for the clipping circle
+ int initialRadius = (int) Math.hypot(view.getWidth(), view.getHeight());
+
+ // create the animation (the final radius is zero)
+ Animator animator = ViewAnimationUtils.createCircularReveal(view, point.x, point.y, initialRadius, 0);
+
+ // make the view invisible when the animation is done
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+
+ animator.setInterpolator(new AccelerateDecelerateInterpolator());
+ animator.setDuration(500);
+ animator.start();
+ }
+
+ private static ValueAnimator slideAnimator(int start, int end, final View summary) {
+ ValueAnimator animator = ValueAnimator.ofInt(start, end);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ //Update Height
+ int value = (Integer) valueAnimator.getAnimatedValue();
+
+ ViewGroup.LayoutParams layoutParams = summary.getLayoutParams();
+ layoutParams.height = value;
+ summary.setLayoutParams(layoutParams);
+ }
+ });
+ return animator;
+ }
+
+ public static void expand(View summary) {
+ //set Visible
+ summary.setVisibility(View.VISIBLE);
+ final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ summary.measure(widthSpec, 300);
+ ValueAnimator mAnimator = slideAnimator(0, 300, summary);
+ mAnimator.start();
+ }
+
+ public static void collapse(final View summary) {
+ int finalHeight = summary.getHeight();
+ ValueAnimator mAnimator = slideAnimator(finalHeight, 0, summary);
+ mAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ //Height=0, but it set visibility to GONE
+ summary.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+ }
+ });
+ mAnimator.start();
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/helper/OnTabSelectedListenerAdapter.java b/outlay/app/src/main/java/app/outlay/view/helper/OnTabSelectedListenerAdapter.java
similarity index 92%
rename from outlay/app/src/main/java/com/outlay/helper/OnTabSelectedListenerAdapter.java
rename to outlay/app/src/main/java/app/outlay/view/helper/OnTabSelectedListenerAdapter.java
index acd8828..2277486 100644
--- a/outlay/app/src/main/java/com/outlay/helper/OnTabSelectedListenerAdapter.java
+++ b/outlay/app/src/main/java/app/outlay/view/helper/OnTabSelectedListenerAdapter.java
@@ -1,4 +1,4 @@
-package com.outlay.helper;
+package app.outlay.view.helper;
import android.support.design.widget.TabLayout;
diff --git a/outlay/app/src/main/java/com/outlay/helper/TextWatcherAdapter.java b/outlay/app/src/main/java/app/outlay/view/helper/TextWatcherAdapter.java
similarity index 93%
rename from outlay/app/src/main/java/com/outlay/helper/TextWatcherAdapter.java
rename to outlay/app/src/main/java/app/outlay/view/helper/TextWatcherAdapter.java
index 59ebb2f..fdbe79d 100644
--- a/outlay/app/src/main/java/com/outlay/helper/TextWatcherAdapter.java
+++ b/outlay/app/src/main/java/app/outlay/view/helper/TextWatcherAdapter.java
@@ -1,4 +1,4 @@
-package com.outlay.helper;
+package app.outlay.view.helper;
import android.text.Editable;
import android.text.TextWatcher;
diff --git a/outlay/app/src/main/java/app/outlay/view/helper/ViewHelper.java b/outlay/app/src/main/java/app/outlay/view/helper/ViewHelper.java
new file mode 100644
index 0000000..28e0378
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/helper/ViewHelper.java
@@ -0,0 +1,17 @@
+package app.outlay.view.helper;
+
+import android.content.res.Resources;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class ViewHelper {
+ public static int dpToPx(int dp) {
+ return Math.round(dp * Resources.getSystem().getDisplayMetrics().density);
+ }
+
+ public static int pxToDp(int px) {
+ return Math.round(px / Resources.getSystem().getDisplayMetrics().density);
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/view/helper/itemtouch/ItemTouchHelperAdapter.java b/outlay/app/src/main/java/app/outlay/view/helper/itemtouch/ItemTouchHelperAdapter.java
similarity index 98%
rename from outlay/app/src/main/java/com/outlay/view/helper/itemtouch/ItemTouchHelperAdapter.java
rename to outlay/app/src/main/java/app/outlay/view/helper/itemtouch/ItemTouchHelperAdapter.java
index 121a787..467cd72 100755
--- a/outlay/app/src/main/java/com/outlay/view/helper/itemtouch/ItemTouchHelperAdapter.java
+++ b/outlay/app/src/main/java/app/outlay/view/helper/itemtouch/ItemTouchHelperAdapter.java
@@ -1,4 +1,4 @@
-package com.outlay.view.helper.itemtouch;
+package app.outlay.view.helper.itemtouch;
import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
diff --git a/outlay/app/src/main/java/com/outlay/view/helper/itemtouch/ItemTouchHelperViewHolder.java b/outlay/app/src/main/java/app/outlay/view/helper/itemtouch/ItemTouchHelperViewHolder.java
similarity index 93%
rename from outlay/app/src/main/java/com/outlay/view/helper/itemtouch/ItemTouchHelperViewHolder.java
rename to outlay/app/src/main/java/app/outlay/view/helper/itemtouch/ItemTouchHelperViewHolder.java
index af73c36..f5f8e53 100755
--- a/outlay/app/src/main/java/com/outlay/view/helper/itemtouch/ItemTouchHelperViewHolder.java
+++ b/outlay/app/src/main/java/app/outlay/view/helper/itemtouch/ItemTouchHelperViewHolder.java
@@ -1,4 +1,4 @@
-package com.outlay.view.helper.itemtouch;
+package app.outlay.view.helper.itemtouch;
import android.support.v7.widget.helper.ItemTouchHelper;
diff --git a/outlay/app/src/main/java/com/outlay/view/helper/itemtouch/OnDragListener.java b/outlay/app/src/main/java/app/outlay/view/helper/itemtouch/OnDragListener.java
similarity index 90%
rename from outlay/app/src/main/java/com/outlay/view/helper/itemtouch/OnDragListener.java
rename to outlay/app/src/main/java/app/outlay/view/helper/itemtouch/OnDragListener.java
index dbd114b..25c6217 100755
--- a/outlay/app/src/main/java/com/outlay/view/helper/itemtouch/OnDragListener.java
+++ b/outlay/app/src/main/java/app/outlay/view/helper/itemtouch/OnDragListener.java
@@ -1,4 +1,4 @@
-package com.outlay.view.helper.itemtouch;
+package app.outlay.view.helper.itemtouch;
import android.support.v7.widget.RecyclerView;
diff --git a/outlay/app/src/main/java/com/outlay/view/helper/itemtouch/SimpleItemTouchHelperCallback.java b/outlay/app/src/main/java/app/outlay/view/helper/itemtouch/SimpleItemTouchHelperCallback.java
similarity index 99%
rename from outlay/app/src/main/java/com/outlay/view/helper/itemtouch/SimpleItemTouchHelperCallback.java
rename to outlay/app/src/main/java/app/outlay/view/helper/itemtouch/SimpleItemTouchHelperCallback.java
index addf497..caa9844 100755
--- a/outlay/app/src/main/java/com/outlay/view/helper/itemtouch/SimpleItemTouchHelperCallback.java
+++ b/outlay/app/src/main/java/app/outlay/view/helper/itemtouch/SimpleItemTouchHelperCallback.java
@@ -1,4 +1,4 @@
-package com.outlay.view.helper.itemtouch;
+package app.outlay.view.helper.itemtouch;
import android.graphics.Canvas;
import android.support.v7.widget.GridLayoutManager;
diff --git a/outlay/app/src/main/java/app/outlay/view/model/CategorizedExpenses.java b/outlay/app/src/main/java/app/outlay/view/model/CategorizedExpenses.java
new file mode 100644
index 0000000..61e2fbe
--- /dev/null
+++ b/outlay/app/src/main/java/app/outlay/view/model/CategorizedExpenses.java
@@ -0,0 +1,49 @@
+package app.outlay.view.model;
+
+import app.outlay.domain.model.Category;
+import app.outlay.domain.model.Report;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by bmelnychuk on 2/8/17.
+ */
+
+public class CategorizedExpenses {
+ private Map grouped;
+ private List categories;
+
+ public CategorizedExpenses(Report report) {
+ grouped = report.groupByCategory();
+ categories = new ArrayList<>(grouped.keySet());
+ }
+
+ public CategorizedExpenses() {
+ grouped = new HashMap<>();
+ categories = new ArrayList<>();
+ }
+
+ public int getCategoriesSize() {
+ return grouped.size();
+ }
+
+ public List getCategories() {
+ return categories;
+ }
+
+ public Category getCategory(int index) {
+ return categories.get(index);
+ }
+
+ public Report getReport(Category category) {
+ return grouped.get(category);
+ }
+
+ public Report getReport(int index) {
+ Category c = categories.get(index);
+ return getReport(c);
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/view/numpad/NumpadButton.java b/outlay/app/src/main/java/app/outlay/view/numpad/NumpadButton.java
similarity index 80%
rename from outlay/app/src/main/java/com/outlay/view/numpad/NumpadButton.java
rename to outlay/app/src/main/java/app/outlay/view/numpad/NumpadButton.java
index b08a528..a2d6c15 100644
--- a/outlay/app/src/main/java/com/outlay/view/numpad/NumpadButton.java
+++ b/outlay/app/src/main/java/app/outlay/view/numpad/NumpadButton.java
@@ -1,4 +1,4 @@
-package com.outlay.view.numpad;
+package app.outlay.view.numpad;
import android.annotation.TargetApi;
import android.content.Context;
@@ -12,7 +12,6 @@
import android.widget.TextView;
import com.github.johnkil.print.PrintView;
-import com.outlay.R;
import butterknife.Bind;
import butterknife.ButterKnife;
@@ -23,11 +22,11 @@
public class NumpadButton extends RelativeLayout {
@Nullable
- @Bind(R.id.textValue)
+ @Bind(app.outlay.R.id.textValue)
TextView textValue;
@Nullable
- @Bind(R.id.numpadIcon)
+ @Bind(app.outlay.R.id.numpadIcon)
PrintView numpadIcon;
public NumpadButton(Context context) {
@@ -58,10 +57,10 @@ private void init(AttributeSet attrs) {
return;
}
- TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.NumpadButton, 0, 0);
+ TypedArray ta = getContext().obtainStyledAttributes(attrs, app.outlay.R.styleable.NumpadButton, 0, 0);
try {
- String icon = ta.getString(R.styleable.NumpadButton_numpadButtonIcon);
- String name = ta.getString(R.styleable.NumpadButton_numpadButtonText);
+ String icon = ta.getString(app.outlay.R.styleable.NumpadButton_numpadButtonIcon);
+ String name = ta.getString(app.outlay.R.styleable.NumpadButton_numpadButtonText);
if (icon == null) {
initTextButton(inflater);
textValue.setText(name);
@@ -76,13 +75,13 @@ private void init(AttributeSet attrs) {
}
private View initTextButton(LayoutInflater inflater) {
- View parent = inflater.inflate(R.layout.view_numpad_button, this, true);
+ View parent = inflater.inflate(app.outlay.R.layout.view_numpad_button, this, true);
ButterKnife.bind(this, parent);
return parent;
}
private View initIconButton(LayoutInflater inflater) {
- View parent = inflater.inflate(R.layout.view_numpad_icon_button, this, true);
+ View parent = inflater.inflate(app.outlay.R.layout.view_numpad_icon_button, this, true);
ButterKnife.bind(this, parent);
return parent;
}
diff --git a/outlay/app/src/main/java/com/outlay/view/numpad/NumpadEditable.java b/outlay/app/src/main/java/app/outlay/view/numpad/NumpadEditable.java
similarity index 81%
rename from outlay/app/src/main/java/com/outlay/view/numpad/NumpadEditable.java
rename to outlay/app/src/main/java/app/outlay/view/numpad/NumpadEditable.java
index 3079fbe..2208f50 100644
--- a/outlay/app/src/main/java/com/outlay/view/numpad/NumpadEditable.java
+++ b/outlay/app/src/main/java/app/outlay/view/numpad/NumpadEditable.java
@@ -1,4 +1,4 @@
-package com.outlay.view.numpad;
+package app.outlay.view.numpad;
/**
* Created by Bogdan Melnychuk on 1/15/16.
diff --git a/outlay/app/src/main/java/com/outlay/view/numpad/NumpadValidator.java b/outlay/app/src/main/java/app/outlay/view/numpad/NumpadValidator.java
similarity index 83%
rename from outlay/app/src/main/java/com/outlay/view/numpad/NumpadValidator.java
rename to outlay/app/src/main/java/app/outlay/view/numpad/NumpadValidator.java
index 892a3df..1bcc637 100644
--- a/outlay/app/src/main/java/com/outlay/view/numpad/NumpadValidator.java
+++ b/outlay/app/src/main/java/app/outlay/view/numpad/NumpadValidator.java
@@ -1,4 +1,4 @@
-package com.outlay.view.numpad;
+package app.outlay.view.numpad;
/**
* Created by Bogdan Melnychuk on 1/15/16.
diff --git a/outlay/app/src/main/java/com/outlay/view/numpad/NumpadView.java b/outlay/app/src/main/java/app/outlay/view/numpad/NumpadView.java
similarity index 75%
rename from outlay/app/src/main/java/com/outlay/view/numpad/NumpadView.java
rename to outlay/app/src/main/java/app/outlay/view/numpad/NumpadView.java
index aea39ef..36ffbe8 100644
--- a/outlay/app/src/main/java/com/outlay/view/numpad/NumpadView.java
+++ b/outlay/app/src/main/java/app/outlay/view/numpad/NumpadView.java
@@ -1,4 +1,4 @@
-package com.outlay.view.numpad;
+package app.outlay.view.numpad;
import android.annotation.TargetApi;
import android.content.Context;
@@ -8,8 +8,6 @@
import android.view.View;
import android.widget.LinearLayout;
-import com.outlay.R;
-
/**
* Created by Bogdan Melnychuk on 1/15/16.
*/
@@ -52,20 +50,20 @@ public NumpadView(Context context, AttributeSet attrs, int defStyleAttr, int def
private void init() {
setOrientation(VERTICAL);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View parent = inflater.inflate(R.layout.view_numpad, this, true);
-
- parent.findViewById(R.id.btn1).setOnClickListener(this);
- parent.findViewById(R.id.btn2).setOnClickListener(this);
- parent.findViewById(R.id.btn3).setOnClickListener(this);
- parent.findViewById(R.id.btn4).setOnClickListener(this);
- parent.findViewById(R.id.btn5).setOnClickListener(this);
- parent.findViewById(R.id.btn6).setOnClickListener(this);
- parent.findViewById(R.id.btn7).setOnClickListener(this);
- parent.findViewById(R.id.btn8).setOnClickListener(this);
- parent.findViewById(R.id.btn9).setOnClickListener(this);
- parent.findViewById(R.id.btn0).setOnClickListener(this);
- parent.findViewById(R.id.btnDecimal).setOnClickListener(this);
- View clearButton = parent.findViewById(R.id.btnClear);
+ View parent = inflater.inflate(app.outlay.R.layout.view_numpad, this, true);
+
+ parent.findViewById(app.outlay.R.id.btn1).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btn2).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btn3).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btn4).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btn5).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btn6).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btn7).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btn8).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btn9).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btn0).setOnClickListener(this);
+ parent.findViewById(app.outlay.R.id.btnDecimal).setOnClickListener(this);
+ View clearButton = parent.findViewById(app.outlay.R.id.btnClear);
clearButton.setOnClickListener(this);
clearButton.setOnLongClickListener(v -> {
if (numpadClickListener != null) {
@@ -94,40 +92,40 @@ public void attachEditable(NumpadEditable attachedEditable) {
@Override
public void onClick(View v) {
switch (v.getId()) {
- case R.id.btn0:
+ case app.outlay.R.id.btn0:
onNumberClicked(0);
break;
- case R.id.btn1:
+ case app.outlay.R.id.btn1:
onNumberClicked(1);
break;
- case R.id.btn2:
+ case app.outlay.R.id.btn2:
onNumberClicked(2);
break;
- case R.id.btn3:
+ case app.outlay.R.id.btn3:
onNumberClicked(3);
break;
- case R.id.btn4:
+ case app.outlay.R.id.btn4:
onNumberClicked(4);
break;
- case R.id.btn5:
+ case app.outlay.R.id.btn5:
onNumberClicked(5);
break;
- case R.id.btn6:
+ case app.outlay.R.id.btn6:
onNumberClicked(6);
break;
- case R.id.btn7:
+ case app.outlay.R.id.btn7:
onNumberClicked(7);
break;
- case R.id.btn8:
+ case app.outlay.R.id.btn8:
onNumberClicked(8);
break;
- case R.id.btn9:
+ case app.outlay.R.id.btn9:
onNumberClicked(9);
break;
- case R.id.btnClear:
+ case app.outlay.R.id.btnClear:
onClearClicked();
break;
- case R.id.btnDecimal:
+ case app.outlay.R.id.btnDecimal:
onDecimalCLicked();
break;
}
diff --git a/outlay/app/src/main/java/com/outlay/view/numpad/SimpleNumpadValidator.java b/outlay/app/src/main/java/app/outlay/view/numpad/SimpleNumpadValidator.java
similarity index 94%
rename from outlay/app/src/main/java/com/outlay/view/numpad/SimpleNumpadValidator.java
rename to outlay/app/src/main/java/app/outlay/view/numpad/SimpleNumpadValidator.java
index 7cd5836..400bc72 100644
--- a/outlay/app/src/main/java/com/outlay/view/numpad/SimpleNumpadValidator.java
+++ b/outlay/app/src/main/java/app/outlay/view/numpad/SimpleNumpadValidator.java
@@ -1,4 +1,4 @@
-package com.outlay.view.numpad;
+package app.outlay.view.numpad;
import android.text.TextUtils;
diff --git a/outlay/app/src/main/java/com/outlay/view/progress/ProgressLayout.java b/outlay/app/src/main/java/app/outlay/view/progress/ProgressLayout.java
similarity index 90%
rename from outlay/app/src/main/java/com/outlay/view/progress/ProgressLayout.java
rename to outlay/app/src/main/java/app/outlay/view/progress/ProgressLayout.java
index 506aea1..f06e7ac 100644
--- a/outlay/app/src/main/java/com/outlay/view/progress/ProgressLayout.java
+++ b/outlay/app/src/main/java/app/outlay/view/progress/ProgressLayout.java
@@ -1,4 +1,4 @@
-package com.outlay.view.progress;
+package app.outlay.view.progress;
/**
* Created by Bogdan Melnychuk on 1/31/16.
@@ -14,8 +14,6 @@
import android.util.AttributeSet;
import android.view.View;
-import com.outlay.R;
-
public class ProgressLayout extends View implements Animatable {
private static final int COLOR_EMPTY_DEFAULT = 0x00000000;
@@ -88,12 +86,12 @@ public ProgressLayout(Context context, AttributeSet attrs, int defStyleAttr, int
private void init(Context context, AttributeSet attrs) {
setWillNotDraw(false);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.progressLayout);
- isAutoProgress = a.getBoolean(R.styleable.progressLayout_autoProgress, true);
- maxProgress = a.getInt(R.styleable.progressLayout_maxProgress, 0);
+ TypedArray a = context.obtainStyledAttributes(attrs, app.outlay.R.styleable.progressLayout);
+ isAutoProgress = a.getBoolean(app.outlay.R.styleable.progressLayout_autoProgress, true);
+ maxProgress = a.getInt(app.outlay.R.styleable.progressLayout_maxProgress, 0);
maxProgress = maxProgress * 10;
- int loadedColor = a.getColor(R.styleable.progressLayout_loadedColor, COLOR_LOADED_DEFAULT);
- int emptyColor = a.getColor(R.styleable.progressLayout_emptyColor, COLOR_EMPTY_DEFAULT);
+ int loadedColor = a.getColor(app.outlay.R.styleable.progressLayout_loadedColor, COLOR_LOADED_DEFAULT);
+ int emptyColor = a.getColor(app.outlay.R.styleable.progressLayout_emptyColor, COLOR_EMPTY_DEFAULT);
a.recycle();
paintProgressEmpty = new Paint();
diff --git a/outlay/app/src/main/java/com/outlay/view/progress/ProgressLayoutListener.java b/outlay/app/src/main/java/app/outlay/view/progress/ProgressLayoutListener.java
similarity index 83%
rename from outlay/app/src/main/java/com/outlay/view/progress/ProgressLayoutListener.java
rename to outlay/app/src/main/java/app/outlay/view/progress/ProgressLayoutListener.java
index cbacaa8..d3750d7 100644
--- a/outlay/app/src/main/java/com/outlay/view/progress/ProgressLayoutListener.java
+++ b/outlay/app/src/main/java/app/outlay/view/progress/ProgressLayoutListener.java
@@ -1,4 +1,4 @@
-package com.outlay.view.progress;
+package app.outlay.view.progress;
/**
* Created by Bogdan Melnychuk on 1/31/16.
diff --git a/outlay/app/src/main/java/com/outlay/App.java b/outlay/app/src/main/java/com/outlay/App.java
deleted file mode 100644
index 5b69a8e..0000000
--- a/outlay/app/src/main/java/com/outlay/App.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.outlay;
-
-import android.app.Application;
-import android.content.Context;
-
-import com.crashlytics.android.Crashlytics;
-import com.outlay.di.AppComponent;
-import com.outlay.di.DaggerAppComponent;
-import com.outlay.di.module.AppModule;
-import com.outlay.di.module.DaoModule;
-
-import io.fabric.sdk.android.Fabric;
-
-/**
- * Created by Bogdan Melnychuk on 1/15/16.
- */
-public class App extends Application {
- private AppComponent appComponent;
-
- @Override
- public void onCreate() {
- super.onCreate();
- Fabric.with(this, new Crashlytics());
- initializeInjector();
- }
-
- private void initializeInjector() {
- appComponent = DaggerAppComponent.builder()
- .appModule(new AppModule(this))
- .daoModule(new DaoModule())
- .build();
- }
-
- public AppComponent getAppComponent() {
- return appComponent;
- }
-
- public static AppComponent getComponent(Context context) {
- return ((App) context.getApplicationContext()).getAppComponent();
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/adapter/CategoriesGridAdapter.java b/outlay/app/src/main/java/com/outlay/adapter/CategoriesGridAdapter.java
deleted file mode 100644
index 8d2f644..0000000
--- a/outlay/app/src/main/java/com/outlay/adapter/CategoriesGridAdapter.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.outlay.adapter;
-
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.github.johnkil.print.PrintView;
-import com.outlay.R;
-import com.outlay.adapter.listener.OnCategoryClickListener;
-import com.outlay.dao.Category;
-import com.outlay.utils.IconUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-
-/**
- * Created by Bogdan Melnychuk on 1/15/16.
- */
-public class CategoriesGridAdapter extends RecyclerView.Adapter {
-
- public static class Style {
- public final int itemHeight;
-
- public Style(int itemHeight) {
- this.itemHeight = itemHeight;
- }
- }
-
- private Style style;
- protected List items;
- private OnCategoryClickListener clickListener;
-
- public void setOnCategoryClickListener(OnCategoryClickListener listener) {
- this.clickListener = listener;
- }
-
- public CategoriesGridAdapter(List categories, Style style) {
- this.style = style;
- this.items = categories;
- }
-
- public CategoriesGridAdapter(Style style) {
- this(new ArrayList<>(), style);
- }
-
- public void setItems(List items) {
- this.items = items;
- notifyDataSetChanged();
- }
-
- @Override
- public CategoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- final View v = inflater.inflate(R.layout.item_category, parent, false);
- final CategoryViewHolder viewHolder = new CategoryViewHolder(v);
- return viewHolder;
- }
-
- @Override
- public void onBindViewHolder(CategoryViewHolder holder, int position) {
- Category currentOne = items.get(position);
- if (style != null) {
- holder.categoryContainer.getLayoutParams().height = style.itemHeight;
- }
- holder.categoryTitle.setText(currentOne.getTitle());
- IconUtils.loadCategoryIcon(currentOne, holder.categoryIcon);
- holder.categoryIcon.setOnClickListener(v -> {
- if (clickListener != null) {
- clickListener.onCategoryClicked(currentOne);
- }
- });
- }
-
- @Override
- public int getItemCount() {
- return items.size();
- }
-
- public class CategoryViewHolder extends RecyclerView.ViewHolder {
- @Bind(R.id.categoryContainer)
- View categoryContainer;
-
- @Bind(R.id.categoryIcon)
- PrintView categoryIcon;
-
- @Bind(R.id.categoryTitle)
- TextView categoryTitle;
-
- public CategoryViewHolder(View v) {
- super(v);
- ButterKnife.bind(this, v);
- }
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/adapter/ExpenseAdapter.java b/outlay/app/src/main/java/com/outlay/adapter/ExpenseAdapter.java
deleted file mode 100644
index 7555668..0000000
--- a/outlay/app/src/main/java/com/outlay/adapter/ExpenseAdapter.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.outlay.adapter;
-
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.github.johnkil.print.PrintView;
-import com.outlay.R;
-import com.outlay.dao.Expense;
-import com.outlay.utils.DateUtils;
-import com.outlay.utils.FormatUtils;
-import com.outlay.utils.IconUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-
-/**
- * Created by Bogdan Melnychuk on 1/15/16.
- */
-public class ExpenseAdapter extends RecyclerView.Adapter {
-
- private List items;
- private OnExpenseClickListener onExpenseClickListener;
-
- public ExpenseAdapter(List categories) {
- this.items = categories;
- }
-
- public ExpenseAdapter() {
- this(new ArrayList<>());
- }
-
- public void setItems(List items) {
- this.items = items;
- notifyDataSetChanged();
- }
-
- public void setOnExpenseClickListener(OnExpenseClickListener onExpenseClickListener) {
- this.onExpenseClickListener = onExpenseClickListener;
- }
-
- @Override
- public ExpenseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- final View v = inflater.inflate(R.layout.item_expense, parent, false);
- final ExpenseViewHolder viewHolder = new ExpenseViewHolder(v);
- return viewHolder;
- }
-
- @Override
- public void onBindViewHolder(ExpenseViewHolder holder, int position) {
- Expense expense = items.get(position);
- holder.note.setText(expense.getNote());
- holder.root.setOnClickListener(v -> {
- if (onExpenseClickListener != null) {
- onExpenseClickListener.onExpenseClicked(expense);
- }
- });
- holder.categoryAmount.setText(FormatUtils.formatAmount(expense.getAmount()));
- holder.categoryDate.setText(DateUtils.toShortString(expense.getReportedAt()));
- holder.categoryTitle.setText(expense.getCategory().getTitle());
- IconUtils.loadCategoryIcon(expense.getCategory(), holder.categoryIcon);
- }
-
- @Override
- public int getItemCount() {
- return items.size();
- }
-
- public class ExpenseViewHolder extends RecyclerView.ViewHolder {
- @Bind(R.id.categoryNote)
- TextView note;
-
- @Bind(R.id.expenseContainer)
- View root;
-
- @Bind(R.id.categoryIcon)
- PrintView categoryIcon;
-
- @Bind(R.id.categoryTitle)
- TextView categoryTitle;
-
- @Bind(R.id.categoryDate)
- TextView categoryDate;
-
- @Bind(R.id.categoryAmount)
- TextView categoryAmount;
-
- public ExpenseViewHolder(View v) {
- super(v);
- ButterKnife.bind(this, v);
- }
- }
-
- public interface OnExpenseClickListener {
- void onExpenseClicked(Expense e);
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/adapter/ReportAdapter.java b/outlay/app/src/main/java/com/outlay/adapter/ReportAdapter.java
deleted file mode 100644
index 4c69c0c..0000000
--- a/outlay/app/src/main/java/com/outlay/adapter/ReportAdapter.java
+++ /dev/null
@@ -1,205 +0,0 @@
-package com.outlay.adapter;
-
-import android.graphics.Color;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.github.johnkil.print.PrintView;
-import com.github.mikephil.charting.animation.Easing;
-import com.github.mikephil.charting.charts.PieChart;
-import com.github.mikephil.charting.data.Entry;
-import com.github.mikephil.charting.data.PieData;
-import com.github.mikephil.charting.data.PieDataSet;
-import com.github.mikephil.charting.highlight.Highlight;
-import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
-import com.outlay.R;
-import com.outlay.model.Report;
-import com.outlay.utils.FormatUtils;
-import com.outlay.utils.IconUtils;
-import com.outlay.view.progress.ProgressLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-
-/**
- * Created by Bogdan Melnychuk on 1/27/16.
- */
-public class ReportAdapter extends RecyclerView.Adapter {
- private static final int TYPE_CHART = 0;
- private static final int TYPE_REPORT_ITEM = 1;
-
-
- private List reports;
- private double maxProgress;
- private ItemClickListener onItemClickListener;
-
- public ReportAdapter(List items) {
- this.reports = items;
- maxProgress = getMaxProgress();
- }
-
- public void setOnItemClickListener(ItemClickListener onItemClickListener) {
- this.onItemClickListener = onItemClickListener;
- }
-
- public ReportAdapter() {
- this(new ArrayList<>());
- }
-
- public void setItems(List items) {
- this.reports = items;
- maxProgress = getMaxProgress();
- notifyDataSetChanged();
- }
-
- @Override
- public int getItemViewType(int position) {
- return position == 0 ? TYPE_CHART : TYPE_REPORT_ITEM;
- }
-
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- RecyclerView.ViewHolder result;
- if (viewType == TYPE_CHART) {
- final View v = inflater.inflate(R.layout.layout_chart, parent, false);
- result = new ChartViewHolder(v);
-
- } else {
- final View v = inflater.inflate(R.layout.item_report, parent, false);
- result = new ReportViewHolder(v);
- }
- return result;
- }
-
- @Override
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
- if (holder instanceof ReportViewHolder) {
- ReportViewHolder reportHolder = (ReportViewHolder) holder;
- Report currentReport = reports.get(position - 1);
- reportHolder.amountText.setText(FormatUtils.formatAmount(currentReport.getAmount()));
- reportHolder.titleText.setText(currentReport.getTitle());
- IconUtils.loadCategoryIcon(currentReport.getIcon(), reportHolder.icon);
- reportHolder.progressLayout.setMaxProgress((int) (maxProgress * 10));
- reportHolder.progressLayout.setCurrentProgress((int) (currentReport.getAmount() * 10));
- reportHolder.icon.setIconColor(currentReport.getColor());
- reportHolder.reportContainer.setOnClickListener(v -> {
- if (onItemClickListener != null) {
- onItemClickListener.onItemClicked(currentReport);
- }
- });
- //reportHolder.progressLayout.setLoadedColor(currentReport.getColor());
- } else if (holder instanceof ChartViewHolder) {
- ChartViewHolder charViewHolder = (ChartViewHolder) holder;
- updateChartData(reports, charViewHolder.chart);
- charViewHolder.chart.animateY(1000, Easing.EasingOption.EaseInOutQuad);
- charViewHolder.chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
- @Override
- public void onValueSelected(Entry e, int dataSetIndex, Highlight h) {
- if (onItemClickListener != null) {
- onItemClickListener.onItemClicked(reports.get(h.getXIndex()));
- }
- }
-
- @Override
- public void onNothingSelected() {
- }
- });
- }
- }
-
- @Override
- public int getItemCount() {
- return reports.size() + 1;
- }
-
- public class ReportViewHolder extends RecyclerView.ViewHolder {
- @Bind(R.id.amount)
- TextView amountText;
- @Bind(R.id.title)
- TextView titleText;
- @Bind(R.id.progressLayout)
- ProgressLayout progressLayout;
- @Bind(R.id.icon)
- PrintView icon;
- @Bind(R.id.reportContainer)
- View reportContainer;
-
- public ReportViewHolder(View v) {
- super(v);
- ButterKnife.bind(this, v);
- }
- }
-
- public class ChartViewHolder extends RecyclerView.ViewHolder {
- @Bind(R.id.chart)
- PieChart chart;
-
- public ChartViewHolder(View v) {
- super(v);
- ButterKnife.bind(this, v);
- chart.getLegend().setEnabled(false);
- chart.setUsePercentValues(false);
- chart.setDescription("");
- chart.setDragDecelerationFrictionCoef(0.95f);
- chart.setDrawHoleEnabled(true);
- chart.setHoleColorTransparent(true);
- chart.setTransparentCircleColor(Color.WHITE);
- chart.setTransparentCircleAlpha(110);
- chart.setHoleRadius(35f);
- chart.setTransparentCircleRadius(38f);
- chart.setRotationEnabled(false);
- chart.setCenterTextColor(Color.WHITE);
- chart.setCenterTextSize(16);
- }
- }
-
- private void updateChartData(List reports, PieChart chart) {
- ArrayList entries = new ArrayList<>();
- ArrayList labels = new ArrayList<>();
- ArrayList colors = new ArrayList<>();
-
- double sum = 0;
- for (int i = 0; i < reports.size(); i++) {
- Report r = reports.get(i);
- sum += r.getAmount();
- entries.add(new Entry((int) (r.getAmount() * 1000), i));
- labels.add(r.getTitle());
- colors.add(r.getColor());
- }
-
- PieDataSet dataSet = new PieDataSet(entries, "Outlay");
- dataSet.setSliceSpace(2f);
- dataSet.setSelectionShift(10f);
- dataSet.setColors(colors);
-
- PieData data = new PieData(labels, dataSet);
- data.setValueFormatter((value, entry, dataSetIndex, viewPortHandler) -> FormatUtils.formatAmount((double)value / 1000));
- data.setValueTextSize(11f);
- data.setValueTextColor(Color.WHITE);
- chart.setData(data);
- chart.setCenterText(FormatUtils.formatAmount(sum));
- chart.highlightValues(null);
- chart.invalidate();
- }
-
- private double getMaxProgress() {
- double max = -1;
- for (Report r : reports) {
- if (max < r.getAmount()) {
- max = r.getAmount();
- }
- }
- return max;
- }
-
- public interface ItemClickListener {
- void onItemClicked(Report report);
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/api/OutlayDatabaseApi.java b/outlay/app/src/main/java/com/outlay/api/OutlayDatabaseApi.java
deleted file mode 100644
index bf86cb2..0000000
--- a/outlay/app/src/main/java/com/outlay/api/OutlayDatabaseApi.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package com.outlay.api;
-
-import android.database.Cursor;
-
-import com.outlay.dao.Category;
-import com.outlay.dao.CategoryDao;
-import com.outlay.dao.DaoSession;
-import com.outlay.dao.Expense;
-import com.outlay.dao.ExpenseDao;
-import com.outlay.model.Summary;
-import com.outlay.utils.DateUtils;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import de.greenrobot.dao.query.DeleteQuery;
-import rx.Observable;
-
-/**
- * Created by Bogdan Melnychuk on 1/21/16.
- */
-public class OutlayDatabaseApi {
- private CategoryDao categoryDao;
- private ExpenseDao expenseDao;
- private DaoSession daoSession;
-
- @Inject
- public OutlayDatabaseApi(CategoryDao categoryDao, ExpenseDao expenseDao, DaoSession session) {
- this.categoryDao = categoryDao;
- this.expenseDao = expenseDao;
- this.daoSession = session;
- }
-
-
- public Observable> getCategories() {
- return Observable.defer(() -> Observable.just(categoryDao.queryBuilder().orderAsc(CategoryDao.Properties.Order).list()));
- }
-
- public Category getCategoryById(Long id) {
- return categoryDao.load(id);
- }
-
- public void updateCategory(Category category) {
- categoryDao.update(category);
- }
-
- public long insertCategory(Category category) {
- return categoryDao.insert(category);
- }
-
- public void deleteCategory(Category category) {
- categoryDao.delete(category);
- }
-
- public Expense getExpenseById(Long id) {
- return expenseDao.load(id);
- }
-
-
- public void updateExpense(Expense e) {
- expenseDao.update(e);
- }
-
- public void deleteExpense(Expense e) {
- expenseDao.delete(e);
- }
-
- public long insertExpense(Expense expense) {
- return expenseDao.insert(expense);
- }
-
- public void deleteExpensesByCategory(Category category) {
- DeleteQuery deleteQuery = expenseDao.queryBuilder().where(ExpenseDao.Properties.CategoryId.ge(category.getId())).buildDelete();
- deleteQuery.executeDeleteWithoutDetachingEntities();
- }
-
- public Observable> insertCategories(List categories) {
- return Observable.create(subscriber -> {
- categoryDao.insertInTx(categories);
- subscriber.onNext(categories);
- subscriber.onCompleted();
- });
- }
-
- public Observable> updateCategories(List categories) {
- return Observable.create(subscriber -> {
- categoryDao.updateInTx(categories);
- subscriber.onNext(categories);
- subscriber.onCompleted();
- });
- }
-
- public Observable> getExpenses(Date startDate, Date endDate) {
- return getExpenses(startDate, endDate, null);
- }
-
- public Observable> getExpenses(Date startDate, Date endDate, Long categoryId) {
- if (categoryId == null) {
- return Observable.defer(() -> Observable.just(
- expenseDao.queryBuilder().where(
- ExpenseDao.Properties.ReportedAt.ge(startDate),
- ExpenseDao.Properties.ReportedAt.le(endDate)
- ).list())
- );
- } else {
- return Observable.defer(() -> Observable.just(
- expenseDao.queryBuilder().where(
- ExpenseDao.Properties.ReportedAt.ge(startDate),
- ExpenseDao.Properties.ReportedAt.le(endDate),
- ExpenseDao.Properties.CategoryId.eq(categoryId)
- ).list())
- );
- }
- }
-
- public Observable getSummary(Date date) {
- return Observable.create(subscriber -> {
- Summary summary = new Summary();
- summary.setMonthAmount(getSumForPeriod(DateUtils.getMonthStart(date), DateUtils.getMonthEnd(date)));
- summary.setWeekAmount(getSumForPeriod(DateUtils.getWeekStart(date), DateUtils.getWeekEnd(date)));
- summary.setDayAmount(getSumForPeriod(DateUtils.getDayStart(date), DateUtils.getDayEnd(date)));
- summary.setDate(date);
- summary.setCategories(getMostPayedCategories(DateUtils.getDayStart(date), DateUtils.getDayEnd(date)));
- subscriber.onNext(summary);
- subscriber.onCompleted();
- });
- }
-
- private double getSumForPeriod(Date dateFrom, Date dateTo) {
- Cursor cursor = daoSession.getDatabase().rawQuery(
- "SELECT SUM(AMOUNT) FROM EXPENSE WHERE REPORTED_AT >= ? AND REPORTED_AT <= ?",
- new String[]{String.valueOf(dateFrom.getTime()), String.valueOf(dateTo.getTime())});
- if (cursor.moveToFirst()) {
- return cursor.getDouble(0);
- }
- return 0;
- }
-
- private List getMostPayedCategories(Date dateFrom, Date dateTo) {
- List result = new ArrayList<>();
- String query = "SELECT * FROM CATEGORY INNER JOIN EXPENSE ON CATEGORY._id = EXPENSE.CATEGORY_ID " +
- "WHERE EXPENSE.REPORTED_AT >= ? AND EXPENSE.REPORTED_AT <= ? " +
- "GROUP BY CATEGORY._id ORDER BY AMOUNT DESC LIMIT 4";
-
- Cursor cursor = daoSession.getDatabase().rawQuery(
- query,
- new String[]{String.valueOf(dateFrom.getTime()), String.valueOf(dateTo.getTime())});
-
- if (cursor != null && cursor.getCount() > 0) {
- cursor.moveToFirst();
- do {
- result.add(categoryDao.readEntity(cursor, 0));
- } while (cursor.moveToNext());
- }
-
- return result;
- }
-
-}
diff --git a/outlay/app/src/main/java/com/outlay/dao/Category.java b/outlay/app/src/main/java/com/outlay/dao/Category.java
deleted file mode 100644
index f2c6035..0000000
--- a/outlay/app/src/main/java/com/outlay/dao/Category.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package com.outlay.dao;
-
-import java.util.List;
-import com.outlay.dao.DaoSession;
-import de.greenrobot.dao.DaoException;
-
-// THIS CODE IS GENERATED BY greenDAO, EDIT ONLY INSIDE THE "KEEP"-SECTIONS
-
-// KEEP INCLUDES - put your custom includes here
-// KEEP INCLUDES END
-/**
- * Entity mapped to table "CATEGORY".
- */
-public class Category {
-
- private Long id;
- /** Not-null value. */
- private String title;
- /** Not-null value. */
- private String icon;
- private int order;
- private int color;
-
- /** Used to resolve relations */
- private transient DaoSession daoSession;
-
- /** Used for active entity operations. */
- private transient CategoryDao myDao;
-
- private List expenseList;
-
- // KEEP FIELDS - put your custom fields here
- // KEEP FIELDS END
-
- public Category() {
- }
-
- public Category(Long id) {
- this.id = id;
- }
-
- public Category(Long id, String title, String icon, int order, int color) {
- this.id = id;
- this.title = title;
- this.icon = icon;
- this.order = order;
- this.color = color;
- }
-
- /** called by internal mechanisms, do not call yourself. */
- public void __setDaoSession(DaoSession daoSession) {
- this.daoSession = daoSession;
- myDao = daoSession != null ? daoSession.getCategoryDao() : null;
- }
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- /** Not-null value. */
- public String getTitle() {
- return title;
- }
-
- /** Not-null value; ensure this value is available before it is saved to the database. */
- public void setTitle(String title) {
- this.title = title;
- }
-
- /** Not-null value. */
- public String getIcon() {
- return icon;
- }
-
- /** Not-null value; ensure this value is available before it is saved to the database. */
- public void setIcon(String icon) {
- this.icon = icon;
- }
-
- public int getOrder() {
- return order;
- }
-
- public void setOrder(int order) {
- this.order = order;
- }
-
- public int getColor() {
- return color;
- }
-
- public void setColor(int color) {
- this.color = color;
- }
-
- /** To-many relationship, resolved on first access (and after reset). Changes to to-many relations are not persisted, make changes to the target entity. */
- public List getExpenseList() {
- if (expenseList == null) {
- if (daoSession == null) {
- throw new DaoException("Entity is detached from DAO context");
- }
- ExpenseDao targetDao = daoSession.getExpenseDao();
- List expenseListNew = targetDao._queryCategory_ExpenseList(id);
- synchronized (this) {
- if(expenseList == null) {
- expenseList = expenseListNew;
- }
- }
- }
- return expenseList;
- }
-
- /** Resets a to-many relationship, making the next get call to query for a fresh result. */
- public synchronized void resetExpenseList() {
- expenseList = null;
- }
-
- /** Convenient call for {@link AbstractDao#delete(Object)}. Entity must attached to an entity context. */
- public void delete() {
- if (myDao == null) {
- throw new DaoException("Entity is detached from DAO context");
- }
- myDao.delete(this);
- }
-
- /** Convenient call for {@link AbstractDao#update(Object)}. Entity must attached to an entity context. */
- public void update() {
- if (myDao == null) {
- throw new DaoException("Entity is detached from DAO context");
- }
- myDao.update(this);
- }
-
- /** Convenient call for {@link AbstractDao#refresh(Object)}. Entity must attached to an entity context. */
- public void refresh() {
- if (myDao == null) {
- throw new DaoException("Entity is detached from DAO context");
- }
- myDao.refresh(this);
- }
-
- // KEEP METHODS - put your custom methods here
- // KEEP METHODS END
-
-}
diff --git a/outlay/app/src/main/java/com/outlay/dao/CategoryDao.java b/outlay/app/src/main/java/com/outlay/dao/CategoryDao.java
deleted file mode 100644
index 6eb176d..0000000
--- a/outlay/app/src/main/java/com/outlay/dao/CategoryDao.java
+++ /dev/null
@@ -1,135 +0,0 @@
-package com.outlay.dao;
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteStatement;
-
-import de.greenrobot.dao.AbstractDao;
-import de.greenrobot.dao.Property;
-import de.greenrobot.dao.internal.DaoConfig;
-
-import com.outlay.dao.Category;
-
-// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
-/**
- * DAO for table "CATEGORY".
-*/
-public class CategoryDao extends AbstractDao {
-
- public static final String TABLENAME = "CATEGORY";
-
- /**
- * Properties of entity Category.
- * Can be used for QueryBuilder and for referencing column names.
- */
- public static class Properties {
- public final static Property Id = new Property(0, Long.class, "id", true, "_id");
- public final static Property Title = new Property(1, String.class, "title", false, "TITLE");
- public final static Property Icon = new Property(2, String.class, "icon", false, "ICON");
- public final static Property Order = new Property(3, int.class, "order", false, "ORDER");
- public final static Property Color = new Property(4, int.class, "color", false, "COLOR");
- };
-
- private DaoSession daoSession;
-
-
- public CategoryDao(DaoConfig config) {
- super(config);
- }
-
- public CategoryDao(DaoConfig config, DaoSession daoSession) {
- super(config, daoSession);
- this.daoSession = daoSession;
- }
-
- /** Creates the underlying database table. */
- public static void createTable(SQLiteDatabase db, boolean ifNotExists) {
- String constraint = ifNotExists? "IF NOT EXISTS ": "";
- db.execSQL("CREATE TABLE " + constraint + "\"CATEGORY\" (" + //
- "\"_id\" INTEGER PRIMARY KEY ," + // 0: id
- "\"TITLE\" TEXT NOT NULL ," + // 1: title
- "\"ICON\" TEXT NOT NULL ," + // 2: icon
- "\"ORDER\" INTEGER NOT NULL ," + // 3: order
- "\"COLOR\" INTEGER NOT NULL );"); // 4: color
- }
-
- /** Drops the underlying database table. */
- public static void dropTable(SQLiteDatabase db, boolean ifExists) {
- String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"CATEGORY\"";
- db.execSQL(sql);
- }
-
- /** @inheritdoc */
- @Override
- protected void bindValues(SQLiteStatement stmt, Category entity) {
- stmt.clearBindings();
-
- Long id = entity.getId();
- if (id != null) {
- stmt.bindLong(1, id);
- }
- stmt.bindString(2, entity.getTitle());
- stmt.bindString(3, entity.getIcon());
- stmt.bindLong(4, entity.getOrder());
- stmt.bindLong(5, entity.getColor());
- }
-
- @Override
- protected void attachEntity(Category entity) {
- super.attachEntity(entity);
- entity.__setDaoSession(daoSession);
- }
-
- /** @inheritdoc */
- @Override
- public Long readKey(Cursor cursor, int offset) {
- return cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0);
- }
-
- /** @inheritdoc */
- @Override
- public Category readEntity(Cursor cursor, int offset) {
- Category entity = new Category( //
- cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
- cursor.getString(offset + 1), // title
- cursor.getString(offset + 2), // icon
- cursor.getInt(offset + 3), // order
- cursor.getInt(offset + 4) // color
- );
- return entity;
- }
-
- /** @inheritdoc */
- @Override
- public void readEntity(Cursor cursor, Category entity, int offset) {
- entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));
- entity.setTitle(cursor.getString(offset + 1));
- entity.setIcon(cursor.getString(offset + 2));
- entity.setOrder(cursor.getInt(offset + 3));
- entity.setColor(cursor.getInt(offset + 4));
- }
-
- /** @inheritdoc */
- @Override
- protected Long updateKeyAfterInsert(Category entity, long rowId) {
- entity.setId(rowId);
- return rowId;
- }
-
- /** @inheritdoc */
- @Override
- public Long getKey(Category entity) {
- if(entity != null) {
- return entity.getId();
- } else {
- return null;
- }
- }
-
- /** @inheritdoc */
- @Override
- protected boolean isEntityUpdateable() {
- return true;
- }
-
-}
diff --git a/outlay/app/src/main/java/com/outlay/dao/DaoMaster.java b/outlay/app/src/main/java/com/outlay/dao/DaoMaster.java
deleted file mode 100644
index 081e5da..0000000
--- a/outlay/app/src/main/java/com/outlay/dao/DaoMaster.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.outlay.dao;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-import de.greenrobot.dao.AbstractDaoMaster;
-import de.greenrobot.dao.identityscope.IdentityScopeType;
-
-import com.outlay.dao.CategoryDao;
-import com.outlay.dao.ExpenseDao;
-
-// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
-/**
- * Master of DAO (schema version 1): knows all DAOs.
-*/
-public class DaoMaster extends AbstractDaoMaster {
- public static final int SCHEMA_VERSION = 1;
-
- /** Creates underlying database table using DAOs. */
- public static void createAllTables(SQLiteDatabase db, boolean ifNotExists) {
- CategoryDao.createTable(db, ifNotExists);
- ExpenseDao.createTable(db, ifNotExists);
- }
-
- /** Drops underlying database table using DAOs. */
- public static void dropAllTables(SQLiteDatabase db, boolean ifExists) {
- CategoryDao.dropTable(db, ifExists);
- ExpenseDao.dropTable(db, ifExists);
- }
-
- public static abstract class OpenHelper extends SQLiteOpenHelper {
-
- public OpenHelper(Context context, String name, CursorFactory factory) {
- super(context, name, factory, SCHEMA_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
- createAllTables(db, false);
- }
- }
-
- /** WARNING: Drops all table on Upgrade! Use only during development. */
- public static class DevOpenHelper extends OpenHelper {
- public DevOpenHelper(Context context, String name, CursorFactory factory) {
- super(context, name, factory);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
- dropAllTables(db, true);
- onCreate(db);
- }
- }
-
- public DaoMaster(SQLiteDatabase db) {
- super(db, SCHEMA_VERSION);
- registerDaoClass(CategoryDao.class);
- registerDaoClass(ExpenseDao.class);
- }
-
- public DaoSession newSession() {
- return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
- }
-
- public DaoSession newSession(IdentityScopeType type) {
- return new DaoSession(db, type, daoConfigMap);
- }
-
-}
diff --git a/outlay/app/src/main/java/com/outlay/dao/DaoSession.java b/outlay/app/src/main/java/com/outlay/dao/DaoSession.java
deleted file mode 100644
index 41afb6a..0000000
--- a/outlay/app/src/main/java/com/outlay/dao/DaoSession.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.outlay.dao;
-
-import android.database.sqlite.SQLiteDatabase;
-
-import java.util.Map;
-
-import de.greenrobot.dao.AbstractDao;
-import de.greenrobot.dao.AbstractDaoSession;
-import de.greenrobot.dao.identityscope.IdentityScopeType;
-import de.greenrobot.dao.internal.DaoConfig;
-
-import com.outlay.dao.Category;
-import com.outlay.dao.Expense;
-
-import com.outlay.dao.CategoryDao;
-import com.outlay.dao.ExpenseDao;
-
-// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
-
-/**
- * {@inheritDoc}
- *
- * @see de.greenrobot.dao.AbstractDaoSession
- */
-public class DaoSession extends AbstractDaoSession {
-
- private final DaoConfig categoryDaoConfig;
- private final DaoConfig expenseDaoConfig;
-
- private final CategoryDao categoryDao;
- private final ExpenseDao expenseDao;
-
- public DaoSession(SQLiteDatabase db, IdentityScopeType type, Map>, DaoConfig>
- daoConfigMap) {
- super(db);
-
- categoryDaoConfig = daoConfigMap.get(CategoryDao.class).clone();
- categoryDaoConfig.initIdentityScope(type);
-
- expenseDaoConfig = daoConfigMap.get(ExpenseDao.class).clone();
- expenseDaoConfig.initIdentityScope(type);
-
- categoryDao = new CategoryDao(categoryDaoConfig, this);
- expenseDao = new ExpenseDao(expenseDaoConfig, this);
-
- registerDao(Category.class, categoryDao);
- registerDao(Expense.class, expenseDao);
- }
-
- public void clear() {
- categoryDaoConfig.getIdentityScope().clear();
- expenseDaoConfig.getIdentityScope().clear();
- }
-
- public CategoryDao getCategoryDao() {
- return categoryDao;
- }
-
- public ExpenseDao getExpenseDao() {
- return expenseDao;
- }
-
-}
diff --git a/outlay/app/src/main/java/com/outlay/dao/Expense.java b/outlay/app/src/main/java/com/outlay/dao/Expense.java
deleted file mode 100644
index 939975e..0000000
--- a/outlay/app/src/main/java/com/outlay/dao/Expense.java
+++ /dev/null
@@ -1,147 +0,0 @@
-package com.outlay.dao;
-
-import com.outlay.dao.DaoSession;
-import de.greenrobot.dao.DaoException;
-
-// THIS CODE IS GENERATED BY greenDAO, EDIT ONLY INSIDE THE "KEEP"-SECTIONS
-
-// KEEP INCLUDES - put your custom includes here
-// KEEP INCLUDES END
-/**
- * Entity mapped to table "EXPENSE".
- */
-public class Expense {
-
- private Long id;
- private String note;
- private Double amount;
- private java.util.Date reportedAt;
- private Long categoryId;
-
- /** Used to resolve relations */
- private transient DaoSession daoSession;
-
- /** Used for active entity operations. */
- private transient ExpenseDao myDao;
-
- private Category category;
- private Long category__resolvedKey;
-
-
- // KEEP FIELDS - put your custom fields here
- // KEEP FIELDS END
-
- public Expense() {
- }
-
- public Expense(Long id) {
- this.id = id;
- }
-
- public Expense(Long id, String note, Double amount, java.util.Date reportedAt, Long categoryId) {
- this.id = id;
- this.note = note;
- this.amount = amount;
- this.reportedAt = reportedAt;
- this.categoryId = categoryId;
- }
-
- /** called by internal mechanisms, do not call yourself. */
- public void __setDaoSession(DaoSession daoSession) {
- this.daoSession = daoSession;
- myDao = daoSession != null ? daoSession.getExpenseDao() : null;
- }
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getNote() {
- return note;
- }
-
- public void setNote(String note) {
- this.note = note;
- }
-
- public Double getAmount() {
- return amount;
- }
-
- public void setAmount(Double amount) {
- this.amount = amount;
- }
-
- public java.util.Date getReportedAt() {
- return reportedAt;
- }
-
- public void setReportedAt(java.util.Date reportedAt) {
- this.reportedAt = reportedAt;
- }
-
- public Long getCategoryId() {
- return categoryId;
- }
-
- public void setCategoryId(Long categoryId) {
- this.categoryId = categoryId;
- }
-
- /** To-one relationship, resolved on first access. */
- public Category getCategory() {
- Long __key = this.categoryId;
- if (category__resolvedKey == null || !category__resolvedKey.equals(__key)) {
- if (daoSession == null) {
- throw new DaoException("Entity is detached from DAO context");
- }
- CategoryDao targetDao = daoSession.getCategoryDao();
- Category categoryNew = targetDao.load(__key);
- synchronized (this) {
- category = categoryNew;
- category__resolvedKey = __key;
- }
- }
- return category;
- }
-
- public void setCategory(Category category) {
- synchronized (this) {
- this.category = category;
- categoryId = category == null ? null : category.getId();
- category__resolvedKey = categoryId;
- }
- }
-
- /** Convenient call for {@link AbstractDao#delete(Object)}. Entity must attached to an entity context. */
- public void delete() {
- if (myDao == null) {
- throw new DaoException("Entity is detached from DAO context");
- }
- myDao.delete(this);
- }
-
- /** Convenient call for {@link AbstractDao#update(Object)}. Entity must attached to an entity context. */
- public void update() {
- if (myDao == null) {
- throw new DaoException("Entity is detached from DAO context");
- }
- myDao.update(this);
- }
-
- /** Convenient call for {@link AbstractDao#refresh(Object)}. Entity must attached to an entity context. */
- public void refresh() {
- if (myDao == null) {
- throw new DaoException("Entity is detached from DAO context");
- }
- myDao.refresh(this);
- }
-
- // KEEP METHODS - put your custom methods here
- // KEEP METHODS END
-
-}
diff --git a/outlay/app/src/main/java/com/outlay/dao/ExpenseDao.java b/outlay/app/src/main/java/com/outlay/dao/ExpenseDao.java
deleted file mode 100644
index 6642c3e..0000000
--- a/outlay/app/src/main/java/com/outlay/dao/ExpenseDao.java
+++ /dev/null
@@ -1,262 +0,0 @@
-package com.outlay.dao;
-
-import java.util.List;
-import java.util.ArrayList;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteStatement;
-
-import de.greenrobot.dao.AbstractDao;
-import de.greenrobot.dao.Property;
-import de.greenrobot.dao.internal.SqlUtils;
-import de.greenrobot.dao.internal.DaoConfig;
-import de.greenrobot.dao.query.Query;
-import de.greenrobot.dao.query.QueryBuilder;
-
-import com.outlay.dao.Expense;
-
-// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
-/**
- * DAO for table "EXPENSE".
-*/
-public class ExpenseDao extends AbstractDao {
-
- public static final String TABLENAME = "EXPENSE";
-
- /**
- * Properties of entity Expense.
- * Can be used for QueryBuilder and for referencing column names.
- */
- public static class Properties {
- public final static Property Id = new Property(0, Long.class, "id", true, "_id");
- public final static Property Note = new Property(1, String.class, "note", false, "NOTE");
- public final static Property Amount = new Property(2, Double.class, "amount", false, "AMOUNT");
- public final static Property ReportedAt = new Property(3, java.util.Date.class, "reportedAt", false, "REPORTED_AT");
- public final static Property CategoryId = new Property(4, Long.class, "categoryId", false, "CATEGORY_ID");
- };
-
- private DaoSession daoSession;
-
- private Query category_ExpenseListQuery;
-
- public ExpenseDao(DaoConfig config) {
- super(config);
- }
-
- public ExpenseDao(DaoConfig config, DaoSession daoSession) {
- super(config, daoSession);
- this.daoSession = daoSession;
- }
-
- /** Creates the underlying database table. */
- public static void createTable(SQLiteDatabase db, boolean ifNotExists) {
- String constraint = ifNotExists? "IF NOT EXISTS ": "";
- db.execSQL("CREATE TABLE " + constraint + "\"EXPENSE\" (" + //
- "\"_id\" INTEGER PRIMARY KEY ," + // 0: id
- "\"NOTE\" TEXT," + // 1: note
- "\"AMOUNT\" REAL," + // 2: amount
- "\"REPORTED_AT\" INTEGER," + // 3: reportedAt
- "\"CATEGORY_ID\" INTEGER);"); // 4: categoryId
- }
-
- /** Drops the underlying database table. */
- public static void dropTable(SQLiteDatabase db, boolean ifExists) {
- String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"EXPENSE\"";
- db.execSQL(sql);
- }
-
- /** @inheritdoc */
- @Override
- protected void bindValues(SQLiteStatement stmt, Expense entity) {
- stmt.clearBindings();
-
- Long id = entity.getId();
- if (id != null) {
- stmt.bindLong(1, id);
- }
-
- String note = entity.getNote();
- if (note != null) {
- stmt.bindString(2, note);
- }
-
- Double amount = entity.getAmount();
- if (amount != null) {
- stmt.bindDouble(3, amount);
- }
-
- java.util.Date reportedAt = entity.getReportedAt();
- if (reportedAt != null) {
- stmt.bindLong(4, reportedAt.getTime());
- }
-
- Long categoryId = entity.getCategoryId();
- if (categoryId != null) {
- stmt.bindLong(5, categoryId);
- }
- }
-
- @Override
- protected void attachEntity(Expense entity) {
- super.attachEntity(entity);
- entity.__setDaoSession(daoSession);
- }
-
- /** @inheritdoc */
- @Override
- public Long readKey(Cursor cursor, int offset) {
- return cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0);
- }
-
- /** @inheritdoc */
- @Override
- public Expense readEntity(Cursor cursor, int offset) {
- Expense entity = new Expense( //
- cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
- cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1), // note
- cursor.isNull(offset + 2) ? null : cursor.getDouble(offset + 2), // amount
- cursor.isNull(offset + 3) ? null : new java.util.Date(cursor.getLong(offset + 3)), // reportedAt
- cursor.isNull(offset + 4) ? null : cursor.getLong(offset + 4) // categoryId
- );
- return entity;
- }
-
- /** @inheritdoc */
- @Override
- public void readEntity(Cursor cursor, Expense entity, int offset) {
- entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));
- entity.setNote(cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1));
- entity.setAmount(cursor.isNull(offset + 2) ? null : cursor.getDouble(offset + 2));
- entity.setReportedAt(cursor.isNull(offset + 3) ? null : new java.util.Date(cursor.getLong(offset + 3)));
- entity.setCategoryId(cursor.isNull(offset + 4) ? null : cursor.getLong(offset + 4));
- }
-
- /** @inheritdoc */
- @Override
- protected Long updateKeyAfterInsert(Expense entity, long rowId) {
- entity.setId(rowId);
- return rowId;
- }
-
- /** @inheritdoc */
- @Override
- public Long getKey(Expense entity) {
- if(entity != null) {
- return entity.getId();
- } else {
- return null;
- }
- }
-
- /** @inheritdoc */
- @Override
- protected boolean isEntityUpdateable() {
- return true;
- }
-
- /** Internal query to resolve the "expenseList" to-many relationship of Category. */
- public List _queryCategory_ExpenseList(Long categoryId) {
- synchronized (this) {
- if (category_ExpenseListQuery == null) {
- QueryBuilder queryBuilder = queryBuilder();
- queryBuilder.where(Properties.CategoryId.eq(null));
- category_ExpenseListQuery = queryBuilder.build();
- }
- }
- Query query = category_ExpenseListQuery.forCurrentThread();
- query.setParameter(0, categoryId);
- return query.list();
- }
-
- private String selectDeep;
-
- protected String getSelectDeep() {
- if (selectDeep == null) {
- StringBuilder builder = new StringBuilder("SELECT ");
- SqlUtils.appendColumns(builder, "T", getAllColumns());
- builder.append(',');
- SqlUtils.appendColumns(builder, "T0", daoSession.getCategoryDao().getAllColumns());
- builder.append(" FROM EXPENSE T");
- builder.append(" LEFT JOIN CATEGORY T0 ON T.\"CATEGORY_ID\"=T0.\"_id\"");
- builder.append(' ');
- selectDeep = builder.toString();
- }
- return selectDeep;
- }
-
- protected Expense loadCurrentDeep(Cursor cursor, boolean lock) {
- Expense entity = loadCurrent(cursor, 0, lock);
- int offset = getAllColumns().length;
-
- Category category = loadCurrentOther(daoSession.getCategoryDao(), cursor, offset);
- entity.setCategory(category);
-
- return entity;
- }
-
- public Expense loadDeep(Long key) {
- assertSinglePk();
- if (key == null) {
- return null;
- }
-
- StringBuilder builder = new StringBuilder(getSelectDeep());
- builder.append("WHERE ");
- SqlUtils.appendColumnsEqValue(builder, "T", getPkColumns());
- String sql = builder.toString();
-
- String[] keyArray = new String[] { key.toString() };
- Cursor cursor = db.rawQuery(sql, keyArray);
-
- try {
- boolean available = cursor.moveToFirst();
- if (!available) {
- return null;
- } else if (!cursor.isLast()) {
- throw new IllegalStateException("Expected unique result, but count was " + cursor.getCount());
- }
- return loadCurrentDeep(cursor, true);
- } finally {
- cursor.close();
- }
- }
-
- /** Reads all available rows from the given cursor and returns a list of new ImageTO objects. */
- public List loadAllDeepFromCursor(Cursor cursor) {
- int count = cursor.getCount();
- List list = new ArrayList(count);
-
- if (cursor.moveToFirst()) {
- if (identityScope != null) {
- identityScope.lock();
- identityScope.reserveRoom(count);
- }
- try {
- do {
- list.add(loadCurrentDeep(cursor, false));
- } while (cursor.moveToNext());
- } finally {
- if (identityScope != null) {
- identityScope.unlock();
- }
- }
- }
- return list;
- }
-
- protected List loadDeepAllAndCloseCursor(Cursor cursor) {
- try {
- return loadAllDeepFromCursor(cursor);
- } finally {
- cursor.close();
- }
- }
-
-
- /** A raw-style query where you can pass any WHERE clause and arguments. */
- public List queryDeep(String where, String... selectionArg) {
- Cursor cursor = db.rawQuery(getSelectDeep() + where, selectionArg);
- return loadDeepAllAndCloseCursor(cursor);
- }
-
-}
diff --git a/outlay/app/src/main/java/com/outlay/di/AppComponent.java b/outlay/app/src/main/java/com/outlay/di/AppComponent.java
deleted file mode 100644
index b5aea2b..0000000
--- a/outlay/app/src/main/java/com/outlay/di/AppComponent.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.outlay.di;
-
-import android.app.Application;
-
-import com.outlay.di.module.AppModule;
-import com.outlay.di.module.DaoModule;
-import com.outlay.view.activity.MainActivity;
-import com.outlay.view.fragment.CategoryDetailsFragment;
-import com.outlay.view.fragment.CategoriesFragment;
-import com.outlay.view.fragment.ExpensesDetailsFragment;
-import com.outlay.view.fragment.ExpensesListFragment;
-import com.outlay.view.fragment.MainFragment;
-import com.outlay.view.fragment.ReportFragment;
-
-import javax.inject.Singleton;
-
-import dagger.Component;
-
-/**
- * Created by Bogdan Melnychuk on 12/17/15.
- */
-@Singleton
-@Component(modules = {AppModule.class, DaoModule.class})
-public interface AppComponent {
- void inject(MainActivity activity);
- void inject(CategoriesFragment fragment);
- void inject(CategoryDetailsFragment fragment);
- void inject(MainFragment fragment);
- void inject(ReportFragment fragment);
- void inject(ExpensesListFragment fragment);
- void inject(ExpensesDetailsFragment fragment);
-
- Application getApplication();
-}
diff --git a/outlay/app/src/main/java/com/outlay/di/module/AppModule.java b/outlay/app/src/main/java/com/outlay/di/module/AppModule.java
deleted file mode 100644
index 0ecc767..0000000
--- a/outlay/app/src/main/java/com/outlay/di/module/AppModule.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.outlay.di.module;
-
-import android.app.Application;
-
-import com.outlay.App;
-import com.outlay.preferences.PreferencesManager;
-
-import javax.inject.Singleton;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Created by Bogdan Melnychuk on 12/17/15.
- */
-@Module
-public class AppModule {
- private final App mApplication;
-
- public AppModule(App mApplication) {
- this.mApplication = mApplication;
- }
-
- @Provides
- @Singleton
- Application provideAppContext() {
- return mApplication;
- }
-
- @Provides
- @Singleton
- PreferencesManager providePreferencesManager() {
- return new PreferencesManager(mApplication);
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/di/module/DaoModule.java b/outlay/app/src/main/java/com/outlay/di/module/DaoModule.java
deleted file mode 100644
index 169012b..0000000
--- a/outlay/app/src/main/java/com/outlay/di/module/DaoModule.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.outlay.di.module;
-
-import android.app.Application;
-import android.database.sqlite.SQLiteDatabase;
-
-import com.outlay.dao.CategoryDao;
-import com.outlay.dao.DaoMaster;
-import com.outlay.dao.DaoSession;
-import com.outlay.dao.ExpenseDao;
-
-import javax.inject.Singleton;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Created by Bogdan Melnychuk on 12/17/15.
- */
-@Module
-public class DaoModule {
- private static final String DATABASE_NAME = "outlay_db";
-
- @Provides
- @Singleton
- public DaoMaster.DevOpenHelper provideDatabaseHelper(Application application) {
- return new DaoMaster.DevOpenHelper(application, DATABASE_NAME, null);
- }
-
- @Provides
- @Singleton
- public SQLiteDatabase provideDatabase(DaoMaster.DevOpenHelper helper) {
- return helper.getWritableDatabase();
- }
-
- @Provides
- @Singleton
- public DaoMaster provideDaoMaster(SQLiteDatabase database) {
- return new DaoMaster(database);
- }
-
- @Provides
- @Singleton
- public DaoSession provideDaoSession(DaoMaster daoMaster) {
- return daoMaster.newSession();
- }
-
- @Provides
- @Singleton
- public CategoryDao provideCategoryDao(DaoSession session) {
- return session.getCategoryDao();
- }
-
- @Provides
- @Singleton
- public ExpenseDao provideExpenseDaoDao(DaoSession session) {
- return session.getExpenseDao();
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/model/Icon.java b/outlay/app/src/main/java/com/outlay/model/Icon.java
deleted file mode 100644
index 63c7fde..0000000
--- a/outlay/app/src/main/java/com/outlay/model/Icon.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.outlay.model;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Created by Bogdan Melnychuk on 1/29/16.
- */
-public final class Icon {
- private static final String[] all = {
- "ic_coins",
- "ic_flight",
- "ic_airplane",
- "ic_bakery",
- "ic_beach",
- "ic_basketball",
- "ic_front",
- "ic_flamenco",
- "ic_fast_food",
- "ic_subscribe",
- "ic_t_shirt",
- "ic_md",
- "ic_map",
- "ic_medical",
- "ic_flower",
- "ic_fuel",
- "ic_money",
- "ic_tea",
- "ic_tag",
- "ic_mobilephone",
- "ic_telephone",
- "ic_multiple",
- "ic_hairsalon",
- "ic_books",
- "ic_bowling",
- "ic_hand",
- "ic_note",
- "ic_tool",
- "ic_tools",
- "ic_people",
- "ic_hardbound",
- "ic_box",
- "ic_cars",
- "ic_healthcare",
- "ic_photo",
- "ic_two",
- "ic_weightlifting",
- "ic_poor",
- "ic_heart",
- "ic_chart",
- "ic_christmas",
- "ic_insurance",
- "ic_rent",
- "ic_restaurant",
- "ic_horse",
- "ic_cigarette",
- "ic_cleaning",
- "ic_house",
- "ic_run",
- "ic_scissors",
- "ic_italian_food",
- "ic_justice",
- "ic_climbing",
- "ic_screen",
- "ic_shopping",
- "ic_legal",
- "ic_controller",
- "ic_dog",
- "ic_lifeline",
- "ic_sofa",
- "ic_sportive",
- "ic_locked",
- "ic_donation",
- "ic_draw",
- "ic_luggage",
- "ic_makeup",
- "ic_ducks"
- };
-
- public static List getAll() {
- return Arrays.asList(all);
- }
-}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/com/outlay/model/Report.java b/outlay/app/src/main/java/com/outlay/model/Report.java
deleted file mode 100644
index 0bf6412..0000000
--- a/outlay/app/src/main/java/com/outlay/model/Report.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.outlay.model;
-
-import com.outlay.dao.Category;
-import com.outlay.dao.Expense;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Created by Bogdan Melnychuk on 1/27/16.
- */
-public class Report {
- private List expenses;
- private double amount;
-
- public Report() {
- expenses = new ArrayList<>();
- }
-
- public void addExpense(Expense e) {
- expenses.add(e);
- amount += e.getAmount();
- }
-
- public double getAmount() {
- return amount;
- }
-
- public String getIcon() {
- String icon = null;
- if (expenses != null && !expenses.isEmpty()) {
- icon = expenses.get(0).getCategory().getIcon();
- }
- return icon;
- }
-
- public int getColor() {
- int color = 0;
- if (expenses != null && !expenses.isEmpty()) {
- color = expenses.get(0).getCategory().getColor();
- }
- return color;
- }
-
- public String getTitle() {
- String title = "";
- if (expenses != null && !expenses.isEmpty()) {
- title = expenses.get(0).getCategory().getTitle();
- }
- return title;
- }
-
- public Category getCategory() {
- Category category = null;
- if (expenses != null && !expenses.isEmpty()) {
- category = expenses.get(0).getCategory();
- }
- return category;
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/model/Summary.java b/outlay/app/src/main/java/com/outlay/model/Summary.java
deleted file mode 100644
index 23b7488..0000000
--- a/outlay/app/src/main/java/com/outlay/model/Summary.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.outlay.model;
-
-import com.outlay.dao.Category;
-
-import java.util.Date;
-import java.util.List;
-
-/**
- * Created by Bogdan Melnychuk on 1/30/16.
- */
-public class Summary {
- private Date date;
- private double dayAmount;
- private double weekAmount;
- private double monthAmount;
- private List categories;
-
- public Date getDate() {
- return date;
- }
-
- public void setDate(Date date) {
- this.date = date;
- }
-
- public double getDayAmount() {
- return dayAmount;
- }
-
- public void setDayAmount(double dayAmount) {
- this.dayAmount = dayAmount;
- }
-
- public double getWeekAmount() {
- return weekAmount;
- }
-
- public void setWeekAmount(double weekAmount) {
- this.weekAmount = weekAmount;
- }
-
- public double getMonthAmount() {
- return monthAmount;
- }
-
- public void setMonthAmount(double monthAmount) {
- this.monthAmount = monthAmount;
- }
-
- public List getCategories() {
- return categories;
- }
-
- public void setCategories(List categories) {
- this.categories = categories;
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/presenter/CategoriesPresenter.java b/outlay/app/src/main/java/com/outlay/presenter/CategoriesPresenter.java
deleted file mode 100644
index 3f2b66b..0000000
--- a/outlay/app/src/main/java/com/outlay/presenter/CategoriesPresenter.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.outlay.presenter;
-
-import com.outlay.api.OutlayDatabaseApi;
-import com.outlay.dao.Category;
-import com.outlay.view.fragment.CategoriesFragment;
-
-import java.util.List;
-
-import javax.inject.Inject;
-
-import rx.android.schedulers.AndroidSchedulers;
-import rx.schedulers.Schedulers;
-
-/**
- * Created by Bogdan Melnychuk on 1/21/16.
- */
-public class CategoriesPresenter {
- private OutlayDatabaseApi api;
- private CategoriesFragment view;
-
- @Inject
- public CategoriesPresenter(OutlayDatabaseApi outlayDatabaseApi) {
- this.api = outlayDatabaseApi;
- }
-
- public void attachView(CategoriesFragment fragment) {
- this.view = fragment;
- }
-
- public void loadCategories() {
- api.getCategories()
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> view.displayCategories(response));
- }
-
-
- public void updateCategories(List categories) {
- api.updateCategories(categories)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe();
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/presenter/CategoryDetailsPresenter.java b/outlay/app/src/main/java/com/outlay/presenter/CategoryDetailsPresenter.java
deleted file mode 100644
index bc0a260..0000000
--- a/outlay/app/src/main/java/com/outlay/presenter/CategoryDetailsPresenter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.outlay.presenter;
-
-import com.outlay.api.OutlayDatabaseApi;
-import com.outlay.dao.Category;
-import com.outlay.view.fragment.CategoryDetailsFragment;
-
-import javax.inject.Inject;
-
-/**
- * Created by Bogdan Melnychuk on 1/21/16.
- */
-public class CategoryDetailsPresenter {
- private OutlayDatabaseApi api;
- private CategoryDetailsFragment view;
-
- @Inject
- public CategoryDetailsPresenter(OutlayDatabaseApi outlayDatabaseApi) {
- this.api = outlayDatabaseApi;
- }
-
- public void attachView(CategoryDetailsFragment fragment) {
- this.view = fragment;
- }
-
- public void loadCategory(Long id) {
- view.displayCategory(api.getCategoryById(id));
- }
-
- public void updateCategory(Category category) {
- if (category.getId() == null) {
- category.setOrder(Integer.MAX_VALUE);
- api.insertCategory(category);
- } else {
- api.updateCategory(category);
- }
-
- }
-
- public void deleteCategory(Category category) {
- api.deleteExpensesByCategory(category);
- api.deleteCategory(category);
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/presenter/ExpensesDetailsPresenter.java b/outlay/app/src/main/java/com/outlay/presenter/ExpensesDetailsPresenter.java
deleted file mode 100644
index fecc4a8..0000000
--- a/outlay/app/src/main/java/com/outlay/presenter/ExpensesDetailsPresenter.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.outlay.presenter;
-
-import com.outlay.api.OutlayDatabaseApi;
-import com.outlay.dao.Expense;
-import com.outlay.view.fragment.ExpensesDetailsFragment;
-
-import org.joda.time.DateTime;
-import org.joda.time.LocalTime;
-
-import javax.inject.Inject;
-
-import rx.android.schedulers.AndroidSchedulers;
-import rx.schedulers.Schedulers;
-
-/**
- * Created by Bogdan Melnychuk on 1/21/16.
- */
-public class ExpensesDetailsPresenter {
- private OutlayDatabaseApi api;
- private ExpensesDetailsFragment view;
-
- @Inject
- public ExpensesDetailsPresenter(OutlayDatabaseApi outlayDatabaseApi) {
- this.api = outlayDatabaseApi;
- }
-
- public void attachView(ExpensesDetailsFragment fragment) {
- this.view = fragment;
- }
-
- public void loadExpense(Long expenseId) {
- view.displayExpense(api.getExpenseById(expenseId));
- }
-
- public void loadCategories() {
- api.getCategories()
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> view.setCategories(response));
- }
-
- public void updateExpense(Expense expense) {
- DateTime dateTime = new DateTime(expense.getReportedAt().getTime());
- dateTime = dateTime.withTime(LocalTime.now());
- expense.setReportedAt(dateTime.toDate());
- if (expense.getId() == null) {
- api.insertExpense(expense);
- } else {
- api.updateExpense(expense);
- }
- }
-
- public void deleteExpense(Expense expense) {
- api.deleteExpense(expense);
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/presenter/ExpensesListPresenter.java b/outlay/app/src/main/java/com/outlay/presenter/ExpensesListPresenter.java
deleted file mode 100644
index 98c0ee7..0000000
--- a/outlay/app/src/main/java/com/outlay/presenter/ExpensesListPresenter.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.outlay.presenter;
-
-import com.outlay.api.OutlayDatabaseApi;
-import com.outlay.dao.Category;
-import com.outlay.view.fragment.ExpensesListFragment;
-
-import java.util.Date;
-
-import javax.inject.Inject;
-
-import rx.android.schedulers.AndroidSchedulers;
-import rx.schedulers.Schedulers;
-
-/**
- * Created by Bogdan Melnychuk on 1/21/16.
- */
-public class ExpensesListPresenter {
- private OutlayDatabaseApi api;
- private ExpensesListFragment view;
-
- @Inject
- public ExpensesListPresenter(OutlayDatabaseApi outlayDatabaseApi) {
- this.api = outlayDatabaseApi;
- }
-
- public void attachView(ExpensesListFragment fragment) {
- this.view = fragment;
- }
-
- public void loadExpenses(Date dateFrom, Date dateTo, Long categoryId) {
- api.getExpenses(dateFrom, dateTo, categoryId)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> view.displayExpenses(response));
- }
-
- public Category getCategoryById(Long id) {
- return api.getCategoryById(id);
- }
-
-}
diff --git a/outlay/app/src/main/java/com/outlay/presenter/MainFragmentPresenter.java b/outlay/app/src/main/java/com/outlay/presenter/MainFragmentPresenter.java
deleted file mode 100644
index 278fab9..0000000
--- a/outlay/app/src/main/java/com/outlay/presenter/MainFragmentPresenter.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.outlay.presenter;
-
-import android.content.Context;
-import android.support.v4.content.ContextCompat;
-
-import com.outlay.R;
-import com.outlay.api.OutlayDatabaseApi;
-import com.outlay.dao.Category;
-import com.outlay.dao.Expense;
-import com.outlay.model.Icon;
-import com.outlay.preferences.PreferencesManager;
-import com.outlay.view.fragment.MainFragment;
-
-import org.joda.time.DateTime;
-import org.joda.time.LocalTime;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import rx.android.schedulers.AndroidSchedulers;
-import rx.schedulers.Schedulers;
-
-/**
- * Created by Bogdan Melnychuk on 1/25/16.
- */
-public class MainFragmentPresenter {
- private OutlayDatabaseApi api;
- private MainFragment view;
- private PreferencesManager preferencesManager;
-
- @Inject
- public MainFragmentPresenter(OutlayDatabaseApi outlayDatabaseApi, PreferencesManager preferencesManager) {
- this.preferencesManager = preferencesManager;
- this.api = outlayDatabaseApi;
- }
-
- public void attachView(MainFragment fragment) {
- this.view = fragment;
- }
-
- public void loadCategories() {
- if (preferencesManager.isFirstRun()) {
- List defaultCategories = getDefault(view.getActivity());
- api.insertCategories(defaultCategories)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> {
- preferencesManager.putFirstRun(false);
- view.displayCategories(response);
- });
- } else {
- api.getCategories()
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> view.displayCategories(response));
- }
-
-
- }
-
- public void loadSummary(Date date) {
- api.getSummary(date)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> view.displaySummary(response));
- }
-
-
- public void insertExpense(Expense expense) {
- DateTime dateTime = new DateTime(expense.getReportedAt().getTime());
- dateTime = dateTime.withTime(LocalTime.now());
- expense.setReportedAt(dateTime.toDate());
- api.insertExpense(expense);
- }
-
- public static List getDefault(Context context) {
- List result = new ArrayList<>();
- result.add(category(context.getString(R.string.category_car), "ic_cars", ContextCompat.getColor(context, R.color.blue), 0));
- result.add(category(context.getString(R.string.category_house), "ic_house", ContextCompat.getColor(context, R.color.red), 1));
- result.add(category(context.getString(R.string.category_grocery), "ic_shopping", ContextCompat.getColor(context, R.color.green), 2));
- result.add(category(context.getString(R.string.category_games), "ic_controller", ContextCompat.getColor(context, R.color.purple), 3));
- result.add(category(context.getString(R.string.category_clothes), "ic_t_shirt", ContextCompat.getColor(context, R.color.teal), 4));
- result.add(category(context.getString(R.string.category_tickets), "ic_tag", ContextCompat.getColor(context, R.color.amber), 5));
- result.add(category(context.getString(R.string.category_sport), "ic_weightlifting", ContextCompat.getColor(context, R.color.brown), 6));
- result.add(category(context.getString(R.string.category_travel), "ic_flight", ContextCompat.getColor(context, R.color.cyan), 7));
- return result;
- }
-
- private static Category category(String title, String icon, int color, int order) {
- Category c = new Category();
- c.setTitle(title);
- c.setIcon(icon);
- c.setColor(color);
- c.setOrder(order);
- return c;
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/presenter/ReportPresenter.java b/outlay/app/src/main/java/com/outlay/presenter/ReportPresenter.java
deleted file mode 100644
index 9ef7c9d..0000000
--- a/outlay/app/src/main/java/com/outlay/presenter/ReportPresenter.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package com.outlay.presenter;
-
-import com.outlay.api.OutlayDatabaseApi;
-import com.outlay.dao.Expense;
-import com.outlay.model.Report;
-import com.outlay.utils.DateUtils;
-import com.outlay.view.Page;
-import com.outlay.view.fragment.ReportFragment;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.inject.Inject;
-
-import rx.android.schedulers.AndroidSchedulers;
-import rx.schedulers.Schedulers;
-
-/**
- * Created by Bogdan Melnychuk on 1/21/16.
- */
-public class ReportPresenter {
- private OutlayDatabaseApi api;
- private ReportFragment view;
-
- @Inject
- public ReportPresenter(OutlayDatabaseApi outlayDatabaseApi) {
- this.api = outlayDatabaseApi;
- }
-
- public void attachView(ReportFragment fragment) {
- this.view = fragment;
- }
-
- public void loadReport(Date date, int period) {
- Date startDate = date;
- Date endDate = date;
-
- switch (period) {
- case ReportFragment.PERIOD_DAY:
- startDate = DateUtils.getDayStart(date);
- endDate = DateUtils.getDayEnd(date);
- break;
- case ReportFragment.PERIOD_WEEK:
- startDate = DateUtils.getWeekStart(date);
- endDate = DateUtils.getWeekEnd(date);
- break;
- case ReportFragment.PERIOD_MONTH:
- startDate = DateUtils.getMonthStart(date);
- endDate = DateUtils.getMonthEnd(date);
- break;
- }
-
- api.getExpenses(startDate, endDate)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(expenses -> {
- //TODO Don't like this map here, this logic should be on database side, though I am too lazy now
- Map reportMap = new TreeMap<>((lhs, rhs) -> {
- if (lhs == rhs) {
- return 0;
- }
- if (lhs == null) {
- return -1;
- }
- if (rhs == null) {
- return 1;
- }
- return lhs.compareTo(rhs);
- });
- for (Expense e : expenses) {
- Report r = reportMap.get(e.getCategory().getTitle());
- if (r == null) {
- r = new Report();
- }
- r.addExpense(e);
- reportMap.put(e.getCategory().getTitle(), r);
- }
- view.displayReports(new ArrayList<>(reportMap.values()));
-
- });
- }
-
- public void goToExpensesList(Date date, int selectedPeriod) {
- this.goToExpensesList(date, selectedPeriod, null);
- }
-
- public void goToExpensesList(Date date, int selectedPeriod, Long category) {
- date = DateUtils.fillCurrentTime(date);
- Date startDate = date;
- Date endDate = date;
-
- switch (selectedPeriod) {
- case ReportFragment.PERIOD_DAY:
- startDate = DateUtils.getDayStart(date);
- endDate = DateUtils.getDayEnd(date);
- break;
- case ReportFragment.PERIOD_WEEK:
- startDate = DateUtils.getWeekStart(date);
- endDate = DateUtils.getWeekEnd(date);
- break;
- case ReportFragment.PERIOD_MONTH:
- startDate = DateUtils.getMonthStart(date);
- endDate = DateUtils.getMonthEnd(date);
- break;
- }
- Page.goToExpensesList(view.getActivity(), startDate, endDate, category);
- }
-}
\ No newline at end of file
diff --git a/outlay/app/src/main/java/com/outlay/utils/FormatUtils.java b/outlay/app/src/main/java/com/outlay/utils/FormatUtils.java
deleted file mode 100644
index b6bd1d6..0000000
--- a/outlay/app/src/main/java/com/outlay/utils/FormatUtils.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.outlay.utils;
-
-/**
- * Created by Bogdan Melnychuk on 2/10/16.
- */
-public final class FormatUtils {
- public static String formatAmount(Double amount) {
- return String.format("%.2f", amount);
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/utils/IconUtils.java b/outlay/app/src/main/java/com/outlay/utils/IconUtils.java
deleted file mode 100644
index 8923d2c..0000000
--- a/outlay/app/src/main/java/com/outlay/utils/IconUtils.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.outlay.utils;
-
-import com.github.johnkil.print.PrintView;
-import com.outlay.dao.Category;
-
-/**
- * Created by Bogdan Melnychuk on 2/2/16.
- */
-public final class IconUtils {
- public static void loadCategoryIcon(Category category, PrintView printView) {
- loadCategoryIcon(category.getIcon(), printView);
- printView.setIconColor(category.getColor());
- }
-
- public static void loadCategoryIcon(String icon, PrintView printView) {
- printView.setIconFont("fonts/font-outlay.ttf");
- printView.setIconCodeRes(ResourceUtils.getIntegerResource(printView.getContext(), icon));
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/view/Page.java b/outlay/app/src/main/java/com/outlay/view/Page.java
deleted file mode 100644
index 974fb14..0000000
--- a/outlay/app/src/main/java/com/outlay/view/Page.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.outlay.view;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.os.Bundle;
-
-import com.outlay.R;
-import com.outlay.model.Report;
-import com.outlay.view.activity.SingleFragmentActivity;
-import com.outlay.view.fragment.CategoryDetailsFragment;
-import com.outlay.view.fragment.ExpensesDetailsFragment;
-import com.outlay.view.fragment.ExpensesListFragment;
-import com.outlay.view.fragment.ReportFragment;
-
-import java.util.Date;
-
-/**
- * Created by Bogdan Melnychuk on 1/24/16.
- */
-public final class Page {
- public static void goToCategoryDetails(Activity activityFrom, Long categoryId) {
- Bundle b = new Bundle();
- if (categoryId != null) {
- b.putLong(CategoryDetailsFragment.ARG_CATEGORY_PARAM, categoryId);
- }
- changeFragment(activityFrom, CategoryDetailsFragment.class, b);
- }
-
- public static void goToReport(Activity activityFrom, Date date) {
- Bundle b = new Bundle();
- b.putLong(ReportFragment.ARG_DATE, date.getTime());
- SingleFragmentActivity.start(activityFrom, ReportFragment.class, b);
- }
-
- public static void goToExpensesList(Activity activityFrom, Date dateFrom, Date dateTo, Long categoryId) {
- Bundle b = new Bundle();
- if(categoryId != null) {
- b.putLong(ExpensesListFragment.ARG_CATEGORY_ID, categoryId);
- }
- if (dateFrom != null) {
- b.putLong(ExpensesListFragment.ARG_DATE_FROM, dateFrom.getTime());
- }
- if (dateTo != null) {
- b.putLong(ExpensesListFragment.ARG_DATE_TO, dateTo.getTime());
- }
- changeFragment(activityFrom, ExpensesListFragment.class, b);
- }
-
- public static void goToExpenseDetails(Activity activityFrom, Long expenseId) {
- Bundle b = new Bundle();
- if(expenseId != null) {
- b.putLong(ExpensesDetailsFragment.ARG_EXPENSE_ID, expenseId);
- }
- changeFragment(activityFrom, ExpensesDetailsFragment.class, b);
- }
-
- private static void changeFragment(Activity activityFrom, Class> clazz, Bundle b) {
- String className = clazz.getName();
- Fragment f = Fragment.instantiate(activityFrom, className);
- f.setArguments(b);
- activityFrom.getFragmentManager()
- .beginTransaction()
- .replace(R.id.fragment, f, className)
- .addToBackStack(className)
- .commit();
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/view/activity/BaseActivity.java b/outlay/app/src/main/java/com/outlay/view/activity/BaseActivity.java
deleted file mode 100644
index a982371..0000000
--- a/outlay/app/src/main/java/com/outlay/view/activity/BaseActivity.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package com.outlay.view.activity;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.github.johnkil.print.PrintView;
-import com.mikepenz.google_material_typeface_library.GoogleMaterial;
-import com.mikepenz.materialdrawer.Drawer;
-import com.mikepenz.materialdrawer.DrawerBuilder;
-import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
-import com.outlay.Constants;
-import com.outlay.R;
-import com.outlay.dao.Category;
-import com.outlay.model.Summary;
-import com.outlay.utils.IconUtils;
-import com.outlay.view.alert.Alert;
-import com.outlay.view.fragment.AboutFragment;
-import com.outlay.view.fragment.CategoriesFragment;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-
-/**
- * Created by Bogdan Melnychuk on 1/15/16.
- */
-public class BaseActivity extends AppCompatActivity {
-
- private static final int ITEM_FEEDBACK = 2;
- private static final int ITEM_CATEGORIES = 1;
- private static final int ITEM_ABOUT = 3;
-
- private Drawer drawer;
-
- @Nullable
- @Bind(R.id.toolbar)
- Toolbar toolbar;
-
- private View headerView;
- private LayoutInflater inflater;
-
- @Override
- public void setContentView(int layoutResID) {
- super.setContentView(layoutResID);
- ButterKnife.bind(this);
- if (toolbar != null) {
- setSupportActionBar(toolbar);
- }
- if (hasDrawer()) {
- setupDrawer(this.toolbar);
- }
- inflater = LayoutInflater.from(this);
- }
-
- public void setupDrawer(Toolbar toolbar) {
- LayoutInflater inflater = LayoutInflater.from(this);
- headerView = inflater.inflate(R.layout.layout_drawer_header, null, false);
-
- drawer = new DrawerBuilder()
- .withHeader(headerView)
- .withToolbar(toolbar)
- .withFullscreen(true)
- .withActivity(this)
- .withSelectedItem(-1)
- .addDrawerItems(
- new PrimaryDrawerItem().withName(R.string.menu_item_categories).withIcon(GoogleMaterial.Icon.gmd_apps).withIdentifier(ITEM_CATEGORIES),
- new PrimaryDrawerItem().withName(R.string.menu_item_feedback).withIcon(GoogleMaterial.Icon.gmd_mail).withIdentifier(ITEM_FEEDBACK),
- new PrimaryDrawerItem().withName(R.string.menu_item_about).withIcon(GoogleMaterial.Icon.gmd_info).withIdentifier(ITEM_ABOUT)
- )
- .withOnDrawerItemClickListener((view, i, iDrawerItem) -> {
- if (iDrawerItem != null) {
- int id = iDrawerItem.getIdentifier();
- switch (id) {
- case ITEM_CATEGORIES:
- SingleFragmentActivity.start(BaseActivity.this, CategoriesFragment.class);
- break;
- case ITEM_FEEDBACK:
- Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(
- "mailto", Constants.CONTACT_EMAIL, null));
- emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{Constants.CONTACT_EMAIL});
- emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Outlay Feedback");
- try {
- startActivity(Intent.createChooser(emailIntent, getString(R.string.label_send_email)));
- } catch (android.content.ActivityNotFoundException ex) {
- Alert.error(getRootView(), getString(R.string.error_no_email_clients));
- }
- break;
- case ITEM_ABOUT:
- SingleFragmentActivity.start(BaseActivity.this, AboutFragment.class);
- break;
- }
- drawer.setSelection(-1);
- drawer.closeDrawer();
- }
- return false;
- })
- .build();
- }
-
- @Override
- public void onBackPressed() {
- if (drawer != null && drawer.isDrawerOpen()) {
- drawer.closeDrawer();
- } else {
- super.onBackPressed();
- }
- }
-
- public void updateDrawerData(Summary summary) {
- if (headerView != null) {
- TextView dateAmount = (TextView) headerView.findViewById(R.id.dateAmount);
- TextView weekAmount = (TextView) headerView.findViewById(R.id.weekAmount);
- TextView monthAmount = (TextView) headerView.findViewById(R.id.monthAmount);
-
- dateAmount.setText(String.valueOf(summary.getDayAmount()));
- weekAmount.setText(String.valueOf(summary.getWeekAmount()));
- monthAmount.setText(String.valueOf(summary.getMonthAmount()));
-
- LinearLayout categoriesContainer = (LinearLayout) headerView.findViewById(R.id.mostSpentCategories);
- categoriesContainer.removeAllViews();
- for (Category c : summary.getCategories()) {
- categoriesContainer.addView(createCategoryView(c));
- }
- }
- }
-
- public boolean hasDrawer() {
- return true;
- }
-
- public Drawer getDrawer() {
- return drawer;
- }
-
- private View createCategoryView(Category category) {
- View categoryView = inflater.inflate(R.layout.item_category, null, false);
-
- LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- 1);
- categoryView.setLayoutParams(layoutParams);
-
- PrintView icon = (PrintView) categoryView.findViewById(R.id.categoryIcon);
- TextView title = (TextView) categoryView.findViewById(R.id.categoryTitle);
- title.setText(category.getTitle());
- icon.setIconSizeRes(R.dimen.category_icon);
- IconUtils.loadCategoryIcon(category, icon);
- return categoryView;
- }
-
- public View getRootView() {
- return findViewById(android.R.id.content);
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/view/activity/MainActivity.java b/outlay/app/src/main/java/com/outlay/view/activity/MainActivity.java
deleted file mode 100644
index d88909f..0000000
--- a/outlay/app/src/main/java/com/outlay/view/activity/MainActivity.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.outlay.view.activity;
-
-import android.app.Fragment;
-import android.os.Bundle;
-
-import com.outlay.R;
-import com.outlay.view.fragment.MainFragment;
-
-public class MainActivity extends BaseActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_single_fragment);
- setTitle(null);
-
- Fragment f = Fragment.instantiate(this, MainFragment.class.getName());
- getFragmentManager().beginTransaction().replace(R.id.fragment, f, MainFragment.class.getName()).commit();
- }
-
- @Override
- public boolean hasDrawer() {
- return false;
- }
-}
diff --git a/outlay/app/src/main/java/com/outlay/view/fragment/MainFragment.java b/outlay/app/src/main/java/com/outlay/view/fragment/MainFragment.java
deleted file mode 100644
index 88476f6..0000000
--- a/outlay/app/src/main/java/com/outlay/view/fragment/MainFragment.java
+++ /dev/null
@@ -1,255 +0,0 @@
-package com.outlay.view.fragment;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.design.widget.AppBarLayout;
-import android.support.design.widget.CoordinatorLayout;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.Toolbar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import com.outlay.App;
-import com.outlay.R;
-import com.outlay.adapter.CategoriesGridAdapter;
-import com.outlay.dao.Category;
-import com.outlay.dao.Expense;
-import com.outlay.helper.TextWatcherAdapter;
-import com.outlay.model.Summary;
-import com.outlay.presenter.MainFragmentPresenter;
-import com.outlay.utils.DateUtils;
-import com.outlay.utils.DeviceUtils;
-import com.outlay.utils.ResourceUtils;
-import com.outlay.view.Page;
-import com.outlay.view.activity.BaseActivity;
-import com.outlay.view.alert.Alert;
-import com.outlay.view.dialog.DatePickerFragment;
-import com.outlay.view.numpad.NumpadEditable;
-import com.outlay.view.numpad.NumpadView;
-import com.outlay.view.numpad.SimpleNumpadValidator;
-
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-
-public class MainFragment extends BaseFragment implements AppBarLayout.OnOffsetChangedListener {
- private static final float PERCENTAGE_TO_HIDE_TITLE_DETAILS = 0.2f;
- private static final int ALPHA_ANIMATIONS_DURATION = 200;
-
- @Nullable
- @Bind(R.id.toolbar)
- Toolbar toolbar;
-
- @Bind(R.id.amountEditable)
- EditText amountText;
-
- @Bind(R.id.numpadView)
- NumpadView numpadView;
-
- @Bind(R.id.categoriesGrid)
- RecyclerView categoriesGrid;
-
- @Bind(R.id.appbar)
- AppBarLayout appbar;
-
- @Bind(R.id.toolbarAmountValue)
- TextView toolbarAmountValue;
-
- @Bind(R.id.toolbarContainer)
- View toolbarContainer;
-
- @Bind(R.id.dateLabel)
- TextView dateLabel;
-
- @Bind(R.id.coordinatorLayout)
- CoordinatorLayout coordinatorLayout;
-
- @Inject
- MainFragmentPresenter presenter;
-
- private CategoriesGridAdapter adapter;
- private boolean mIsTheTitleContainerVisible = false;
- private Date selectedDate = new Date();
- private SimpleNumpadValidator validator = new SimpleNumpadValidator() {
- @Override
- public void onInvalidInput(String value) {
- super.onInvalidInput(value);
- inputError();
- }
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- App.getComponent(getActivity()).inject(this);
- presenter.attachView(this);
- }
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_main, null, false);
- ButterKnife.bind(this, view);
- enableToolbar(toolbar);
- ((BaseActivity) getActivity()).setupDrawer(toolbar);
- return view;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- appbar.addOnOffsetChangedListener(this);
- startAlphaAnimation(toolbarContainer, 0, View.INVISIBLE);
-
- numpadView.attachEditable(new NumpadEditable() {
- @Override
- public String getText() {
- return amountText.getText().toString();
- }
-
- @Override
- public void setText(String text) {
- amountText.setText(text);
- }
- }, validator);
- amountText.addTextChangedListener(new TextWatcherAdapter() {
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- toolbarAmountValue.setText(s);
- }
- });
-
- GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 4);
- categoriesGrid.setLayoutManager(gridLayoutManager);
-
- int availableHeight = DeviceUtils.getScreenSize(getActivity()).heightPixels
- - DeviceUtils.getStatusBarHeight(getActivity())
- - DeviceUtils.getActionBarHeight(getActivity()); // Bottom panel has actionBarHeight
- int categoriesGridHeight = (int) (availableHeight / 2.7f);
- appbar.getLayoutParams().height = availableHeight - categoriesGridHeight;
-
- adapter = new CategoriesGridAdapter(new CategoriesGridAdapter.Style(categoriesGridHeight / 2));
- adapter.setOnCategoryClickListener(c -> {
- if (validator.valid(amountText.getText().toString())) {
- Expense e = new Expense();
- e.setCategory(c);
- e.setAmount(Double.valueOf(amountText.getText().toString()));
- e.setReportedAt(selectedDate);
- presenter.insertExpense(e);
- presenter.loadSummary(new Date());
- amountText.setText("");
-
- String message = getString(R.string.info_expense_created);
- message = String.format(message, e.getAmount(), e.getCategory().getTitle());
- Alert.info(getRootView(), message,
- v -> {
- e.delete();
- presenter.loadSummary(new Date());
- amountText.setText(String.valueOf(e.getAmount()));
- }
- );
- } else {
- validator.onInvalidInput(amountText.getText().toString());
- }
- });
- categoriesGrid.setAdapter(adapter);
- dateLabel.setOnClickListener(v -> {
- DatePickerFragment datePickerFragment = new DatePickerFragment();
- datePickerFragment.setOnDateSetListener((parent, year, monthOfYear, dayOfMonth) -> {
- Calendar c = Calendar.getInstance();
- c.set(year, monthOfYear, dayOfMonth);
- Date selected = c.getTime();
- selectedDate = selected;
- dateLabel.setText(DateUtils.toLongString(selected));
- });
- datePickerFragment.show(getChildFragmentManager(), "datePicker");
- });
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- amountText.setText("");
- presenter.loadCategories();
- presenter.loadSummary(new Date());
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- inflater.inflate(R.menu.menu_main, menu);
- MenuItem summaryItem = menu.findItem(R.id.action_summary);
- summaryItem.setIcon(ResourceUtils.getCustomToolbarIcon(getActivity(), R.integer.ic_chart));
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_summary:
- Page.goToReport(getActivity(), selectedDate);
- break;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
- int maxScroll = appBarLayout.getTotalScrollRange();
- float percentage = (float) Math.abs(verticalOffset) / (float) maxScroll;
- handleAlphaOnTitle(percentage);
- }
-
- private void handleAlphaOnTitle(float percentage) {
- if (percentage < PERCENTAGE_TO_HIDE_TITLE_DETAILS) {
- if (mIsTheTitleContainerVisible) {
- startAlphaAnimation(toolbarContainer, ALPHA_ANIMATIONS_DURATION, View.INVISIBLE);
- mIsTheTitleContainerVisible = false;
- }
- } else {
- if (!mIsTheTitleContainerVisible) {
- startAlphaAnimation(toolbarContainer, ALPHA_ANIMATIONS_DURATION, View.VISIBLE);
- mIsTheTitleContainerVisible = true;
- }
- }
- }
-
- public static void startAlphaAnimation(View v, long duration, int visibility) {
- AlphaAnimation alphaAnimation = (visibility == View.VISIBLE)
- ? new AlphaAnimation(0f, 1f)
- : new AlphaAnimation(1f, 0f);
-
- alphaAnimation.setDuration(duration);
- alphaAnimation.setFillAfter(true);
- v.startAnimation(alphaAnimation);
- }
-
- public void inputError() {
- Animation shakeAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.shake);
- amountText.startAnimation(shakeAnimation);
- }
-
- public void displaySummary(Summary summary) {
- ((BaseActivity) getActivity()).updateDrawerData(summary);
- }
-
- public void displayCategories(List categoryList) {
- adapter.setItems(categoryList);
- }
-}
diff --git a/outlay/app/src/main/res/drawable/drawer_image.jpg b/outlay/app/src/main/res/drawable/drawer_image.jpg
new file mode 100644
index 0000000..061d617
Binary files /dev/null and b/outlay/app/src/main/res/drawable/drawer_image.jpg differ
diff --git a/outlay/app/src/main/res/layout/activity_login.xml b/outlay/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..431182a
--- /dev/null
+++ b/outlay/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outlay/app/src/main/res/layout/fragment_analysis.xml b/outlay/app/src/main/res/layout/fragment_analysis.xml
new file mode 100644
index 0000000..e0f1809
--- /dev/null
+++ b/outlay/app/src/main/res/layout/fragment_analysis.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/outlay/app/src/main/res/layout/fragment_categories.xml b/outlay/app/src/main/res/layout/fragment_categories.xml
index 04ab7af..a72a100 100644
--- a/outlay/app/src/main/res/layout/fragment_categories.xml
+++ b/outlay/app/src/main/res/layout/fragment_categories.xml
@@ -14,18 +14,45 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/outlay/app/src/main/res/layout/fragment_expense_details.xml b/outlay/app/src/main/res/layout/fragment_expense_details.xml
index 7392c96..803fa9b 100644
--- a/outlay/app/src/main/res/layout/fragment_expense_details.xml
+++ b/outlay/app/src/main/res/layout/fragment_expense_details.xml
@@ -20,7 +20,7 @@
android:paddingLeft="@dimen/spacing_default"
android:paddingRight="@dimen/spacing_default">
-
+ android:layout_marginRight="@dimen/spacing_default" />
@@ -81,7 +78,8 @@
android:id="@+id/noteInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@+id/dateInputLayout">
+ android:layout_below="@+id/dateInputLayout"
+ android:visibility="gone">
@@ -58,9 +58,13 @@
diff --git a/outlay/app/src/main/res/layout/fragment_login.xml b/outlay/app/src/main/res/layout/fragment_login.xml
new file mode 100644
index 0000000..249add2
--- /dev/null
+++ b/outlay/app/src/main/res/layout/fragment_login.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outlay/app/src/main/res/layout/fragment_main.xml b/outlay/app/src/main/res/layout/fragment_main.xml
index 081604b..1496d8c 100644
--- a/outlay/app/src/main/res/layout/fragment_main.xml
+++ b/outlay/app/src/main/res/layout/fragment_main.xml
@@ -4,30 +4,73 @@
android:layout_height="match_parent"
android:background="@color/background_main">
+
+
+
+
+
+
+
+
+ android:textColor="@color/text_primary"
+ android:textSize="@dimen/text_big" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_above="@+id/footer"
+ android:layout_below="@+id/amountEditable"
+ android:layout_marginLeft="@dimen/spacing_default"
+ android:layout_marginRight="@dimen/spacing_default"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:scrollbars="none" />
+
+
-
\ No newline at end of file
diff --git a/outlay/app/src/main/res/layout/fragment_report.xml b/outlay/app/src/main/res/layout/fragment_report.xml
index 5f1ca50..02ff46d 100644
--- a/outlay/app/src/main/res/layout/fragment_report.xml
+++ b/outlay/app/src/main/res/layout/fragment_report.xml
@@ -30,7 +30,7 @@
diff --git a/outlay/app/src/main/res/layout/fragment_sync_guest.xml b/outlay/app/src/main/res/layout/fragment_sync_guest.xml
new file mode 100644
index 0000000..87f7db7
--- /dev/null
+++ b/outlay/app/src/main/res/layout/fragment_sync_guest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/outlay/app/src/main/res/layout/item_category.xml b/outlay/app/src/main/res/layout/item_category.xml
index 0567ac4..9eea24b 100644
--- a/outlay/app/src/main/res/layout/item_category.xml
+++ b/outlay/app/src/main/res/layout/item_category.xml
@@ -1,31 +1,29 @@
-
+ android:clickable="true"
+ android:padding="@dimen/spacing_very_small" />
\ No newline at end of file
diff --git a/outlay/app/src/main/res/layout/item_icon.xml b/outlay/app/src/main/res/layout/item_icon.xml
index be90a87..c94129f 100644
--- a/outlay/app/src/main/res/layout/item_icon.xml
+++ b/outlay/app/src/main/res/layout/item_icon.xml
@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/categoryContainer"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="@dimen/spacing_small">
@@ -12,7 +12,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
+ android:layout_centerInParent="true"
android:layout_marginBottom="@dimen/spacing_small"
android:layout_marginTop="@dimen/spacing_very_small"
android:background="@drawable/btn_rippled"
diff --git a/outlay/app/src/main/res/layout/item_report.xml b/outlay/app/src/main/res/layout/item_report.xml
index 0415187..1f14af5 100644
--- a/outlay/app/src/main/res/layout/item_report.xml
+++ b/outlay/app/src/main/res/layout/item_report.xml
@@ -6,7 +6,7 @@
android:layout_height="48dp"
android:layout_marginBottom="@dimen/spacing_tiny">
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/outlay/app/src/main/res/layout/layout_no_expenses.xml b/outlay/app/src/main/res/layout/layout_no_expenses.xml
index 67807e9..5ebab3f 100644
--- a/outlay/app/src/main/res/layout/layout_no_expenses.xml
+++ b/outlay/app/src/main/res/layout/layout_no_expenses.xml
@@ -2,30 +2,34 @@
+ android:background="@color/background_main"
+ android:visibility="gone">
-
+ android:orientation="vertical">
-
+
+
+
+
\ No newline at end of file
diff --git a/outlay/app/src/main/res/layout/item_expense.xml b/outlay/app/src/main/res/layout/recycler_grid_expense.xml
similarity index 97%
rename from outlay/app/src/main/res/layout/item_expense.xml
rename to outlay/app/src/main/res/layout/recycler_grid_expense.xml
index 756b3b5..1c632b8 100644
--- a/outlay/app/src/main/res/layout/item_expense.xml
+++ b/outlay/app/src/main/res/layout/recycler_grid_expense.xml
@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/expenseContainer"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_tiny"
android:background="@color/dark"
android:orientation="vertical"
diff --git a/outlay/app/src/main/res/layout/recycler_list_expense.xml b/outlay/app/src/main/res/layout/recycler_list_expense.xml
new file mode 100644
index 0000000..e74b9a7
--- /dev/null
+++ b/outlay/app/src/main/res/layout/recycler_list_expense.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/outlay/app/src/main/res/layout/recycler_numpad.xml b/outlay/app/src/main/res/layout/recycler_numpad.xml
new file mode 100644
index 0000000..eccfd18
--- /dev/null
+++ b/outlay/app/src/main/res/layout/recycler_numpad.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/outlay/app/src/main/res/layout/view_login_form.xml b/outlay/app/src/main/res/layout/view_login_form.xml
new file mode 100644
index 0000000..41e7149
--- /dev/null
+++ b/outlay/app/src/main/res/layout/view_login_form.xml
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/outlay/app/src/main/res/layout/view_numpad.xml b/outlay/app/src/main/res/layout/view_numpad.xml
index 6f9da8c..00933eb 100644
--- a/outlay/app/src/main/res/layout/view_numpad.xml
+++ b/outlay/app/src/main/res/layout/view_numpad.xml
@@ -8,21 +8,21 @@
android:layout_weight="1"
android:orientation="horizontal">
-
-
-
-
-
-
-
-
-
-
-
- @color/material_drawer_dark_primary_text
#202020
+ #00FFFFFF
#F44336
#E91E63
diff --git a/outlay/app/src/main/res/values/dimens.xml b/outlay/app/src/main/res/values/dimens.xml
index 5136289..6fee327 100644
--- a/outlay/app/src/main/res/values/dimens.xml
+++ b/outlay/app/src/main/res/values/dimens.xml
@@ -23,5 +23,6 @@
40sp
32sp
48dp
+ 104dp
24dp
diff --git a/outlay/app/src/main/res/values/strings.xml b/outlay/app/src/main/res/values/strings.xml
index 41757be..4e7fc97 100644
--- a/outlay/app/src/main/res/values/strings.xml
+++ b/outlay/app/src/main/res/values/strings.xml
@@ -3,11 +3,18 @@
Categories
Feedback
About
+ Sign Out
+ Sign In
+ Create User
+ Analysis
You just spent %1$.2f on %2$s
No emails client installed
Category can not be empty
+ Invalid email
+ Invalid password. At least 6 chars
+ Password does not match
Invalid category name
Invalid amount
Icon was not selected
@@ -62,5 +69,6 @@
Grocery
Games
Tickets
+ Create Your first category
diff --git a/outlay/app/src/main/res/values/styles.xml b/outlay/app/src/main/res/values/styles.xml
index 9c51f8f..b3ea0ab 100644
--- a/outlay/app/src/main/res/values/styles.xml
+++ b/outlay/app/src/main/res/values/styles.xml
@@ -1,6 +1,6 @@
-
+
+
+
+
+
+
diff --git a/outlay/build.gradle b/outlay/build.gradle
index aa8aa5f..8315428 100644
--- a/outlay/build.gradle
+++ b/outlay/build.gradle
@@ -2,18 +2,18 @@
ext {
configuration = [
- package : "com.outlay",
- buildToolsVersion : "23.0.2",
- compileVersion : 23,
+ package : "app.outlay",
+ buildToolsVersion : "25.0.2",
+ compileVersion : 25,
minSdk : 17,
- targetSdk : 23,
+ targetSdk : 25,
version_code : 2,
version_name : "1.1",
]
libraries = [
- supportVersion : "23.1.1",
- dagger : "2.0.2",
+ supportVersion : "25.1.1",
+ dagger : "2.9",
rxjava : "1.1.0",
rxandroid : "1.1.0",
butterknife : "7.0.1",
@@ -21,6 +21,7 @@ ext {
circleimageview : "2.0.0",
picasso : "2.5.2",
greenDao : "2.0.0",
+ firebase : "10.0.1",
]
}
@@ -30,11 +31,10 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.5.0'
+ classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
- classpath 'me.tatarka:gradle-retrolambda:3.1.0'
- classpath 'com.android.databinding:dataBinder:1.0-rc4'
-
+ classpath 'me.tatarka:gradle-retrolambda:3.4.0'
+ classpath 'com.google.gms:google-services:3.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
diff --git a/outlay/daogenerator/build.gradle b/outlay/daogenerator/build.gradle
deleted file mode 100644
index 74c6b7d..0000000
--- a/outlay/daogenerator/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: 'application'
-apply plugin: 'java'
-
-mainClassName = "com.outlay.dao.gen.Main"
-
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'de.greenrobot:greendao-generator:2.0.0'
-}
\ No newline at end of file
diff --git a/outlay/daogenerator/.gitignore b/outlay/domain/.gitignore
similarity index 100%
rename from outlay/daogenerator/.gitignore
rename to outlay/domain/.gitignore
diff --git a/outlay/domain/build.gradle b/outlay/domain/build.gradle
new file mode 100644
index 0000000..8b807cd
--- /dev/null
+++ b/outlay/domain/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'java'
+apply plugin: 'me.tatarka.retrolambda'
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+configurations {
+ provided
+}
+
+def cfg = rootProject.ext.configuration
+def libs = rootProject.ext.libraries
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile "io.reactivex:rxjava:${libs.rxjava}"
+ compile "com.google.code.gson:gson:2.6.2"
+ compile "com.google.dagger:dagger:${libs.dagger}"
+ compile 'joda-time:joda-time:2.9.2'
+}
\ No newline at end of file
diff --git a/outlay/domain/src/main/java/app/outlay/core/data/AppPreferences.java b/outlay/domain/src/main/java/app/outlay/core/data/AppPreferences.java
new file mode 100644
index 0000000..5c6a79c
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/core/data/AppPreferences.java
@@ -0,0 +1,12 @@
+package app.outlay.core.data;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public interface AppPreferences {
+ boolean isFirstRun();
+ void setFirstRun(boolean firstRun);
+ String getSessionId();
+ void setSessionId(String sessionId);
+}
diff --git a/outlay/domain/src/main/java/app/outlay/core/executor/DefaultSubscriber.java b/outlay/domain/src/main/java/app/outlay/core/executor/DefaultSubscriber.java
new file mode 100644
index 0000000..9e78981
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/core/executor/DefaultSubscriber.java
@@ -0,0 +1,29 @@
+package app.outlay.core.executor;
+
+
+import app.outlay.core.logger.Logger;
+import app.outlay.core.logger.LoggerFactory;
+
+import rx.Subscriber;
+
+/**
+ * Created by Bogdan Melnychuk on 2/19/16.
+ */
+public class DefaultSubscriber extends Subscriber {
+ private static Logger log = LoggerFactory.getLogger();
+
+ @Override
+ public void onCompleted() {
+
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ log.error("Rx uncaught error.", e);
+ }
+
+ @Override
+ public void onNext(T t) {
+
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/core/executor/PostExecutionThread.java b/outlay/domain/src/main/java/app/outlay/core/executor/PostExecutionThread.java
new file mode 100644
index 0000000..7607874
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/core/executor/PostExecutionThread.java
@@ -0,0 +1,10 @@
+package app.outlay.core.executor;
+
+import rx.Scheduler;
+
+/**
+ * Created by bmelnychuk on 5/6/16.
+ */
+public interface PostExecutionThread {
+ Scheduler getScheduler();
+}
diff --git a/outlay/domain/src/main/java/app/outlay/core/executor/ThreadExecutor.java b/outlay/domain/src/main/java/app/outlay/core/executor/ThreadExecutor.java
new file mode 100644
index 0000000..5c0cbee
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/core/executor/ThreadExecutor.java
@@ -0,0 +1,9 @@
+package app.outlay.core.executor;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Created by bmelnychuk on 5/6/16.
+ */
+public interface ThreadExecutor extends Executor {
+}
diff --git a/outlay/domain/src/main/java/app/outlay/core/logger/DefaultEmptyLogger.java b/outlay/domain/src/main/java/app/outlay/core/logger/DefaultEmptyLogger.java
new file mode 100644
index 0000000..0e38699
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/core/logger/DefaultEmptyLogger.java
@@ -0,0 +1,30 @@
+package app.outlay.core.logger;
+
+/**
+ * Created by Bogdan Melnychuk on 4/23/16.
+ */
+public class DefaultEmptyLogger implements Logger {
+ @Override
+ public void info(String message) {
+ }
+
+ @Override
+ public void warn(String message) {
+ }
+
+ @Override
+ public void warn(String message, Throwable e) {
+ }
+
+ @Override
+ public void debug(String message) {
+ }
+
+ @Override
+ public void error(String message) {
+ }
+
+ @Override
+ public void error(String message, Throwable e) {
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/core/logger/Logger.java b/outlay/domain/src/main/java/app/outlay/core/logger/Logger.java
new file mode 100644
index 0000000..8bd1988
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/core/logger/Logger.java
@@ -0,0 +1,18 @@
+package app.outlay.core.logger;
+
+/**
+ * Created by Bogdan Melnychuk on 4/23/16.
+ */
+public interface Logger {
+ void info(String message);
+
+ void warn(String message);
+
+ void warn(String message, Throwable e);
+
+ void debug(String message);
+
+ void error(String message);
+
+ void error(String message, Throwable e);
+}
diff --git a/outlay/domain/src/main/java/app/outlay/core/logger/LoggerFactory.java b/outlay/domain/src/main/java/app/outlay/core/logger/LoggerFactory.java
new file mode 100644
index 0000000..084d71f
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/core/logger/LoggerFactory.java
@@ -0,0 +1,19 @@
+package app.outlay.core.logger;
+
+/**
+ * Created by Bogdan Melnychuk on 4/23/16.
+ */
+public class LoggerFactory {
+ private static Logger logger;
+
+ public static Logger getLogger() {
+ if (logger == null) {
+ logger = new DefaultEmptyLogger();
+ }
+ return logger;
+ }
+
+ public static void registerLogger(Logger logger) {
+ LoggerFactory.logger = logger;
+ }
+}
diff --git a/outlay/app/src/main/java/com/outlay/utils/DateUtils.java b/outlay/domain/src/main/java/app/outlay/core/utils/DateUtils.java
similarity index 78%
rename from outlay/app/src/main/java/com/outlay/utils/DateUtils.java
rename to outlay/domain/src/main/java/app/outlay/core/utils/DateUtils.java
index ff1692f..d16d8e6 100644
--- a/outlay/app/src/main/java/com/outlay/utils/DateUtils.java
+++ b/outlay/domain/src/main/java/app/outlay/core/utils/DateUtils.java
@@ -1,4 +1,4 @@
-package com.outlay.utils;
+package app.outlay.core.utils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
@@ -15,8 +15,13 @@
public final class DateUtils {
private static final SimpleDateFormat SHORT_STRING_FORMAT = new SimpleDateFormat("dd MMM yy");
+ private static final SimpleDateFormat YEAR_MONTH_STRING_FORMAT = new SimpleDateFormat("yyyyMM");
private static final SimpleDateFormat LONG_STRING_FORMAT = new SimpleDateFormat("dd MMMM yyyy");
+ public static String toYearMonthString(Date date) {
+ return YEAR_MONTH_STRING_FORMAT.format(date);
+ }
+
public static String toShortString(Date date) {
if (isToday(date)) {
return "Today";
@@ -37,6 +42,14 @@ public static Date fillCurrentTime(Date date) {
return dateTime.toDate();
}
+ public static boolean isInPeriod(Date date, Date start, Date end) {
+ DateTime dateTime = new DateTime(date);
+ DateTime startDate = new DateTime(start);
+ DateTime endDate = new DateTime(end);
+
+ return dateTime.isAfter(startDate) && dateTime.isBefore(endDate);
+ }
+
public static boolean isSameDay(Date date1, Date date2) {
if (date1 == null || date2 == null) {
throw new IllegalArgumentException("The dates must not be null");
@@ -89,4 +102,12 @@ public static Date getDayEnd(Date date) {
LocalTime time = new LocalTime().withHourOfDay(23).withMinuteOfHour(59).withSecondOfMinute(59);
return LocalDate.fromDateFields(date).toDateTime(time).toDate();
}
+
+ public static Date getMin(Date date1, Date date2) {
+ return new DateTime(date1).isAfter(new DateTime(date2)) ? date2 : date1;
+ }
+
+ public static Date getMax(Date date1, Date date2) {
+ return new DateTime(date1).isAfter(new DateTime(date2)) ? date1 : date2;
+ }
}
diff --git a/outlay/domain/src/main/java/app/outlay/core/utils/NumberUtils.java b/outlay/domain/src/main/java/app/outlay/core/utils/NumberUtils.java
new file mode 100644
index 0000000..779b6b4
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/core/utils/NumberUtils.java
@@ -0,0 +1,17 @@
+package app.outlay.core.utils;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+
+/**
+ * Created by Bogdan Melnychuk on 2/10/16.
+ */
+public final class NumberUtils {
+ public static String formatAmount(Double amount) {
+ return String.format("%.2f", amount);
+ }
+
+ public static String formatAmount(BigDecimal amount) {
+ return new DecimalFormat("#0.00").format(amount);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/core/utils/TextUtils.java b/outlay/domain/src/main/java/app/outlay/core/utils/TextUtils.java
new file mode 100644
index 0000000..88dac9e
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/core/utils/TextUtils.java
@@ -0,0 +1,11 @@
+package app.outlay.core.utils;
+
+/**
+ * Created by bmelnychuk on 2/5/17.
+ */
+
+public final class TextUtils {
+ public static final boolean isEmpty(String s) {
+ return s == null || s.isEmpty();
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/data/repository/CategoryRepositoryImpl.java b/outlay/domain/src/main/java/app/outlay/data/repository/CategoryRepositoryImpl.java
new file mode 100644
index 0000000..903d32b
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/data/repository/CategoryRepositoryImpl.java
@@ -0,0 +1,100 @@
+package app.outlay.data.repository;
+
+import app.outlay.core.utils.TextUtils;
+import app.outlay.data.source.CategoryDataSource;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.repository.CategoryRepository;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class CategoryRepositoryImpl implements CategoryRepository {
+ private CategoryDataSource databaseSource;
+ private CategoryDataSource firebaseSource;
+
+ private Map categoryMap;
+
+ @Inject
+ public CategoryRepositoryImpl(
+ CategoryDataSource firebaseSource
+ ) {
+ this.firebaseSource = firebaseSource;
+ }
+
+ private CategoryDataSource getDataSource() {
+ return firebaseSource;
+ }
+
+ @Override
+ public Observable> getAll() {
+ if (categoryMap != null) {
+ return Observable.just(new ArrayList<>(categoryMap.values()));
+ }
+ return getDataSource().getAll().doOnNext(categories -> cacheCategories(categories));
+ }
+
+ @Override
+ public Observable getById(String id) {
+ if (categoryMap != null && categoryMap.containsKey(id)) {
+ return Observable.just(categoryMap.get(id));
+ } else {
+ return getDataSource().getById(id);
+ }
+ }
+
+ @Override
+ public Observable> updateOrder(List categories) {
+ clearCache();
+ return getDataSource().updateOrder(categories).doOnNext(updated -> cacheCategories(updated));
+ }
+
+ @Override
+ public Observable save(Category category) {
+ return getAll().switchMap(categories -> {
+ if (TextUtils.isEmpty(category.getId())) {
+ if (categories.isEmpty()) {
+ category.setOrder(0);
+ } else {
+ category.setOrder(categories.get(categories.size() - 1).getOrder() + 1);
+ }
+ }
+ return getDataSource().save(category);
+ }).doOnNext(stored -> cacheCategory(stored));
+ }
+
+ @Override
+ public Observable remove(Category category) {
+ clearCache();
+ return getDataSource().remove(category);
+ }
+
+ private void clearCache() {
+ this.categoryMap = null;
+ }
+
+
+ private void cacheCategories(List categories) {
+ if (categories != null) {
+ for (Category c : categories) {
+ cacheCategory(c);
+ }
+ }
+ }
+
+ private void cacheCategory(Category category) {
+ if (this.categoryMap == null) {
+ categoryMap = new LinkedHashMap<>();
+ }
+ categoryMap.put(category.getId(), category);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/data/repository/ExpenseRepositoryImpl.java b/outlay/domain/src/main/java/app/outlay/data/repository/ExpenseRepositoryImpl.java
new file mode 100644
index 0000000..fda2af2
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/data/repository/ExpenseRepositoryImpl.java
@@ -0,0 +1,63 @@
+package app.outlay.data.repository;
+
+import app.outlay.data.source.ExpenseDataSource;
+import app.outlay.domain.model.Expense;
+import app.outlay.domain.repository.ExpenseRepository;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class ExpenseRepositoryImpl implements ExpenseRepository {
+ private ExpenseDataSource firebaseSource;
+
+ @Inject
+ public ExpenseRepositoryImpl(
+ ExpenseDataSource firebaseSource
+ ) {
+ this.firebaseSource = firebaseSource;
+ }
+
+ private ExpenseDataSource getDataSource() {
+ return firebaseSource;
+ }
+
+ @Override
+ public Observable saveExpense(Expense expense) {
+ return getDataSource().saveExpense(expense);
+ }
+
+ @Override
+ public Observable remove(Expense expense) {
+ return getDataSource().remove(expense);
+ }
+
+ @Override
+ public Observable findExpense(String expenseId, Date date) {
+ return getDataSource().findExpense(expenseId, date);
+ }
+
+ @Override
+ public Observable> getExpenses(
+ Date startDate,
+ Date endDate
+ ) {
+ return getExpenses(startDate, endDate, null);
+ }
+
+ @Override
+ public Observable> getExpenses(
+ Date startDate,
+ Date endDate,
+ String categoryId
+ ) {
+ return getDataSource().getExpenses(startDate, endDate, categoryId);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/data/source/CategoryDataSource.java b/outlay/domain/src/main/java/app/outlay/data/source/CategoryDataSource.java
new file mode 100644
index 0000000..b5b1acf
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/data/source/CategoryDataSource.java
@@ -0,0 +1,23 @@
+package app.outlay.data.source;
+
+import app.outlay.domain.model.Category;
+
+import java.util.List;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/25/16.
+ */
+
+public interface CategoryDataSource {
+ Observable> getAll();
+
+ Observable getById(String id);
+
+ Observable> updateOrder(List categories);
+
+ Observable save(Category category);
+
+ Observable remove(Category category);
+}
diff --git a/outlay/domain/src/main/java/app/outlay/data/source/ExpenseDataSource.java b/outlay/domain/src/main/java/app/outlay/data/source/ExpenseDataSource.java
new file mode 100644
index 0000000..70cb643
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/data/source/ExpenseDataSource.java
@@ -0,0 +1,20 @@
+package app.outlay.data.source;
+
+import app.outlay.domain.model.Expense;
+
+import java.util.Date;
+import java.util.List;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/25/16.
+ */
+
+public interface ExpenseDataSource {
+ Observable saveExpense(Expense expense);
+ Observable> getExpenses(Date startDate, Date endDate);
+ Observable> getExpenses(Date startDate, Date endDate, String categoryId);
+ Observable findExpense(String expenseId, Date date);
+ Observable remove(Expense expense);
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/DeleteCategoryUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/DeleteCategoryUseCase.java
new file mode 100644
index 0000000..db3a37b
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/DeleteCategoryUseCase.java
@@ -0,0 +1,33 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.repository.CategoryRepository;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class DeleteCategoryUseCase extends UseCase {
+ private CategoryRepository categoryRepository;
+
+ @Inject
+ public DeleteCategoryUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ CategoryRepository categoryRepository
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.categoryRepository = categoryRepository;
+ }
+
+ @Override
+ protected Observable buildUseCaseObservable(Category category) {
+ return categoryRepository.remove(category);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/DeleteExpenseUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/DeleteExpenseUseCase.java
new file mode 100644
index 0000000..dbffb4e
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/DeleteExpenseUseCase.java
@@ -0,0 +1,33 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Expense;
+import app.outlay.domain.repository.ExpenseRepository;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class DeleteExpenseUseCase extends UseCase {
+ private ExpenseRepository expenseRepository;
+
+ @Inject
+ public DeleteExpenseUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ ExpenseRepository expenseRepository
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.expenseRepository = expenseRepository;
+ }
+
+ @Override
+ protected Observable buildUseCaseObservable(Expense expense) {
+ return expenseRepository.remove(expense);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/GetCategoriesUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/GetCategoriesUseCase.java
new file mode 100644
index 0000000..6ed31e7
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/GetCategoriesUseCase.java
@@ -0,0 +1,35 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.repository.CategoryRepository;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class GetCategoriesUseCase extends UseCase> {
+ private CategoryRepository categoryRepository;
+
+ @Inject
+ public GetCategoriesUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ CategoryRepository categoryRepository
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.categoryRepository = categoryRepository;
+ }
+
+ @Override
+ protected Observable> buildUseCaseObservable(Void aVoid) {
+ return categoryRepository.getAll();
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/GetCategoryUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/GetCategoryUseCase.java
new file mode 100644
index 0000000..f120449
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/GetCategoryUseCase.java
@@ -0,0 +1,33 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.repository.CategoryRepository;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class GetCategoryUseCase extends UseCase {
+ private CategoryRepository categoryRepository;
+
+ @Inject
+ public GetCategoryUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ CategoryRepository categoryRepository
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.categoryRepository = categoryRepository;
+ }
+
+ @Override
+ protected Observable buildUseCaseObservable(String categoryId) {
+ return categoryRepository.getById(categoryId);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/GetExpenseUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/GetExpenseUseCase.java
new file mode 100644
index 0000000..5451e3b
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/GetExpenseUseCase.java
@@ -0,0 +1,46 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Expense;
+import app.outlay.domain.repository.ExpenseRepository;
+
+import java.util.Date;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class GetExpenseUseCase extends UseCase {
+ private ExpenseRepository expenseRepository;
+
+ @Inject
+ public GetExpenseUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ ExpenseRepository expenseRepository
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.expenseRepository = expenseRepository;
+ }
+
+ @Override
+ protected Observable buildUseCaseObservable(GetExpenseUseCase.Input input) {
+ return expenseRepository.findExpense(input.id, input.date);
+ }
+
+ public static class Input {
+ private final String id;
+ private final Date date;
+
+ public Input(String id, Date date) {
+ this.id = id;
+ this.date = date;
+ }
+ }
+
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/GetExpensesUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/GetExpensesUseCase.java
new file mode 100644
index 0000000..24c102d
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/GetExpensesUseCase.java
@@ -0,0 +1,73 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Report;
+import app.outlay.domain.repository.CategoryRepository;
+import app.outlay.domain.repository.ExpenseRepository;
+
+import java.util.Date;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class GetExpensesUseCase extends UseCase {
+ private ExpenseRepository expenseRepository;
+ private CategoryRepository categoryRepository;
+
+ @Inject
+ public GetExpensesUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ ExpenseRepository expenseRepository,
+ CategoryRepository categoryRepository
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.expenseRepository = expenseRepository;
+ this.categoryRepository = categoryRepository;
+ }
+
+ @Override
+ protected Observable buildUseCaseObservable(GetExpensesUseCase.Input input) {
+// if (input.categoryId != null) {
+// return Observable.zip(
+// categoryRepository.getById(input.categoryId),
+// expenseRepository.getExpenses(input.startDate, input.endDate, input.categoryId),
+// (category, expenses) -> {
+// Report report = new Report();
+// report.setEndDate(input.endDate);
+// report.setStartDate(input.startDate);
+// report.setExpenses(expenses);
+// report.setCategory(category);
+// return report;
+// }
+// );
+// } else {
+ return expenseRepository.getExpenses(input.startDate, input.endDate, input.categoryId)
+ .map(expenses -> {
+ Report report = new Report();
+ report.setEndDate(input.endDate);
+ report.setStartDate(input.startDate);
+ report.setExpenses(expenses);
+ return report;
+ });
+// }
+ }
+
+ public static class Input {
+ private final Date startDate;
+ private final Date endDate;
+ private final String categoryId;
+
+ public Input(Date startDate, Date endDate, String categoryId) {
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.categoryId = categoryId;
+ }
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/LinkAccountUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/LinkAccountUseCase.java
new file mode 100644
index 0000000..0ac9001
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/LinkAccountUseCase.java
@@ -0,0 +1,34 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Credentials;
+import app.outlay.domain.model.User;
+import app.outlay.domain.repository.AuthService;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class LinkAccountUseCase extends UseCase {
+ private AuthService authService;
+
+ @Inject
+ public LinkAccountUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ AuthService authService
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.authService = authService;
+ }
+
+ @Override
+ protected Observable buildUseCaseObservable(Credentials credentials) {
+ return authService.linkCredentials(credentials);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/ResetPasswordUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/ResetPasswordUseCase.java
new file mode 100644
index 0000000..17ce955
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/ResetPasswordUseCase.java
@@ -0,0 +1,33 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.User;
+import app.outlay.domain.repository.AuthService;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/26/16.
+ */
+
+public class ResetPasswordUseCase extends UseCase {
+ private AuthService authService;
+
+ @Inject
+ public ResetPasswordUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ AuthService authService
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.authService = authService;
+ }
+
+ @Override
+ protected Observable buildUseCaseObservable(User user) {
+ return authService.resetPassword(user);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/SaveCategoryUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/SaveCategoryUseCase.java
new file mode 100644
index 0000000..62279c0
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/SaveCategoryUseCase.java
@@ -0,0 +1,33 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.repository.CategoryRepository;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class SaveCategoryUseCase extends UseCase {
+ private CategoryRepository categoryRepository;
+
+ @Inject
+ public SaveCategoryUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ CategoryRepository categoryRepository
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.categoryRepository = categoryRepository;
+ }
+
+ @Override
+ protected Observable buildUseCaseObservable(Category category) {
+ return categoryRepository.save(category);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/SaveExpenseUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/SaveExpenseUseCase.java
new file mode 100644
index 0000000..f43719c
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/SaveExpenseUseCase.java
@@ -0,0 +1,39 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Expense;
+import app.outlay.domain.repository.ExpenseRepository;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalTime;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class SaveExpenseUseCase extends UseCase {
+ private ExpenseRepository expenseRepository;
+
+ @Inject
+ public SaveExpenseUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ ExpenseRepository expenseRepository
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.expenseRepository = expenseRepository;
+ }
+
+ @Override
+ protected Observable buildUseCaseObservable(Expense expense) {
+ DateTime dateTime = new DateTime(expense.getReportedWhen().getTime());
+ dateTime = dateTime.withTime(LocalTime.now());
+ expense.setReportedWhen(dateTime.toDate());
+ return expenseRepository.saveExpense(expense);
+ }
+}
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/UpdateCategoriesUseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/UpdateCategoriesUseCase.java
new file mode 100644
index 0000000..8c68e4c
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/UpdateCategoriesUseCase.java
@@ -0,0 +1,35 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+import app.outlay.domain.model.Category;
+import app.outlay.domain.repository.CategoryRepository;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import rx.Observable;
+
+/**
+ * Created by bmelnychuk on 10/24/16.
+ */
+
+public class UpdateCategoriesUseCase extends UseCase, List> {
+ private CategoryRepository categoryRepository;
+
+ @Inject
+ public UpdateCategoriesUseCase(
+ ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread,
+ CategoryRepository categoryRepository
+ ) {
+ super(threadExecutor, postExecutionThread);
+ this.categoryRepository = categoryRepository;
+ }
+
+ @Override
+ protected Observable> buildUseCaseObservable(List categories) {
+ return categoryRepository.updateOrder(categories);
+ }
+}
\ No newline at end of file
diff --git a/outlay/domain/src/main/java/app/outlay/domain/interactor/UseCase.java b/outlay/domain/src/main/java/app/outlay/domain/interactor/UseCase.java
new file mode 100644
index 0000000..b07868a
--- /dev/null
+++ b/outlay/domain/src/main/java/app/outlay/domain/interactor/UseCase.java
@@ -0,0 +1,51 @@
+package app.outlay.domain.interactor;
+
+import app.outlay.core.executor.PostExecutionThread;
+import app.outlay.core.executor.ThreadExecutor;
+
+import rx.Observable;
+import rx.Subscriber;
+import rx.Subscription;
+import rx.schedulers.Schedulers;
+import rx.subscriptions.Subscriptions;
+
+/**
+ * Created by bmelnychuk on 5/10/16.
+ */
+public abstract class UseCase {
+ private final ThreadExecutor threadExecutor;
+ private final PostExecutionThread postExecutionThread;
+
+ private Subscription subscription = Subscriptions.unsubscribed();
+
+ protected UseCase(ThreadExecutor threadExecutor,
+ PostExecutionThread postExecutionThread) {
+ this.threadExecutor = threadExecutor;
+ this.postExecutionThread = postExecutionThread;
+ }
+
+ protected abstract Observable