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 { + //
Icons made by Gregor Cresnar from www.flaticon.com is licensed by CC 3.0 BY
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" /> + +