├── crashlytics_release_notes.txt ├── settings.gradle ├── app ├── .gitignore ├── fabric.properties ├── keystore │ └── debug.keystore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-hdpi │ │ │ │ ├── ic_drawer.png │ │ │ │ ├── drawer_shadow.9.png │ │ │ │ ├── ic_add_white_24dp.png │ │ │ │ ├── ic_action_content_new.png │ │ │ │ └── ic_attach_money_white_24dp.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── ic_drawer.png │ │ │ │ ├── drawer_shadow.9.png │ │ │ │ ├── ic_add_white_24dp.png │ │ │ │ ├── ic_action_content_new.png │ │ │ │ └── ic_attach_money_white_24dp.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── ic_drawer.png │ │ │ │ ├── drawer_shadow.9.png │ │ │ │ ├── ic_add_white_24dp.png │ │ │ │ ├── ic_action_content_new.png │ │ │ │ └── ic_attach_money_white_24dp.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── ic_drawer.png │ │ │ │ ├── drawer_shadow.9.png │ │ │ │ ├── ic_add_white_24dp.png │ │ │ │ ├── ic_action_content_new.png │ │ │ │ └── ic_attach_money_white_24dp.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable │ │ │ │ ├── ic_action_content_new.png │ │ │ │ ├── percent_change_pill.xml │ │ │ │ ├── percent_change_pill_green.xml │ │ │ │ ├── percent_change_pill_red.xml │ │ │ │ └── touch_selector_dark.xml │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── ic_add_white_24dp.png │ │ │ │ └── ic_attach_money_white_24dp.png │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── drawable-v21 │ │ │ │ └── touch_selector_dark.xml │ │ │ ├── xml │ │ │ │ └── appwidget_info.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ └── layout │ │ │ │ ├── frame_container.xml │ │ │ │ ├── appwidget_collection.xml │ │ │ │ ├── fragment_stocks_graph.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── appwidget_collection_item.xml │ │ │ │ └── item_stock.xml │ │ ├── java │ │ │ └── rajan │ │ │ │ └── udacity │ │ │ │ └── stock │ │ │ │ └── hawk │ │ │ │ ├── injection │ │ │ │ ├── ActivityContext.java │ │ │ │ ├── ApplicationContext.java │ │ │ │ ├── PerActivity.java │ │ │ │ ├── ConfigPersistent.java │ │ │ │ ├── module │ │ │ │ │ ├── ActivityModule.java │ │ │ │ │ └── ApplicationModule.java │ │ │ │ └── component │ │ │ │ │ ├── ActivityComponent.java │ │ │ │ │ ├── ConfigPersistentComponent.java │ │ │ │ │ └── ApplicationComponent.java │ │ │ │ ├── touch_helper │ │ │ │ ├── ItemTouchHelperAdapter.java │ │ │ │ ├── ItemTouchHelperViewHolder.java │ │ │ │ └── SimpleItemTouchHelperCallback.java │ │ │ │ ├── util │ │ │ │ ├── RxUtil.java │ │ │ │ ├── Constants.java │ │ │ │ ├── ViewUtil.java │ │ │ │ ├── NetworkUtil.java │ │ │ │ ├── ActivityUtils.java │ │ │ │ ├── AndroidComponentUtil.java │ │ │ │ ├── RxEventBus.java │ │ │ │ ├── DialogFactory.java │ │ │ │ └── Utils.java │ │ │ │ ├── ui │ │ │ │ ├── base │ │ │ │ │ ├── Presenter.java │ │ │ │ │ ├── MvpView.java │ │ │ │ │ ├── BasePresenter.java │ │ │ │ │ └── BaseActivity.java │ │ │ │ ├── widget │ │ │ │ │ ├── ListRemoteViewFactoryMvpView.java │ │ │ │ │ ├── WidgetRemoteViewsService.java │ │ │ │ │ ├── WidgetProvider.java │ │ │ │ │ ├── ListRemoteViewFactoryPresenter.java │ │ │ │ │ └── ListRemoteViewFactory.java │ │ │ │ ├── stockgraph │ │ │ │ │ ├── StockGraphMvpView.java │ │ │ │ │ ├── StockGraphActivity.java │ │ │ │ │ ├── StockGraphPresenter.java │ │ │ │ │ └── StockGraphFragment.java │ │ │ │ └── main │ │ │ │ │ ├── MainMvpView.java │ │ │ │ │ ├── StockAdapter.java │ │ │ │ │ ├── MainPresenter.java │ │ │ │ │ └── MainActivity.java │ │ │ │ ├── data │ │ │ │ ├── local │ │ │ │ │ ├── StockBaseModel.java │ │ │ │ │ ├── StockDatabase.java │ │ │ │ │ ├── PreferencesHelper.java │ │ │ │ │ └── DatabaseHelper.java │ │ │ │ ├── ApiEndPoint.java │ │ │ │ ├── model │ │ │ │ │ ├── single │ │ │ │ │ │ ├── Stock.java │ │ │ │ │ │ ├── Result.java │ │ │ │ │ │ └── Query.java │ │ │ │ │ ├── multiple │ │ │ │ │ │ ├── Stocks.java │ │ │ │ │ │ ├── Result.java │ │ │ │ │ │ └── Query.java │ │ │ │ │ └── financechart │ │ │ │ │ │ ├── Labels.java │ │ │ │ │ │ ├── Date.java │ │ │ │ │ │ ├── FinanceChartCallBack.java │ │ │ │ │ │ ├── Series.java │ │ │ │ │ │ └── Meta.java │ │ │ │ ├── remote │ │ │ │ │ ├── UrlBuilder.java │ │ │ │ │ └── StocksService.java │ │ │ │ ├── DataManager.java │ │ │ │ └── SyncService.java │ │ │ │ └── StockHawkApplication.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── rajan │ │ │ └── udacity │ │ │ └── stock │ │ │ └── hawk │ │ │ ├── util │ │ │ ├── DefaultConfig.java │ │ │ ├── RxEventBusTest.java │ │ │ └── RxSchedulersOverrideRule.java │ │ │ ├── DatabaseHelperTest.java │ │ │ ├── MainPresenterTest.java │ │ │ └── DataManagerTest.java │ ├── debug │ │ └── AndroidManifest.xml │ ├── commonTest │ │ └── java │ │ │ └── rajan │ │ │ └── udacity │ │ │ └── stock │ │ │ └── hawk │ │ │ └── test │ │ │ └── common │ │ │ ├── injection │ │ │ ├── component │ │ │ │ └── TestComponent.java │ │ │ └── module │ │ │ │ └── ApplicationTestModule.java │ │ │ ├── TestDataFactory.java │ │ │ └── TestComponentRule.java │ └── androidTest │ │ └── java │ │ └── rajan │ │ └── udacity │ │ └── stock │ │ └── hawk │ │ ├── runner │ │ ├── RxAndroidJUnitRunner.java │ │ └── UnlockDeviceAndroidJUnitRunner.java │ │ ├── util │ │ ├── RxIdlingExecutionHook.java │ │ └── RxIdlingResource.java │ │ └── MainActivityTest.java ├── proguard-rules.pro └── build.gradle ├── screenshots ├── s1.png ├── s2.png ├── s3.png ├── s4.png └── s5.png ├── images └── check-task-diagram.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── config └── quality │ ├── findbugs │ └── android-exclude-filter.xml │ ├── pmd │ └── pmd-ruleset.xml │ ├── quality.gradle │ └── checkstyle │ └── checkstyle-config.xml ├── gradlew.bat ├── CONTRIBUTING.md ├── README.md └── gradlew /crashlytics_release_notes.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *iml 3 | *.iml 4 | .idea -------------------------------------------------------------------------------- /app/fabric.properties: -------------------------------------------------------------------------------- 1 | apiSecret=changeMeToYourRealApiSecret 2 | apiKey=changeMeToYourRealApiKey -------------------------------------------------------------------------------- /screenshots/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/screenshots/s1.png -------------------------------------------------------------------------------- /screenshots/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/screenshots/s2.png -------------------------------------------------------------------------------- /screenshots/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/screenshots/s3.png -------------------------------------------------------------------------------- /screenshots/s4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/screenshots/s4.png -------------------------------------------------------------------------------- /screenshots/s5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/screenshots/s5.png -------------------------------------------------------------------------------- /app/keystore/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/keystore/debug.keystore -------------------------------------------------------------------------------- /images/check-task-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/images/check-task-diagram.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle performance variables 2 | org.gradle.jvmargs=-Xmx4G -XX:MaxPermSize=512m 3 | org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | .DS_Store 5 | /build 6 | .idea/ 7 | *iml 8 | *.iml 9 | */build -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-hdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-mdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xhdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xxhdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-hdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-mdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_content_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable/ic_action_content_new.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_content_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-hdpi/ic_action_content_new.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_content_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-mdpi/ic_action_content_new.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_content_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xhdpi/ic_action_content_new.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_content_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xxhdpi/ic_action_content_new.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_attach_money_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-hdpi/ic_attach_money_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_attach_money_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-mdpi/ic_attach_money_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_attach_money_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xhdpi/ic_attach_money_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_attach_money_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xxhdpi/ic_attach_money_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_attach_money_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therajanmaurya/Stock-Hawk/HEAD/app/src/main/res/drawable-xxxhdpi/ic_attach_money_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/percent_change_pill.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/percent_change_pill_green.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/percent_change_pill_red.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 29 15:59:49 BST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip 7 | -------------------------------------------------------------------------------- /app/src/test/java/rajan/udacity/stock/hawk/util/DefaultConfig.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | /** 4 | * Robolectric default config properties 5 | */ 6 | public class DefaultConfig { 7 | //The api level that Roboelectric will use to run the unit tests 8 | public static final int EMULATE_SDK = 23; 9 | } -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/injection/ActivityContext.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Qualifier; 7 | 8 | @Qualifier 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ActivityContext { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/injection/ApplicationContext.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Qualifier; 7 | 8 | @Qualifier 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ApplicationContext { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/touch_helper/ItemTouchHelperAdapter.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.touch_helper; 2 | 3 | /** 4 | * Created by sam_chordas on 10/6/15. 5 | * credit to Paul Burke (ipaulpro) 6 | * Interface to enable swipe to delete 7 | */ 8 | public interface ItemTouchHelperAdapter { 9 | 10 | void onItemDismiss(int position); 11 | } 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please make sure these boxes are checked before submitting your pull request - thanks! 2 | 3 | Fixes #{Issue Number} 4 | 5 | - [ ] Follow the style used in this project. 6 | 7 | - [ ] Run the unit tests with `./gradlew check` to make sure you didn't break anything 8 | 9 | - [ ] If you have multiple commits please combine them into one commit by squashing them. -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/util/RxUtil.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import rx.Subscription; 4 | 5 | public class RxUtil { 6 | 7 | public static void unsubscribe(Subscription subscription) { 8 | if (subscription != null && !subscription.isUnsubscribed()) { 9 | subscription.unsubscribe(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/touch_helper/ItemTouchHelperViewHolder.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.touch_helper; 2 | 3 | /** 4 | * Created by sam_chordas on 10/6/15. 5 | * credit to Paul Burke (ipaulpro) 6 | * Interface for enabling swiping to delete 7 | */ 8 | public interface ItemTouchHelperViewHolder { 9 | void onItemSelected(); 10 | 11 | void onItemClear(); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1976D2 4 | #2196F3 5 | #D50000 6 | #00C853 7 | 8 | #ffffff 9 | #000000 10 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/base/Presenter.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.base; 2 | 3 | /** 4 | * Every presenter in the app must either implement this interface or extend BasePresenter 5 | * indicating the MvpView type that wants to be attached with. 6 | */ 7 | public interface Presenter { 8 | 9 | void attachView(V mvpView); 10 | 11 | void detachView(); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/touch_selector_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/touch_selector_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/base/MvpView.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.base; 2 | 3 | 4 | /** 5 | * Base interface that any class that wants to act as a View in the MVP (Model View Presenter) 6 | * pattern must implement. Generally this interface will be extended by a more specific interface 7 | * that then usually will be implemented by an Activity or Fragment. 8 | */ 9 | public interface MvpView { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/local/StockBaseModel.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.local; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import com.raizlabs.android.dbflow.structure.BaseModel; 6 | 7 | /** 8 | * Created by Rajan Maurya on 23/06/16. 9 | */ 10 | public class StockBaseModel extends BaseModel { 11 | 12 | @Override 13 | public String toString() { 14 | return new Gson().toJson(this); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/xml/appwidget_info.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/ApiEndPoint.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data; 2 | 3 | /** 4 | * Created by Rajan Maurya on 16/08/16. 5 | */ 6 | public class ApiEndPoint { 7 | 8 | public static final String YAHOO_QUERY_LANGUAGE = "yql?"; 9 | 10 | public static final String RESPONSE_FORMAT = 11 | "&format=json&diagnostics=true&env=store%3A%2F%2Fdatatables." 12 | + "org%2Falltableswithkeys&callback="; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /config/quality/findbugs/android-exclude-filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/injection/PerActivity.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * A scoping annotation to permit objects whose lifetime should 10 | * conform to the life of the Activity to be memorised in the 11 | * correct component. 12 | */ 13 | @Scope 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface PerActivity { 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/widget/ListRemoteViewFactoryMvpView.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.widget; 2 | 3 | import java.util.List; 4 | 5 | import rajan.udacity.stock.hawk.data.model.Quote; 6 | import rajan.udacity.stock.hawk.ui.base.MvpView; 7 | 8 | /** 9 | * Created by Rajan Maurya on 30/08/16. 10 | */ 11 | public interface ListRemoteViewFactoryMvpView extends MvpView { 12 | 13 | void showStocks(List quoteList); 14 | 15 | void showStocksEmpty(); 16 | 17 | void showError(); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/widget/WidgetRemoteViewsService.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.widget; 2 | 3 | import android.content.Intent; 4 | import android.widget.RemoteViewsService; 5 | 6 | /** 7 | * Created by Rajan Maurya on 30/08/16. 8 | */ 9 | public class WidgetRemoteViewsService extends RemoteViewsService { 10 | 11 | @Override 12 | public RemoteViewsFactory onGetViewFactory(Intent intent) { 13 | return new ListRemoteViewFactory(this.getApplicationContext(), intent); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/commonTest/java/rajan/udacity/stock/hawk/test/common/injection/component/TestComponent.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.test.common.injection.component; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Component; 6 | import rajan.udacity.stock.hawk.injection.component.ApplicationComponent; 7 | import rajan.udacity.stock.hawk.test.common.injection.module.ApplicationTestModule; 8 | 9 | @Singleton 10 | @Component(modules = ApplicationTestModule.class) 11 | public interface TestComponent extends ApplicationComponent { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/injection/ConfigPersistent.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | import rajan.udacity.stock.hawk.injection.component.ConfigPersistentComponent; 9 | 10 | /** 11 | * A scoping annotation to permit dependencies conform to the life of the 12 | * {@link ConfigPersistentComponent} 13 | */ 14 | @Scope 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface ConfigPersistent { 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/local/StockDatabase.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.local; 2 | 3 | import com.raizlabs.android.dbflow.annotation.Database; 4 | 5 | /** 6 | * Created by Rajan Maurya on 23/08/16. 7 | */ 8 | 9 | @Database(name = StockDatabase.NAME, version = StockDatabase.VERSION, foreignKeysSupported = true) 10 | public class StockDatabase { 11 | 12 | // database name will be Stocks.db 13 | public static final String NAME = "Stocks"; 14 | 15 | //Always Increase the Version Number 16 | public static final int VERSION = 1; 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/stockgraph/StockGraphMvpView.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.stockgraph; 2 | 3 | import rajan.udacity.stock.hawk.data.model.financechart.FinanceChartCallBack; 4 | import rajan.udacity.stock.hawk.ui.base.MvpView; 5 | 6 | /** 7 | * Created by Rajan Maurya on 30/08/16. 8 | */ 9 | public interface StockGraphMvpView extends MvpView { 10 | 11 | void showProgressBar(Boolean show); 12 | 13 | void showFinanceChartData(FinanceChartCallBack financeChartCallBack); 14 | 15 | void initGraph(); 16 | 17 | void showError(); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/frame_container.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 24sp 7 | 22sp 8 | 20sp 9 | 18sp 10 | 16sp 11 | 14sp 12 | 16dp 13 | 14 | 240dp 15 | 300dp 16 | 17 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributing A Patch 4 | 5 | 1. Submit an issue describing your proposed change to the repo in question. 6 | 1. The repo owner will respond to your issue promptly. 7 | 1. Fork the desired repo, develop and test your code changes. 8 | 1. Ensure that your code adheres to the existing style in the sample to which 9 | you are contributing. Refer to the 10 | [Android Code Style Guide] 11 | (https://source.android.com/source/code-style.html) for the 12 | recommended coding standards for this organization. 13 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 14 | 1. Submit a pull request. 15 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/injection/module/ActivityModule.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.injection.module; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | 6 | import dagger.Module; 7 | import dagger.Provides; 8 | import rajan.udacity.stock.hawk.injection.ActivityContext; 9 | 10 | @Module 11 | public class ActivityModule { 12 | 13 | private Activity mActivity; 14 | 15 | public ActivityModule(Activity activity) { 16 | mActivity = activity; 17 | } 18 | 19 | @Provides 20 | Activity provideActivity() { 21 | return mActivity; 22 | } 23 | 24 | @Provides 25 | @ActivityContext 26 | Context providesContext() { 27 | return mActivity; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | Try to describe your issue in details. 4 | 5 | ### Stock-Hawk Version 6 | 7 | 8 | 9 | ### Android Version (19, 26, etc.) 10 | 11 | 12 | 13 | ### Expected Behavior 14 | 15 | Please describe the expected behavior of the issue, starting from the first action. 16 | 17 | 1. 18 | 2. 19 | 3. 20 | 21 | ### Actual Behavior 22 | 23 | Please provide a description of what actually happens, working from the same starting point. 24 | 25 | Be descriptive: "it doesn't work" does not describe what the behavior actually is. 26 | 27 | 1. 28 | 2. 29 | 3. 30 | 31 | ### Reproducible Test Case 32 | 33 | If the issue is more complex or requires configuration, please provide a link to a project on Github that reproduces the issue. 34 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/injection/component/ActivityComponent.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.injection.component; 2 | 3 | import dagger.Subcomponent; 4 | import rajan.udacity.stock.hawk.injection.PerActivity; 5 | import rajan.udacity.stock.hawk.injection.module.ActivityModule; 6 | import rajan.udacity.stock.hawk.ui.main.MainActivity; 7 | import rajan.udacity.stock.hawk.ui.stockgraph.StockGraphFragment; 8 | 9 | /** 10 | * This component inject dependencies to all Activities across the application 11 | */ 12 | @PerActivity 13 | @Subcomponent(modules = ActivityModule.class) 14 | public interface ActivityComponent { 15 | 16 | void inject(MainActivity mainActivity); 17 | 18 | void inject(StockGraphFragment stockGraphFragment); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/util/Constants.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | /** 4 | * Created by Rajan Maurya on 16/08/16. 5 | */ 6 | public class Constants { 7 | 8 | public static String YAHOO_STOCK_SYMBOL = "\"YHOO\""; 9 | 10 | public static String APPLE_STOCK_SYMBOL = "\"AAPL\""; 11 | 12 | public static String GOOGLE_STOCK_SYMBOL = "\"GOOG\""; 13 | 14 | public static String MICROSOFT_STOCK_SYMBOL = "\"MSFT\""; 15 | 16 | public static String SYMBOL = "symbol"; 17 | 18 | public static String GRAPH_LABLES = "graph lables"; 19 | 20 | public static String GRAPH_VALUES = "graph values"; 21 | 22 | public static String CLICK_ACTION = "rajan.udacity.stock.hawk.quotelistwidget.CLICKQUOTE"; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/main/MainMvpView.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.main; 2 | 3 | import java.util.List; 4 | 5 | import rajan.udacity.stock.hawk.data.model.Quote; 6 | import rajan.udacity.stock.hawk.ui.base.MvpView; 7 | 8 | public interface MainMvpView extends MvpView { 9 | 10 | void showStocks(List quoteList); 11 | 12 | void showStocksEmpty(); 13 | 14 | void showStock(Quote quote); 15 | 16 | void showStockDoesNotExist(); 17 | 18 | void showMaterialDialogAddStock(); 19 | 20 | Boolean checkSymbolExistOrNot(String symbol, List quoteList); 21 | 22 | void showStockAlreadyExist(); 23 | 24 | void showChangeInPercent(Boolean changeInPercent); 25 | 26 | void updateChangeInPercent(Boolean changeInPercent); 27 | 28 | void showError(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 16 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/injection/component/ConfigPersistentComponent.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.injection.component; 2 | 3 | import dagger.Component; 4 | import rajan.udacity.stock.hawk.injection.ConfigPersistent; 5 | import rajan.udacity.stock.hawk.injection.module.ActivityModule; 6 | import rajan.udacity.stock.hawk.ui.base.BaseActivity; 7 | 8 | /** 9 | * A dagger component that will live during the lifecycle of an Activity but it won't 10 | * be destroy during configuration changes. Check {@link BaseActivity} to see how this components 11 | * survives configuration changes. 12 | * Use the {@link ConfigPersistent} scope to annotate dependencies that need to survive 13 | * configuration changes (for example Presenters). 14 | */ 15 | @ConfigPersistent 16 | @Component(dependencies = ApplicationComponent.class) 17 | public interface ConfigPersistentComponent { 18 | 19 | ActivityComponent activityComponent(ActivityModule activityModule); 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/util/ViewUtil.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.view.inputmethod.InputMethodManager; 7 | 8 | public final class ViewUtil { 9 | 10 | public static float pxToDp(float px) { 11 | float densityDpi = Resources.getSystem().getDisplayMetrics().densityDpi; 12 | return px / (densityDpi / 160f); 13 | } 14 | 15 | public static int dpToPx(int dp) { 16 | float density = Resources.getSystem().getDisplayMetrics().density; 17 | return Math.round(dp * density); 18 | } 19 | 20 | public static void hideKeyboard(Activity activity) { 21 | InputMethodManager imm = 22 | (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); 23 | imm.hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), 0); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/util/NetworkUtil.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | import retrofit2.adapter.rxjava.HttpException; 8 | 9 | public class NetworkUtil { 10 | 11 | /** 12 | * Returns true if the Throwable is an instance of RetrofitError with an 13 | * http status code equals to the given one. 14 | */ 15 | public static boolean isHttpStatusCode(Throwable throwable, int statusCode) { 16 | return throwable instanceof HttpException 17 | && ((HttpException) throwable).code() == statusCode; 18 | } 19 | 20 | public static boolean isNetworkConnected(Context context) { 21 | ConnectivityManager cm = 22 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 23 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 24 | return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/rajan/udacity/stock/hawk/runner/RxAndroidJUnitRunner.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.runner; 2 | 3 | import android.os.Bundle; 4 | import android.support.test.espresso.Espresso; 5 | 6 | import rx.plugins.RxJavaPlugins; 7 | import rajan.udacity.stock.hawk.util.RxIdlingExecutionHook; 8 | import rajan.udacity.stock.hawk.util.RxIdlingResource; 9 | 10 | /** 11 | * Runner that registers a Espresso Indling resource that handles waiting for 12 | * RxJava Observables to finish. 13 | * WARNING - Using this runner will block the tests if the application uses long-lived hot 14 | * Observables such us event buses, etc. 15 | */ 16 | public class RxAndroidJUnitRunner extends UnlockDeviceAndroidJUnitRunner { 17 | 18 | @Override 19 | public void onCreate(Bundle arguments) { 20 | super.onCreate(arguments); 21 | RxIdlingResource rxIdlingResource = new RxIdlingResource(); 22 | RxJavaPlugins.getInstance() 23 | .registerObservableExecutionHook(new RxIdlingExecutionHook(rxIdlingResource)); 24 | Espresso.registerIdlingResources(rxIdlingResource); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/injection/module/ApplicationModule.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.injection.module; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Module; 9 | import dagger.Provides; 10 | import rajan.udacity.stock.hawk.data.remote.StocksService; 11 | import rajan.udacity.stock.hawk.injection.ApplicationContext; 12 | 13 | /** 14 | * Provide application-level dependencies. 15 | */ 16 | @Module 17 | public class ApplicationModule { 18 | protected final Application mApplication; 19 | 20 | public ApplicationModule(Application application) { 21 | mApplication = application; 22 | } 23 | 24 | @Provides 25 | Application provideApplication() { 26 | return mApplication; 27 | } 28 | 29 | @Provides 30 | @ApplicationContext 31 | Context provideContext() { 32 | return mApplication; 33 | } 34 | 35 | @Provides 36 | @Singleton 37 | StocksService provideRibotsService() { 38 | return StocksService.Creator.newStocksService(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/appwidget_collection.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/util/ActivityUtils.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentTransaction; 7 | 8 | import static com.google.gson.internal.$Gson$Preconditions.checkNotNull; 9 | 10 | /** 11 | * @author RajanMaurya 12 | * 13 | * This provides methods to help Activities load their UI. 14 | */ 15 | public class ActivityUtils { 16 | 17 | /** 18 | * The {@code fragment} is added to the container view with movieId {@code frameId}. The operation is 19 | * performed by the {@code fragmentManager}. 20 | * 21 | */ 22 | public static void addFragmentToActivity (@NonNull FragmentManager fragmentManager, 23 | @NonNull Fragment fragment, int frameId) { 24 | checkNotNull(fragmentManager); 25 | checkNotNull(fragment); 26 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 27 | transaction.add(frameId, fragment); 28 | transaction.commit(); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/base/BasePresenter.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.base; 2 | 3 | /** 4 | * Base class that implements the Presenter interface and provides a base implementation for 5 | * attachView() and detachView(). It also handles keeping a reference to the mvpView that 6 | * can be accessed from the children classes by calling getMvpView(). 7 | */ 8 | public class BasePresenter implements Presenter { 9 | 10 | private T mMvpView; 11 | 12 | @Override 13 | public void attachView(T mvpView) { 14 | mMvpView = mvpView; 15 | } 16 | 17 | @Override 18 | public void detachView() { 19 | mMvpView = null; 20 | } 21 | 22 | public boolean isViewAttached() { 23 | return mMvpView != null; 24 | } 25 | 26 | public T getMvpView() { 27 | return mMvpView; 28 | } 29 | 30 | public void checkViewAttached() { 31 | if (!isViewAttached()) throw new MvpViewNotAttachedException(); 32 | } 33 | 34 | public static class MvpViewNotAttachedException extends RuntimeException { 35 | public MvpViewNotAttachedException() { 36 | super("Please call Presenter.attachView(MvpView) before" + 37 | " requesting data to the Presenter"); 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_stocks_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 19 | 20 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/injection/component/ApplicationComponent.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.injection.component; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Component; 9 | import rajan.udacity.stock.hawk.data.DataManager; 10 | import rajan.udacity.stock.hawk.data.SyncService; 11 | import rajan.udacity.stock.hawk.data.local.DatabaseHelper; 12 | import rajan.udacity.stock.hawk.data.local.PreferencesHelper; 13 | import rajan.udacity.stock.hawk.data.remote.StocksService; 14 | import rajan.udacity.stock.hawk.injection.ApplicationContext; 15 | import rajan.udacity.stock.hawk.injection.module.ApplicationModule; 16 | import rajan.udacity.stock.hawk.ui.widget.ListRemoteViewFactory; 17 | import rajan.udacity.stock.hawk.util.RxEventBus; 18 | 19 | @Singleton 20 | @Component(modules = ApplicationModule.class) 21 | public interface ApplicationComponent { 22 | 23 | void inject(SyncService syncService); 24 | 25 | void inject(ListRemoteViewFactory listRemoteViewFactory); 26 | 27 | @ApplicationContext Context context(); 28 | Application application(); 29 | StocksService ribotsService(); 30 | PreferencesHelper preferencesHelper(); 31 | DatabaseHelper databaseHelper(); 32 | DataManager dataManager(); 33 | RxEventBus eventBus(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/util/AndroidComponentUtil.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import android.app.ActivityManager; 4 | import android.app.ActivityManager.RunningServiceInfo; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.pm.PackageManager; 8 | 9 | public final class AndroidComponentUtil { 10 | 11 | public static void toggleComponent(Context context, Class componentClass, boolean enable) { 12 | ComponentName componentName = new ComponentName(context, componentClass); 13 | PackageManager pm = context.getPackageManager(); 14 | pm.setComponentEnabledSetting(componentName, 15 | enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : 16 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 17 | PackageManager.DONT_KILL_APP); 18 | } 19 | 20 | public static boolean isServiceRunning(Context context, Class serviceClass) { 21 | ActivityManager manager = 22 | (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 23 | for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { 24 | if (serviceClass.getName().equals(service.service.getClassName())) { 25 | return true; 26 | } 27 | } 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/androidTest/java/rajan/udacity/stock/hawk/util/RxIdlingExecutionHook.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import rx.Observable; 4 | import rx.Subscription; 5 | import rx.plugins.RxJavaObservableExecutionHook; 6 | 7 | /** 8 | * RxJava Observable execution hook that handles updating the active subscription 9 | * count for a given Espresso RxIdlingResource. 10 | */ 11 | public class RxIdlingExecutionHook extends RxJavaObservableExecutionHook { 12 | 13 | private RxIdlingResource mRxIdlingResource; 14 | 15 | public RxIdlingExecutionHook(RxIdlingResource rxIdlingResource) { 16 | mRxIdlingResource = rxIdlingResource; 17 | } 18 | 19 | @Override 20 | public Observable.OnSubscribe onSubscribeStart( 21 | Observable observableInstance, Observable.OnSubscribe onSubscribe) { 22 | mRxIdlingResource.incrementActiveSubscriptionsCount(); 23 | return super.onSubscribeStart(observableInstance, onSubscribe); 24 | } 25 | 26 | @Override 27 | public Throwable onSubscribeError(Throwable e) { 28 | mRxIdlingResource.decrementActiveSubscriptionsCount(); 29 | return super.onSubscribeError(e); 30 | } 31 | 32 | @Override 33 | public Subscription onSubscribeReturn(Subscription subscription) { 34 | mRxIdlingResource.decrementActiveSubscriptionsCount(); 35 | return super.onSubscribeReturn(subscription); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/single/Stock.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.single; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | public class Stock implements Parcelable { 9 | 10 | public static final Creator CREATOR = new Creator() { 11 | @Override 12 | public Stock createFromParcel(Parcel source) { 13 | return new Stock(source); 14 | } 15 | 16 | @Override 17 | public Stock[] newArray(int size) { 18 | return new Stock[size]; 19 | } 20 | }; 21 | @SerializedName("query") 22 | Query mQuery = new Query(); 23 | 24 | public Stock() { 25 | } 26 | 27 | protected Stock(Parcel in) { 28 | this.mQuery = in.readParcelable(Query.class.getClassLoader()); 29 | } 30 | 31 | public Query getQuery() { 32 | return mQuery; 33 | } 34 | 35 | public void setQuery(Query query) { 36 | mQuery = query; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "Stock{" + 42 | "mQuery=" + mQuery + 43 | '}'; 44 | } 45 | 46 | @Override 47 | public int describeContents() { 48 | return 0; 49 | } 50 | 51 | @Override 52 | public void writeToParcel(Parcel dest, int flags) { 53 | dest.writeParcelable(this.mQuery, flags); 54 | } 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/multiple/Stocks.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.multiple; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | public class Stocks implements Parcelable { 9 | 10 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 11 | @Override 12 | public Stocks createFromParcel(Parcel source) { 13 | return new Stocks(source); 14 | } 15 | 16 | @Override 17 | public Stocks[] newArray(int size) { 18 | return new Stocks[size]; 19 | } 20 | }; 21 | @SerializedName("query") 22 | Query mQuery = new Query(); 23 | 24 | public Stocks() { 25 | } 26 | 27 | protected Stocks(Parcel in) { 28 | this.mQuery = in.readParcelable(Query.class.getClassLoader()); 29 | } 30 | 31 | public Query getQuery() { 32 | return mQuery; 33 | } 34 | 35 | public void setQuery(Query query) { 36 | mQuery = query; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "Stocks{" + 42 | "mQuery=" + mQuery + 43 | '}'; 44 | } 45 | 46 | @Override 47 | public int describeContents() { 48 | return 0; 49 | } 50 | 51 | @Override 52 | public void writeToParcel(Parcel dest, int flags) { 53 | dest.writeParcelable(this.mQuery, flags); 54 | } 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/widget/WidgetProvider.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.widget; 2 | 3 | import android.appwidget.AppWidgetManager; 4 | import android.appwidget.AppWidgetProvider; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.widget.RemoteViews; 9 | 10 | import rajan.udacity.stock.hawk.R; 11 | 12 | /** 13 | * Created by Rajan Maurya on 30/08/16. 14 | */ 15 | public class WidgetProvider extends AppWidgetProvider { 16 | 17 | @Override 18 | public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 19 | 20 | // Perform this loop procedure for each App Widget that belongs to this provider 21 | for (int appWidgetId : appWidgetIds) { 22 | // Get the layout for the App Widget and attach an on-click listener 23 | // to the button 24 | RemoteViews rv = new RemoteViews(context.getPackageName(), 25 | R.layout.appwidget_collection); 26 | 27 | final Intent intent = new Intent(context, WidgetRemoteViewsService.class); 28 | intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 29 | intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); 30 | rv.setRemoteAdapter(R.id.widget_list, intent); 31 | 32 | appWidgetManager.updateAppWidget(appWidgetId, rv); 33 | } 34 | super.onUpdate(context, appWidgetManager, appWidgetIds); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/commonTest/java/rajan/udacity/stock/hawk/test/common/injection/module/ApplicationTestModule.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.test.common.injection.module; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Module; 9 | import dagger.Provides; 10 | import rajan.udacity.stock.hawk.data.DataManager; 11 | import rajan.udacity.stock.hawk.data.remote.StocksService; 12 | import rajan.udacity.stock.hawk.injection.ApplicationContext; 13 | 14 | import static org.mockito.Mockito.mock; 15 | 16 | /** 17 | * Provides application-level dependencies for an app running on a testing environment 18 | * This allows injecting mocks if necessary. 19 | */ 20 | @Module 21 | public class ApplicationTestModule { 22 | 23 | private final Application mApplication; 24 | 25 | public ApplicationTestModule(Application application) { 26 | mApplication = application; 27 | } 28 | 29 | @Provides 30 | Application provideApplication() { 31 | return mApplication; 32 | } 33 | 34 | @Provides 35 | @ApplicationContext 36 | Context provideContext() { 37 | return mApplication; 38 | } 39 | 40 | /************* MOCKS *************/ 41 | 42 | @Provides 43 | @Singleton 44 | DataManager provideDataManager() { 45 | return mock(DataManager.class); 46 | } 47 | 48 | @Provides 49 | @Singleton 50 | StocksService provideRibotsService() { 51 | return mock(StocksService.class); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/local/PreferencesHelper.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.local; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import javax.inject.Inject; 7 | import javax.inject.Singleton; 8 | 9 | import rajan.udacity.stock.hawk.injection.ApplicationContext; 10 | import rx.Observable; 11 | 12 | @Singleton 13 | public class PreferencesHelper { 14 | 15 | public static final String PREF_FILE_NAME = "stocks_pref_file"; 16 | public static final String PREF_CHANGE_IN_PERCENT = "change_in_percent"; 17 | 18 | private final SharedPreferences mPref; 19 | 20 | @Inject 21 | public PreferencesHelper(@ApplicationContext Context context) { 22 | mPref = context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE); 23 | } 24 | 25 | public void clear() { 26 | mPref.edit().clear().apply(); 27 | } 28 | 29 | public Boolean getChangeInPercentFromPref() { 30 | return mPref.getBoolean(PREF_CHANGE_IN_PERCENT, true); 31 | } 32 | 33 | public Observable getChangeInPercent() { 34 | return Observable.just(mPref.getBoolean(PREF_CHANGE_IN_PERCENT, true)); 35 | } 36 | 37 | public void setChangeInPercent(boolean changeInPercent) { 38 | mPref.edit().putBoolean(PREF_CHANGE_IN_PERCENT, changeInPercent).apply(); 39 | } 40 | 41 | public Observable updateChangeInPercentInPref() { 42 | setChangeInPercent(!getChangeInPercentFromPref()); 43 | return getChangeInPercent(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/test/java/rajan/udacity/stock/hawk/util/RxEventBusTest.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import org.junit.Before; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | 7 | import rx.observers.TestSubscriber; 8 | 9 | public class RxEventBusTest { 10 | 11 | private RxEventBus mEventBus; 12 | 13 | @Rule 14 | // Must be added to every test class that targets app code that uses RxJava 15 | public final RxSchedulersOverrideRule mOverrideSchedulersRule = new RxSchedulersOverrideRule(); 16 | 17 | @Before 18 | public void setUp() { 19 | mEventBus = new RxEventBus(); 20 | } 21 | 22 | @Test 23 | public void postedObjectsAreReceived() { 24 | TestSubscriber testSubscriber = new TestSubscriber<>(); 25 | mEventBus.observable().subscribe(testSubscriber); 26 | 27 | Object event1 = new Object(); 28 | Object event2 = new Object(); 29 | mEventBus.post(event1); 30 | mEventBus.post(event2); 31 | 32 | testSubscriber.assertValues(event1, event2); 33 | } 34 | 35 | @Test 36 | public void filteredObservableOnlyReceivesSomeObjects() { 37 | TestSubscriber testSubscriber = new TestSubscriber<>(); 38 | mEventBus.filteredObservable(String.class).subscribe(testSubscriber); 39 | 40 | String stringEvent = "Event"; 41 | Integer intEvent = 20; 42 | mEventBus.post(stringEvent); 43 | mEventBus.post(intEvent); 44 | 45 | testSubscriber.assertValueCount(1); 46 | testSubscriber.assertValue(stringEvent); 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/multiple/Result.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.multiple; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import rajan.udacity.stock.hawk.data.model.Quote; 10 | 11 | /** 12 | * Created by Rajan Maurya on 07/08/16. 13 | */ 14 | 15 | public class Result implements Parcelable { 16 | 17 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 18 | @Override 19 | public Result createFromParcel(Parcel source) { 20 | return new Result(source); 21 | } 22 | 23 | @Override 24 | public Result[] newArray(int size) { 25 | return new Result[size]; 26 | } 27 | }; 28 | List quote = new ArrayList<>(); 29 | 30 | public Result() { 31 | } 32 | 33 | protected Result(Parcel in) { 34 | this.quote = in.createTypedArrayList(Quote.CREATOR); 35 | } 36 | 37 | public List getQuote() { 38 | return quote; 39 | } 40 | 41 | public void setQuote(List quote) { 42 | this.quote = quote; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "Result{" + 48 | "quote=" + quote + 49 | '}'; 50 | } 51 | 52 | @Override 53 | public int describeContents() { 54 | return 0; 55 | } 56 | 57 | @Override 58 | public void writeToParcel(Parcel dest, int flags) { 59 | dest.writeTypedList(this.quote); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Stock Hawk 3 | 4 | Settings 5 | 6 | OK 7 | Cancel 8 | Sorry 9 | 10 | There aren\'t any Stocks 11 | There was a problem loading the stocks 12 | Uh-oh! This app requires an internet connection 13 | FB 14 | 15 | Change Units 16 | Symbol Search 17 | Search for a Stock Symbol 18 | This stock is already saved! 19 | Stock does not exist. 20 | This is Stock Graph 21 | Finance Chart 22 | Failed To Load Finance Chart Data 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/financechart/Labels.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.financechart; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by Rajan Maurya on 31/08/16. 11 | */ 12 | public class Labels implements Parcelable { 13 | 14 | List labels = new ArrayList<>(); 15 | 16 | public List getLabels() { 17 | return labels; 18 | } 19 | 20 | public void setLabels(List labels) { 21 | this.labels = labels; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "Labels{" + 27 | "labels=" + labels + 28 | '}'; 29 | } 30 | 31 | @Override 32 | public int describeContents() { 33 | return 0; 34 | } 35 | 36 | @Override 37 | public void writeToParcel(Parcel dest, int flags) { 38 | dest.writeList(this.labels); 39 | } 40 | 41 | public Labels() { 42 | } 43 | 44 | protected Labels(Parcel in) { 45 | this.labels = new ArrayList(); 46 | in.readList(this.labels, Double.class.getClassLoader()); 47 | } 48 | 49 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 50 | @Override 51 | public Labels createFromParcel(Parcel source) { 52 | return new Labels(source); 53 | } 54 | 55 | @Override 56 | public Labels[] newArray(int size) { 57 | return new Labels[size]; 58 | } 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/single/Result.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.single; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | import rajan.udacity.stock.hawk.data.model.Quote; 9 | 10 | /** 11 | * Created by Rajan Maurya on 07/08/16. 12 | */ 13 | 14 | public class Result implements Parcelable { 15 | 16 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 17 | @Override 18 | public Result createFromParcel(Parcel source) { 19 | return new Result(source); 20 | } 21 | 22 | @Override 23 | public Result[] newArray(int size) { 24 | return new Result[size]; 25 | } 26 | }; 27 | @SerializedName("quote") 28 | Quote quote = new Quote(); 29 | 30 | public Result() { 31 | } 32 | 33 | protected Result(Parcel in) { 34 | this.quote = in.readParcelable(Quote.class.getClassLoader()); 35 | } 36 | 37 | public Quote getQuote() { 38 | return quote; 39 | } 40 | 41 | public void setQuote(Quote quote) { 42 | this.quote = quote; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "Result{" + 48 | "quote=" + quote + 49 | '}'; 50 | } 51 | 52 | @Override 53 | public int describeContents() { 54 | return 0; 55 | } 56 | 57 | @Override 58 | public void writeToParcel(Parcel dest, int flags) { 59 | dest.writeParcelable(this.quote, flags); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/commonTest/java/rajan/udacity/stock/hawk/test/common/TestDataFactory.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.test.common; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.UUID; 6 | 7 | import rajan.udacity.stock.hawk.data.model.multiple.Stocks; 8 | 9 | /** 10 | * Factory class that makes instances of data models with random field values. 11 | * The aim of this class is to help setting up test fixtures. 12 | */ 13 | public class TestDataFactory { 14 | 15 | public static String randomUuid() { 16 | return UUID.randomUUID().toString(); 17 | } 18 | 19 | /*public static Stocks makeRibot(String uniqueSuffix) { 20 | return Stocks.create(makeProfile(uniqueSuffix)); 21 | }*/ 22 | 23 | public static List makeListRibots(int number) { 24 | List ribots = new ArrayList<>(); 25 | for (int i = 0; i < number; i++) { 26 | //ribots.add(makeRibot(String.valueOf(i))); 27 | } 28 | return ribots; 29 | } 30 | 31 | /*public static Profile makeProfile(String uniqueSuffix) { 32 | return Profile.builder() 33 | .setName(makeName(uniqueSuffix)) 34 | .setEmail("email" + uniqueSuffix + "@ribot.co.uk") 35 | .setDateOfBirth(new Date()) 36 | .setHexColor("#0066FF") 37 | .setAvatar("http://api.ribot.io/images/" + uniqueSuffix) 38 | .setBio(randomUuid()) 39 | .build(); 40 | }*/ 41 | 42 | /*public static Name makeName(String uniqueSuffix) { 43 | return Name.create("Name-" + uniqueSuffix, "Surname-" + uniqueSuffix); 44 | }*/ 45 | 46 | } -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/util/RxEventBus.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | import rx.Observable; 7 | import rx.functions.Func1; 8 | import rx.subjects.PublishSubject; 9 | 10 | /** 11 | * A simple event bus built with RxJava 12 | */ 13 | @Singleton 14 | public class RxEventBus { 15 | 16 | private final PublishSubject mBusSubject; 17 | 18 | @Inject 19 | public RxEventBus() { 20 | mBusSubject = PublishSubject.create(); 21 | } 22 | 23 | /** 24 | * Posts an object (usually an Event) to the bus 25 | */ 26 | public void post(Object event) { 27 | mBusSubject.onNext(event); 28 | } 29 | 30 | /** 31 | * Observable that will emmit everything posted to the event bus. 32 | */ 33 | public Observable observable() { 34 | return mBusSubject; 35 | } 36 | 37 | /** 38 | * Observable that only emits events of a specific class. 39 | * Use this if you only want to subscribe to one type of events. 40 | */ 41 | public Observable filteredObservable(final Class eventClass) { 42 | return mBusSubject.filter(new Func1() { 43 | @Override 44 | public Boolean call(Object event) { 45 | return eventClass.isInstance(event); 46 | } 47 | }).map(new Func1() { 48 | //Safe to cast because of the previous filter 49 | @SuppressWarnings("unchecked") 50 | @Override 51 | public T call(Object event) { 52 | return (T) event; 53 | } 54 | }); 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/stockgraph/StockGraphActivity.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.stockgraph; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | 7 | import rajan.udacity.stock.hawk.R; 8 | import rajan.udacity.stock.hawk.ui.base.BaseActivity; 9 | import rajan.udacity.stock.hawk.util.ActivityUtils; 10 | import rajan.udacity.stock.hawk.util.Constants; 11 | 12 | /** 13 | * Created by Rajan Maurya on 30/08/16. 14 | */ 15 | public class StockGraphActivity extends BaseActivity { 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.frame_container); 21 | 22 | 23 | final Intent intent = getIntent(); 24 | String symbol = intent.getStringExtra(Constants.SYMBOL); 25 | 26 | //Setting the Fragment to FrameLayout 27 | StockGraphFragment mainFragment = (StockGraphFragment) getSupportFragmentManager() 28 | .findFragmentById(R.id.frame_container); 29 | if (mainFragment == null) { 30 | // Create the fragment 31 | mainFragment = StockGraphFragment.newInstance(symbol); 32 | ActivityUtils.addFragmentToActivity( 33 | getSupportFragmentManager(), mainFragment, R.id.frame_container); 34 | } 35 | } 36 | 37 | @Override 38 | public boolean onOptionsItemSelected(MenuItem item) { 39 | switch (item.getItemId()) { 40 | case android.R.id.home: 41 | // Open the navigation drawer when the home icon is selected from the toolbar. 42 | finish(); 43 | return true; 44 | } 45 | return super.onOptionsItemSelected(item); 46 | } 47 | } -------------------------------------------------------------------------------- /config/quality/pmd/pmd-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | Custom ruleset for ribot Android application 8 | 9 | .*/R.java 10 | .*/gen/.* 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/financechart/Date.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.financechart; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | /** 9 | * Created by Rajan Maurya on 31/08/16. 10 | */ 11 | public class Date implements Parcelable { 12 | 13 | @SerializedName("min") 14 | Double mMin; 15 | 16 | @SerializedName("max") 17 | Double mMax; 18 | 19 | public Double getMin() { 20 | return mMin; 21 | } 22 | 23 | public void setMin(Double min) { 24 | mMin = min; 25 | } 26 | 27 | public Double getMax() { 28 | return mMax; 29 | } 30 | 31 | public void setMax(Double max) { 32 | mMax = max; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "Date{" + 38 | "mMin=" + mMin + 39 | ", mMax=" + mMax + 40 | '}'; 41 | } 42 | 43 | 44 | @Override 45 | public int describeContents() { 46 | return 0; 47 | } 48 | 49 | @Override 50 | public void writeToParcel(Parcel dest, int flags) { 51 | dest.writeValue(this.mMin); 52 | dest.writeValue(this.mMax); 53 | } 54 | 55 | public Date() { 56 | } 57 | 58 | protected Date(Parcel in) { 59 | this.mMin = (Double) in.readValue(Double.class.getClassLoader()); 60 | this.mMax = (Double) in.readValue(Double.class.getClassLoader()); 61 | } 62 | 63 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 64 | @Override 65 | public Date createFromParcel(Parcel source) { 66 | return new Date(source); 67 | } 68 | 69 | @Override 70 | public Date[] newArray(int size) { 71 | return new Date[size]; 72 | } 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/StockHawkApplication.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk; 2 | 3 | import com.crashlytics.android.Crashlytics; 4 | import com.facebook.stetho.Stetho; 5 | import com.raizlabs.android.dbflow.config.FlowConfig; 6 | import com.raizlabs.android.dbflow.config.FlowManager; 7 | 8 | import android.app.Application; 9 | import android.content.Context; 10 | 11 | import io.fabric.sdk.android.Fabric; 12 | import rajan.udacity.stock.hawk.injection.component.ApplicationComponent; 13 | import rajan.udacity.stock.hawk.injection.component.DaggerApplicationComponent; 14 | import rajan.udacity.stock.hawk.injection.module.ApplicationModule; 15 | import timber.log.Timber; 16 | 17 | public class StockHawkApplication extends Application { 18 | 19 | ApplicationComponent mApplicationComponent; 20 | 21 | @Override 22 | public void onCreate() { 23 | super.onCreate(); 24 | 25 | if (BuildConfig.DEBUG) { 26 | Timber.plant(new Timber.DebugTree()); 27 | Fabric.with(this, new Crashlytics()); 28 | } 29 | 30 | FlowManager.init(new FlowConfig.Builder(this).build()); 31 | Stetho.initializeWithDefaults(this); 32 | } 33 | 34 | public static StockHawkApplication get(Context context) { 35 | return (StockHawkApplication) context.getApplicationContext(); 36 | } 37 | 38 | public ApplicationComponent getComponent() { 39 | if (mApplicationComponent == null) { 40 | mApplicationComponent = DaggerApplicationComponent.builder() 41 | .applicationModule(new ApplicationModule(this)) 42 | .build(); 43 | } 44 | return mApplicationComponent; 45 | } 46 | 47 | // Needed to replace the component with a test specific one 48 | public void setComponent(ApplicationComponent applicationComponent) { 49 | mApplicationComponent = applicationComponent; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/androidTest/java/rajan/udacity/stock/hawk/runner/UnlockDeviceAndroidJUnitRunner.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.runner; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Application; 5 | import android.app.KeyguardManager; 6 | import android.os.PowerManager; 7 | import android.support.test.runner.AndroidJUnitRunner; 8 | 9 | import static android.content.Context.KEYGUARD_SERVICE; 10 | import static android.content.Context.POWER_SERVICE; 11 | import static android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP; 12 | import static android.os.PowerManager.FULL_WAKE_LOCK; 13 | import static android.os.PowerManager.ON_AFTER_RELEASE; 14 | 15 | /** 16 | * Extension of AndroidJUnitRunner that adds some functionality to unblock the device screen 17 | * before starting the tests. 18 | */ 19 | public class UnlockDeviceAndroidJUnitRunner extends AndroidJUnitRunner { 20 | 21 | private PowerManager.WakeLock mWakeLock; 22 | 23 | @SuppressLint("MissingPermission") 24 | @Override 25 | public void onStart() { 26 | Application application = (Application) getTargetContext().getApplicationContext(); 27 | String simpleName = UnlockDeviceAndroidJUnitRunner.class.getSimpleName(); 28 | // Unlock the device so that the tests can input keystrokes. 29 | ((KeyguardManager) application.getSystemService(KEYGUARD_SERVICE)) 30 | .newKeyguardLock(simpleName) 31 | .disableKeyguard(); 32 | // Wake up the screen. 33 | PowerManager powerManager = ((PowerManager) application.getSystemService(POWER_SERVICE)); 34 | mWakeLock = powerManager.newWakeLock(FULL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP | 35 | ON_AFTER_RELEASE, simpleName); 36 | mWakeLock.acquire(); 37 | super.onStart(); 38 | } 39 | 40 | @Override 41 | public void onDestroy() { 42 | super.onDestroy(); 43 | mWakeLock.release(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/androidTest/java/rajan/udacity/stock/hawk/util/RxIdlingResource.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import android.support.test.espresso.IdlingResource; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | import timber.log.Timber; 8 | 9 | /** 10 | * Espresso Idling resource that handles waiting for RxJava Observables executions. 11 | * This class must be used with RxIdlingExecutionHook. 12 | * Before registering this idling resource you must: 13 | * 1. Create an instance of RxIdlingExecutionHook by passing an instance of this class. 14 | * 2. Register RxIdlingExecutionHook with the RxJavaPlugins using registerObservableExecutionHook() 15 | * 3. Register this idle resource with Espresso using Espresso.registerIdlingResources() 16 | */ 17 | public class RxIdlingResource implements IdlingResource { 18 | 19 | private final AtomicInteger mActiveSubscriptionsCount = new AtomicInteger(0); 20 | private ResourceCallback mResourceCallback; 21 | 22 | @Override 23 | public String getName() { 24 | return getClass().getSimpleName(); 25 | } 26 | 27 | @Override 28 | public boolean isIdleNow() { 29 | return mActiveSubscriptionsCount.get() == 0; 30 | } 31 | 32 | @Override 33 | public void registerIdleTransitionCallback(ResourceCallback callback) { 34 | mResourceCallback = callback; 35 | } 36 | 37 | public void incrementActiveSubscriptionsCount() { 38 | int count = mActiveSubscriptionsCount.incrementAndGet(); 39 | Timber.i("Active subscriptions count increased to %d", count); 40 | } 41 | 42 | public void decrementActiveSubscriptionsCount() { 43 | int count = mActiveSubscriptionsCount.decrementAndGet(); 44 | Timber.i("Active subscriptions count decreased to %d", count); 45 | if (isIdleNow()) { 46 | Timber.i("There is no active subscriptions, transitioning to Idle"); 47 | mResourceCallback.onTransitionToIdle(); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/appwidget_collection_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 26 | 27 | 35 | 36 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/commonTest/java/rajan/udacity/stock/hawk/test/common/TestComponentRule.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.test.common; 2 | 3 | import android.content.Context; 4 | 5 | import org.junit.rules.TestRule; 6 | import org.junit.runner.Description; 7 | import org.junit.runners.model.Statement; 8 | 9 | import rajan.udacity.stock.hawk.StockHawkApplication; 10 | import rajan.udacity.stock.hawk.data.DataManager; 11 | import rajan.udacity.stock.hawk.test.common.injection.component.DaggerTestComponent; 12 | import rajan.udacity.stock.hawk.test.common.injection.component.TestComponent; 13 | import rajan.udacity.stock.hawk.test.common.injection.module.ApplicationTestModule; 14 | 15 | /** 16 | * Test rule that creates and sets a Dagger TestComponent into the application overriding the 17 | * existing application component. 18 | * Use this rule in your test case in order for the app to use mock dependencies. 19 | * It also exposes some of the dependencies so they can be easily accessed from the tests, e.g. to 20 | * stub mocks etc. 21 | */ 22 | public class TestComponentRule implements TestRule { 23 | 24 | private final TestComponent mTestComponent; 25 | private final Context mContext; 26 | 27 | public TestComponentRule(Context context) { 28 | mContext = context; 29 | StockHawkApplication application = StockHawkApplication.get(context); 30 | mTestComponent = DaggerTestComponent.builder() 31 | .applicationTestModule(new ApplicationTestModule(application)) 32 | .build(); 33 | } 34 | 35 | public Context getContext() { 36 | return mContext; 37 | } 38 | 39 | public DataManager getMockDataManager() { 40 | return mTestComponent.dataManager(); 41 | } 42 | 43 | @Override 44 | public Statement apply(final Statement base, Description description) { 45 | return new Statement() { 46 | @Override 47 | public void evaluate() throws Throwable { 48 | StockHawkApplication application = StockHawkApplication.get(mContext); 49 | application.setComponent(mTestComponent); 50 | base.evaluate(); 51 | application.setComponent(null); 52 | } 53 | }; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/touch_helper/SimpleItemTouchHelperCallback.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.touch_helper; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.support.v7.widget.helper.ItemTouchHelper; 5 | 6 | /** 7 | * Created by sam_chordas on 10/6/15. 8 | * credit to Paul Burke (ipaulpro) 9 | * this class enables swipe to delete in RecyclerView 10 | */ 11 | public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback { 12 | public static final float ALPHA_FULL = 1.0f; 13 | private final ItemTouchHelperAdapter mAdapter; 14 | 15 | public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) { 16 | mAdapter = adapter; 17 | } 18 | 19 | @Override 20 | public boolean isItemViewSwipeEnabled() { 21 | return true; 22 | } 23 | 24 | @Override 25 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 26 | final int dragFlags = 0; 27 | final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; 28 | return makeMovementFlags(dragFlags, swipeFlags); 29 | } 30 | 31 | @Override 32 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder sourceViewHolder, RecyclerView.ViewHolder targetViewHolder) { 33 | return true; 34 | } 35 | 36 | @Override 37 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) { 38 | mAdapter.onItemDismiss(viewHolder.getAdapterPosition()); 39 | } 40 | 41 | 42 | @Override 43 | public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { 44 | if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { 45 | ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; 46 | itemViewHolder.onItemSelected(); 47 | } 48 | 49 | super.onSelectedChanged(viewHolder, actionState); 50 | } 51 | 52 | @Override 53 | public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 54 | super.clearView(recyclerView, viewHolder); 55 | 56 | ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; 57 | itemViewHolder.onItemClear(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/test/java/rajan/udacity/stock/hawk/DatabaseHelperTest.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk; 2 | 3 | import org.junit.Rule; 4 | import org.junit.runner.RunWith; 5 | import org.robolectric.RobolectricGradleTestRunner; 6 | import org.robolectric.annotation.Config; 7 | 8 | import rajan.udacity.stock.hawk.util.DefaultConfig; 9 | import rajan.udacity.stock.hawk.util.RxSchedulersOverrideRule; 10 | 11 | /** 12 | * Unit tests integration with a SQLite Database using Robolectric 13 | */ 14 | @RunWith(RobolectricGradleTestRunner.class) 15 | @Config(constants = BuildConfig.class, sdk = DefaultConfig.EMULATE_SDK) 16 | public class DatabaseHelperTest { 17 | 18 | /*private final DatabaseHelper mDatabaseHelper = 19 | new DatabaseHelper(new DbOpenHelper(RuntimeEnvironment.application));*/ 20 | 21 | @Rule 22 | public final RxSchedulersOverrideRule mOverrideSchedulersRule = new RxSchedulersOverrideRule(); 23 | 24 | /* @Test 25 | public void setStocks() { 26 | Stocks ribot1 = TestDataFactory.makeRibot("r1"); 27 | Stocks ribot2 = TestDataFactory.makeRibot("r2"); 28 | List ribots = Arrays.asList(ribot1, ribot2); 29 | 30 | TestSubscriber result = new TestSubscriber<>(); 31 | mDatabaseHelper.setStocks(ribots).subscribe(result); 32 | result.assertNoErrors(); 33 | result.assertReceivedOnNext(ribots); 34 | 35 | Cursor cursor = mDatabaseHelper.getBriteDb() 36 | .query("SELECT * FROM " + Db.RibotProfileTable.TABLE_NAME); 37 | assertEquals(2, cursor.getCount()); 38 | for (Stocks ribot : ribots) { 39 | cursor.moveToNext(); 40 | assertEquals(ribot.profile(), Db.RibotProfileTable.parseCursor(cursor)); 41 | } 42 | } 43 | 44 | @Test 45 | public void getStocks() { 46 | Stocks ribot1 = TestDataFactory.makeRibot("r1"); 47 | Stocks ribot2 = TestDataFactory.makeRibot("r2"); 48 | List ribots = Arrays.asList(ribot1, ribot2); 49 | 50 | mDatabaseHelper.setStocks(ribots).subscribe(); 51 | 52 | TestSubscriber> result = new TestSubscriber<>(); 53 | mDatabaseHelper.getStocks().subscribe(result); 54 | result.assertNoErrors(); 55 | result.assertValue(ribots); 56 | }*/ 57 | 58 | } -------------------------------------------------------------------------------- /app/src/test/java/rajan/udacity/stock/hawk/util/RxSchedulersOverrideRule.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import org.junit.rules.TestRule; 4 | import org.junit.runner.Description; 5 | import org.junit.runners.model.Statement; 6 | 7 | import rx.Scheduler; 8 | import rx.android.plugins.RxAndroidPlugins; 9 | import rx.android.plugins.RxAndroidSchedulersHook; 10 | import rx.plugins.RxJavaPlugins; 11 | import rx.plugins.RxJavaSchedulersHook; 12 | import rx.schedulers.Schedulers; 13 | 14 | /** 15 | * This rule registers SchedulerHooks for RxJava and RxAndroid to ensure that subscriptions 16 | * always subscribeOn and observeOn Schedulers.immediate(). 17 | * Warning, this rule will reset RxAndroidPlugins and RxJavaPlugins before and after each test so 18 | * if the application code uses RxJava plugins this may affect the behaviour of the testing method. 19 | */ 20 | public class RxSchedulersOverrideRule implements TestRule { 21 | 22 | private final RxJavaSchedulersHook mRxJavaSchedulersHook = new RxJavaSchedulersHook() { 23 | @Override 24 | public Scheduler getIOScheduler() { 25 | return Schedulers.immediate(); 26 | } 27 | 28 | @Override 29 | public Scheduler getNewThreadScheduler() { 30 | return Schedulers.immediate(); 31 | } 32 | }; 33 | 34 | private final RxAndroidSchedulersHook mRxAndroidSchedulersHook = new RxAndroidSchedulersHook() { 35 | @Override 36 | public Scheduler getMainThreadScheduler() { 37 | return Schedulers.immediate(); 38 | } 39 | }; 40 | 41 | @Override 42 | public Statement apply(final Statement base, Description description) { 43 | return new Statement() { 44 | @Override 45 | public void evaluate() throws Throwable { 46 | RxAndroidPlugins.getInstance().reset(); 47 | RxAndroidPlugins.getInstance().registerSchedulersHook(mRxAndroidSchedulersHook); 48 | 49 | RxJavaPlugins.getInstance().reset(); 50 | RxJavaPlugins.getInstance().registerSchedulersHook(mRxJavaSchedulersHook); 51 | 52 | base.evaluate(); 53 | 54 | RxAndroidPlugins.getInstance().reset(); 55 | RxJavaPlugins.getInstance().reset(); 56 | } 57 | }; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_stock.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 28 | 29 | 34 | 35 | 42 | 43 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/util/DialogFactory.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import android.app.Dialog; 4 | import android.app.ProgressDialog; 5 | import android.content.Context; 6 | import android.support.annotation.StringRes; 7 | import android.support.v7.app.AlertDialog; 8 | 9 | import rajan.udacity.stock.hawk.R; 10 | 11 | public final class DialogFactory { 12 | 13 | public static Dialog createSimpleOkErrorDialog(Context context, String title, String message) { 14 | AlertDialog.Builder alertDialog = new AlertDialog.Builder(context) 15 | .setTitle(title) 16 | .setMessage(message) 17 | .setNeutralButton(R.string.dialog_action_ok, null); 18 | return alertDialog.create(); 19 | } 20 | 21 | public static Dialog createSimpleOkErrorDialog(Context context, 22 | @StringRes int titleResource, 23 | @StringRes int messageResource) { 24 | 25 | return createSimpleOkErrorDialog(context, 26 | context.getString(titleResource), 27 | context.getString(messageResource)); 28 | } 29 | 30 | public static Dialog createGenericErrorDialog(Context context, String message) { 31 | AlertDialog.Builder alertDialog = new AlertDialog.Builder(context) 32 | .setTitle(context.getString(R.string.dialog_error_title)) 33 | .setMessage(message) 34 | .setNeutralButton(R.string.dialog_action_ok, null); 35 | return alertDialog.create(); 36 | } 37 | 38 | public static Dialog createGenericErrorDialog(Context context, @StringRes int messageResource) { 39 | return createGenericErrorDialog(context, context.getString(messageResource)); 40 | } 41 | 42 | public static ProgressDialog createProgressDialog(Context context, String message) { 43 | ProgressDialog progressDialog = new ProgressDialog(context); 44 | progressDialog.setMessage(message); 45 | return progressDialog; 46 | } 47 | 48 | public static ProgressDialog createProgressDialog(Context context, 49 | @StringRes int messageResource) { 50 | return createProgressDialog(context, context.getString(messageResource)); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/remote/UrlBuilder.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.remote; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.URLEncoder; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by Rajan Maurya on 15/08/16. 10 | */ 11 | public class UrlBuilder { 12 | 13 | public static StringBuilder urlBuilder = new StringBuilder(); 14 | 15 | /** 16 | * This Method Add the Select Yahoo query to Request URl 17 | */ 18 | public static void addYahooSelectQuotesQuery() { 19 | 20 | try { 21 | urlBuilder.append(URLEncoder.encode("select * from yahoo.finance.quotes where symbol " 22 | + "in (", "UTF-8")); 23 | } catch (UnsupportedEncodingException e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | 28 | /** 29 | * This Method Add the Symbol to the Query. This Method Takes the Array of Symbol and 30 | * encode with URLEncoder.encode and adding to the Query. 31 | * 32 | * @param symbol Symbol of the Stocks 33 | */ 34 | public static void addStockSymbol(String... symbol) { 35 | List stocksSymbols = Arrays.asList(symbol); 36 | if (stocksSymbols.size() != 0) { 37 | for (int i = 0; i < stocksSymbols.size(); ++i) { 38 | if (i - (stocksSymbols.size() - 1) == 0) { 39 | try { 40 | urlBuilder.append(URLEncoder.encode(stocksSymbols.get(i) + ")", "UTF-8")); 41 | } catch (UnsupportedEncodingException e) { 42 | e.printStackTrace(); 43 | } 44 | } else { 45 | try { 46 | urlBuilder.append(URLEncoder.encode(stocksSymbols.get(i) + ",", "UTF-8")); 47 | } catch (UnsupportedEncodingException e) { 48 | e.printStackTrace(); 49 | } 50 | } 51 | } 52 | } 53 | 54 | 55 | } 56 | 57 | /** 58 | * This Method Building the Symbol Url Query 59 | * 60 | * @param symbols Stocks Symbol 61 | * @return String Query 62 | */ 63 | public static String queryBuilder(String... symbols) { 64 | urlBuilder.setLength(0); 65 | addYahooSelectQuotesQuery(); 66 | addStockSymbol(symbols); 67 | return urlBuilder.toString(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/stockgraph/StockGraphPresenter.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.stockgraph; 2 | 3 | import javax.inject.Inject; 4 | 5 | import okhttp3.ResponseBody; 6 | import rajan.udacity.stock.hawk.data.DataManager; 7 | import rajan.udacity.stock.hawk.injection.ConfigPersistent; 8 | import rajan.udacity.stock.hawk.ui.base.BasePresenter; 9 | import rajan.udacity.stock.hawk.util.Utils; 10 | import rx.Subscriber; 11 | import rx.Subscription; 12 | import rx.android.schedulers.AndroidSchedulers; 13 | import rx.schedulers.Schedulers; 14 | 15 | /** 16 | * Created by Rajan Maurya on 30/08/16. 17 | */ 18 | @ConfigPersistent 19 | public class StockGraphPresenter extends BasePresenter { 20 | 21 | private final DataManager mDataManager; 22 | private Subscription mSubscription; 23 | 24 | @Inject 25 | public StockGraphPresenter(DataManager dataManager) { 26 | mDataManager = dataManager; 27 | } 28 | 29 | @Override 30 | public void attachView(StockGraphMvpView mvpView) { 31 | super.attachView(mvpView); 32 | } 33 | 34 | @Override 35 | public void detachView() { 36 | super.detachView(); 37 | if (mSubscription != null) mSubscription.unsubscribe(); 38 | } 39 | 40 | public void loadFinanceChartData(String symbol) { 41 | checkViewAttached(); 42 | getMvpView().showProgressBar(true); 43 | if (mSubscription != null) mSubscription.unsubscribe(); 44 | mSubscription = mDataManager.getFinanceChartData(symbol) 45 | .observeOn(AndroidSchedulers.mainThread()) 46 | .subscribeOn(Schedulers.io()) 47 | .subscribe(new Subscriber() { 48 | @Override 49 | public void onCompleted() { 50 | } 51 | 52 | @Override 53 | public void onError(Throwable e) { 54 | getMvpView().showProgressBar(false); 55 | getMvpView().showError(); 56 | } 57 | 58 | @Override 59 | public void onNext(ResponseBody financeChartData) { 60 | getMvpView().showFinanceChartData(Utils 61 | .getFinanceChartCallback(financeChartData)); 62 | getMvpView().showProgressBar(false); 63 | } 64 | }); 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/widget/ListRemoteViewFactoryPresenter.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.widget; 2 | 3 | import javax.inject.Inject; 4 | 5 | import rajan.udacity.stock.hawk.data.DataManager; 6 | import rajan.udacity.stock.hawk.data.model.multiple.Stocks; 7 | import rajan.udacity.stock.hawk.ui.base.BasePresenter; 8 | import rajan.udacity.stock.hawk.util.RxUtil; 9 | import rx.Subscriber; 10 | import rx.Subscription; 11 | import rx.android.schedulers.AndroidSchedulers; 12 | import rx.schedulers.Schedulers; 13 | import timber.log.Timber; 14 | 15 | /** 16 | * Created by Rajan Maurya on 30/08/16. 17 | */ 18 | public class ListRemoteViewFactoryPresenter extends BasePresenter { 19 | 20 | private final DataManager mDataManager; 21 | private Subscription mSubscription; 22 | 23 | @Inject 24 | public ListRemoteViewFactoryPresenter(DataManager dataManager) { 25 | mDataManager = dataManager; 26 | } 27 | 28 | @Override 29 | public void attachView(ListRemoteViewFactoryMvpView mvpView) { 30 | super.attachView(mvpView); 31 | } 32 | 33 | @Override 34 | public void detachView() { 35 | super.detachView(); 36 | if (mSubscription != null) mSubscription.unsubscribe(); 37 | } 38 | 39 | public void loadStocks() { 40 | checkViewAttached(); 41 | RxUtil.unsubscribe(mSubscription); 42 | mSubscription = mDataManager.getStocks() 43 | .observeOn(AndroidSchedulers.mainThread()) 44 | .subscribeOn(Schedulers.io()) 45 | .subscribe(new Subscriber() { 46 | @Override 47 | public void onCompleted() { 48 | } 49 | 50 | @Override 51 | public void onError(Throwable e) { 52 | Timber.e(e, "There was an error loading the stocks."); 53 | getMvpView().showError(); 54 | } 55 | 56 | @Override 57 | public void onNext(Stocks stocks) { 58 | showStocks(stocks); 59 | } 60 | }); 61 | } 62 | 63 | private void showStocks(Stocks stocks) { 64 | if (stocks == null) { 65 | getMvpView().showStocksEmpty(); 66 | } else { 67 | getMvpView().showStocks(stocks.getQuery().getResult().getQuote()); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /app/src/test/java/rajan/udacity/stock/hawk/MainPresenterTest.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.Rule; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.Mock; 8 | import org.mockito.runners.MockitoJUnitRunner; 9 | 10 | import rajan.udacity.stock.hawk.data.DataManager; 11 | import rajan.udacity.stock.hawk.ui.main.MainMvpView; 12 | import rajan.udacity.stock.hawk.ui.main.MainPresenter; 13 | import rajan.udacity.stock.hawk.util.RxSchedulersOverrideRule; 14 | 15 | @RunWith(MockitoJUnitRunner.class) 16 | public class MainPresenterTest { 17 | 18 | @Mock MainMvpView mMockMainMvpView; 19 | @Mock DataManager mMockDataManager; 20 | private MainPresenter mMainPresenter; 21 | 22 | @Rule 23 | public final RxSchedulersOverrideRule mOverrideSchedulersRule = new RxSchedulersOverrideRule(); 24 | 25 | @Before 26 | public void setUp() { 27 | mMainPresenter = new MainPresenter(mMockDataManager); 28 | mMainPresenter.attachView(mMockMainMvpView); 29 | } 30 | 31 | @After 32 | public void tearDown() { 33 | mMainPresenter.detachView(); 34 | } 35 | 36 | /*@Test 37 | public void loadRibotsReturnsRibots() { 38 | List ribots = TestDataFactory.makeListRibots(10); 39 | when(mMockDataManager.getStocks()) 40 | .thenReturn(Observable.just(ribots)); 41 | 42 | mMainPresenter.loadStocks(); 43 | verify(mMockMainMvpView).showStocks(ribots); 44 | verify(mMockMainMvpView, never()).showStocksEmpty(); 45 | verify(mMockMainMvpView, never()).showError(); 46 | } 47 | 48 | @Test 49 | public void loadRibotsReturnsEmptyList() { 50 | when(mMockDataManager.getStocks()) 51 | .thenReturn(Observable.just(Collections.emptyList())); 52 | 53 | mMainPresenter.loadStocks(); 54 | verify(mMockMainMvpView).showStocksEmpty(); 55 | verify(mMockMainMvpView, never()).showStocks(anyListOf(Stocks.class)); 56 | verify(mMockMainMvpView, never()).showError(); 57 | } 58 | 59 | @Test 60 | public void loadRibotsFails() { 61 | when(mMockDataManager.getStocks()) 62 | .thenReturn(Observable.>error(new RuntimeException())); 63 | 64 | mMainPresenter.loadStocks(); 65 | verify(mMockMainMvpView).showError(); 66 | verify(mMockMainMvpView, never()).showStocksEmpty(); 67 | verify(mMockMainMvpView, never()).showStocks(anyListOf(Stocks.class)); 68 | }*/ 69 | 70 | } -------------------------------------------------------------------------------- /config/quality/quality.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Set up Checkstyle, Findbugs and PMD to perform extensive code analysis. 3 | * 4 | * Gradle tasks added: 5 | * - checkstyle 6 | * - findbugs 7 | * - pmd 8 | * 9 | * The three tasks above are added as dependencies of the check task so running check will 10 | * run all of them. 11 | */ 12 | 13 | apply plugin: 'checkstyle' 14 | apply plugin: 'findbugs' 15 | apply plugin: 'pmd' 16 | 17 | dependencies { 18 | checkstyle 'com.puppycrawl.tools:checkstyle:6.5' 19 | } 20 | 21 | def qualityConfigDir = "$project.rootDir/config/quality"; 22 | def reportsDir = "$project.buildDir/reports" 23 | 24 | check.dependsOn 'checkstyle', 'findbugs', 'pmd' 25 | 26 | task checkstyle(type: Checkstyle, group: 'Verification', description: 'Runs code style checks') { 27 | configFile file("$qualityConfigDir/checkstyle/checkstyle-config.xml") 28 | source 'src' 29 | include '**/*.java' 30 | 31 | reports { 32 | xml.enabled = true 33 | xml { 34 | destination "$reportsDir/checkstyle/checkstyle.xml" 35 | } 36 | } 37 | 38 | classpath = files( ) 39 | } 40 | 41 | task findbugs(type: FindBugs, 42 | group: 'Verification', 43 | description: 'Inspect java bytecode for bugs', 44 | dependsOn: ['compileDebugSources','compileReleaseSources']) { 45 | 46 | ignoreFailures = false 47 | effort = "max" 48 | reportLevel = "high" 49 | excludeFilter = new File("$qualityConfigDir/findbugs/android-exclude-filter.xml") 50 | classes = files("$project.rootDir/app/build/intermediates/classes") 51 | 52 | source 'src' 53 | include '**/*.java' 54 | exclude '**/gen/**' 55 | 56 | reports { 57 | xml.enabled = true 58 | html.enabled = false 59 | xml { 60 | destination "$reportsDir/findbugs/findbugs.xml" 61 | } 62 | html { 63 | destination "$reportsDir/findbugs/findbugs.html" 64 | } 65 | } 66 | 67 | classpath = files() 68 | } 69 | 70 | 71 | task pmd(type: Pmd, group: 'Verification', description: 'Inspect sourcecode for bugs') { 72 | ruleSetFiles = files("$qualityConfigDir/pmd/pmd-ruleset.xml") 73 | ignoreFailures = false 74 | ruleSets = [] 75 | 76 | source 'src' 77 | include '**/*.java' 78 | exclude '**/gen/**' 79 | 80 | reports { 81 | xml.enabled = true 82 | html.enabled = true 83 | xml { 84 | destination "$reportsDir/pmd/pmd.xml" 85 | } 86 | html { 87 | destination "$reportsDir/pmd/pmd.html" 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # ButterKnife rules 20 | -keep class butterknife.** { *; } 21 | -dontwarn butterknife.internal.** 22 | -keep class **$$ViewBinder { *; } 23 | 24 | -keepclasseswithmembernames class * { 25 | @butterknife.* ; 26 | } 27 | 28 | -keepclasseswithmembernames class * { 29 | @butterknife.* ; 30 | } 31 | 32 | # Retrofit rules 33 | -dontwarn retrofit.** 34 | -keep class retrofit.** { *; } 35 | -keepattributes Signature 36 | -keepattributes Exceptions 37 | 38 | # OkHttp rules 39 | -dontwarn okio.** 40 | -dontwarn com.squareup.okhttp.** 41 | 42 | # Otto rules 43 | -keepclassmembers class ** { 44 | @com.squareup.otto.Subscribe public *; 45 | @com.squareup.otto.Produce public *; 46 | } 47 | 48 | # RxJava rules 49 | # RxAndroid will soon ship with rules so this may not be needed in the future 50 | # https://github.com/ReactiveX/RxAndroid/issues/219 51 | -dontwarn sun.misc.Unsafe 52 | -keep class rx.internal.util.unsafe.** { *; } 53 | 54 | # EasyAdapter rules 55 | -keepclassmembers class * extends uk.co.ribot.easyadapter.ItemViewHolder { 56 | public (...); 57 | } 58 | 59 | # Gson rules 60 | -keepattributes Signature 61 | -keep class sun.misc.Unsafe { *; } 62 | # TODO change to match your package model 63 | # Keep non static or private fields of models so Gson can find their names 64 | -keepclassmembers class uk.co.ribot.androidboilerplate.data.model.** { 65 | !static !private ; 66 | } 67 | # TODO change to match your Retrofit services (only if using inner models withing the service) 68 | # Some models used by gson are inner classes inside the retrofit service 69 | -keepclassmembers class uk.co.ribot.androidboilerplate.data.remote.RibotsService$** { 70 | !static !private ; 71 | } 72 | 73 | # Produces useful obfuscated stack traces 74 | # http://proguard.sourceforge.net/manual/examples.html#stacktrace 75 | -renamesourcefileattribute SourceFile 76 | -keepattributes SourceFile,LineNumberTable 77 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/single/Query.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.single; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | /** 9 | * Created by Rajan Maurya on 07/08/16. 10 | */ 11 | public class Query implements Parcelable { 12 | 13 | public static final Creator CREATOR = new Creator() { 14 | @Override 15 | public Query createFromParcel(Parcel source) { 16 | return new Query(source); 17 | } 18 | 19 | @Override 20 | public Query[] newArray(int size) { 21 | return new Query[size]; 22 | } 23 | }; 24 | @SerializedName("count") 25 | Integer mCount = 0; 26 | @SerializedName("created") 27 | String mCreated; 28 | @SerializedName("lang") 29 | String mLang; 30 | @SerializedName("results") 31 | Result mResult = new Result(); 32 | 33 | public Query() { 34 | } 35 | 36 | protected Query(Parcel in) { 37 | this.mCount = (Integer) in.readValue(Integer.class.getClassLoader()); 38 | this.mCreated = in.readString(); 39 | this.mLang = in.readString(); 40 | this.mResult = in.readParcelable(Result.class.getClassLoader()); 41 | } 42 | 43 | public Integer getCount() { 44 | return mCount; 45 | } 46 | 47 | public void setCount(Integer count) { 48 | mCount = count; 49 | } 50 | 51 | public String getCreated() { 52 | return mCreated; 53 | } 54 | 55 | public void setCreated(String created) { 56 | mCreated = created; 57 | } 58 | 59 | public String getLang() { 60 | return mLang; 61 | } 62 | 63 | public void setLang(String lang) { 64 | mLang = lang; 65 | } 66 | 67 | public Result getResult() { 68 | return mResult; 69 | } 70 | 71 | public void setResult(Result result) { 72 | mResult = result; 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "Query{" + 78 | "mCount=" + mCount + 79 | ", mCreated='" + mCreated + '\'' + 80 | ", mLang='" + mLang + '\'' + 81 | ", mResult=" + mResult + 82 | '}'; 83 | } 84 | 85 | @Override 86 | public int describeContents() { 87 | return 0; 88 | } 89 | 90 | @Override 91 | public void writeToParcel(Parcel dest, int flags) { 92 | dest.writeValue(this.mCount); 93 | dest.writeString(this.mCreated); 94 | dest.writeString(this.mLang); 95 | dest.writeParcelable(this.mResult, flags); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/multiple/Query.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.multiple; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | /** 9 | * Created by Rajan Maurya on 07/08/16. 10 | */ 11 | public class Query implements Parcelable { 12 | 13 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 14 | @Override 15 | public Query createFromParcel(Parcel source) { 16 | return new Query(source); 17 | } 18 | 19 | @Override 20 | public Query[] newArray(int size) { 21 | return new Query[size]; 22 | } 23 | }; 24 | @SerializedName("count") 25 | Integer mCount = 0; 26 | @SerializedName("created") 27 | String mCreated; 28 | @SerializedName("lang") 29 | String mLang; 30 | @SerializedName("results") 31 | Result mResult = new Result(); 32 | 33 | public Query() { 34 | } 35 | 36 | protected Query(Parcel in) { 37 | this.mCount = (Integer) in.readValue(Integer.class.getClassLoader()); 38 | this.mCreated = in.readString(); 39 | this.mLang = in.readString(); 40 | this.mResult = in.readParcelable(Result.class.getClassLoader()); 41 | } 42 | 43 | public Integer getCount() { 44 | return mCount; 45 | } 46 | 47 | public void setCount(Integer count) { 48 | mCount = count; 49 | } 50 | 51 | public String getCreated() { 52 | return mCreated; 53 | } 54 | 55 | public void setCreated(String created) { 56 | mCreated = created; 57 | } 58 | 59 | public String getLang() { 60 | return mLang; 61 | } 62 | 63 | public void setLang(String lang) { 64 | mLang = lang; 65 | } 66 | 67 | public Result getResult() { 68 | return mResult; 69 | } 70 | 71 | public void setResult(Result result) { 72 | mResult = result; 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "Query{" + 78 | "mCount=" + mCount + 79 | ", mCreated='" + mCreated + '\'' + 80 | ", mLang='" + mLang + '\'' + 81 | ", mResult=" + mResult + 82 | '}'; 83 | } 84 | 85 | @Override 86 | public int describeContents() { 87 | return 0; 88 | } 89 | 90 | @Override 91 | public void writeToParcel(Parcel dest, int flags) { 92 | dest.writeValue(this.mCount); 93 | dest.writeString(this.mCreated); 94 | dest.writeString(this.mLang); 95 | dest.writeParcelable(this.mResult, flags); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/remote/StocksService.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.remote; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.ResponseBody; 8 | import okhttp3.logging.HttpLoggingInterceptor; 9 | import rajan.udacity.stock.hawk.BuildConfig; 10 | import rajan.udacity.stock.hawk.data.ApiEndPoint; 11 | import rajan.udacity.stock.hawk.data.model.multiple.Stocks; 12 | import rajan.udacity.stock.hawk.data.model.single.Stock; 13 | import retrofit2.Retrofit; 14 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 15 | import retrofit2.converter.gson.GsonConverterFactory; 16 | import retrofit2.http.GET; 17 | import retrofit2.http.Path; 18 | import retrofit2.http.Query; 19 | import rx.Observable; 20 | 21 | public interface StocksService { 22 | 23 | String ENDPOINT = "https://query.yahooapis.com/v1/public/"; 24 | String FINANCE_CART_API_ENDPOINT = "https://chartapi.finance.yahoo.com/instrument/1.0/"; 25 | 26 | @GET(ApiEndPoint.YAHOO_QUERY_LANGUAGE + ApiEndPoint.RESPONSE_FORMAT) 27 | Observable getStocks(@Query(value = "q", encoded = true) String q); 28 | 29 | @GET(ApiEndPoint.YAHOO_QUERY_LANGUAGE + ApiEndPoint.RESPONSE_FORMAT) 30 | Observable getStock(@Query(value = "q", encoded = true) String q); 31 | 32 | @GET(FINANCE_CART_API_ENDPOINT + "{symbol}/" + "chartdata;type=quote;range=5y/json" ) 33 | Observable getFinanceChartData(@Path("symbol") String symbol); 34 | 35 | /******** Helper class that sets up a new services *******/ 36 | class Creator { 37 | 38 | public static StocksService newStocksService() { 39 | 40 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 41 | logging.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY 42 | : HttpLoggingInterceptor.Level.NONE); 43 | 44 | OkHttpClient okHttpClient = new OkHttpClient.Builder() 45 | .addInterceptor(logging) 46 | .build(); 47 | 48 | Gson gson = new GsonBuilder() 49 | .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") 50 | .setLenient() 51 | .create(); 52 | 53 | Retrofit retrofit = new Retrofit.Builder() 54 | .baseUrl(StocksService.ENDPOINT) 55 | .client(okHttpClient) 56 | .addConverterFactory(GsonConverterFactory.create(gson)) 57 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 58 | .build(); 59 | return retrofit.create(StocksService.class); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/util/Utils.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.util; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import android.content.Context; 6 | 7 | import java.io.IOException; 8 | import java.text.ParseException; 9 | import java.text.SimpleDateFormat; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Locale; 13 | 14 | import okhttp3.ResponseBody; 15 | import rajan.udacity.stock.hawk.data.model.financechart.FinanceChartCallBack; 16 | import rajan.udacity.stock.hawk.data.model.financechart.Series; 17 | import rajan.udacity.stock.hawk.data.remote.UrlBuilder; 18 | 19 | /** 20 | * Created by Rajan Maurya on 25/08/16. 21 | */ 22 | public class Utils { 23 | 24 | public static String getYahooStocksQuery() { 25 | return UrlBuilder.queryBuilder( 26 | Constants.YAHOO_STOCK_SYMBOL, 27 | Constants.APPLE_STOCK_SYMBOL, 28 | Constants.GOOGLE_STOCK_SYMBOL, 29 | Constants.MICROSOFT_STOCK_SYMBOL); 30 | } 31 | 32 | public static String getSingleStockQuery(String query) { 33 | return UrlBuilder.queryBuilder(String.format(Locale.ENGLISH, "\"%1$s\"", query)); 34 | } 35 | 36 | public static FinanceChartCallBack getFinanceChartCallback(ResponseBody responseBody) { 37 | Gson gson=new Gson(); 38 | String result = null; 39 | try { 40 | result = responseBody.string(); 41 | if (result.startsWith("finance_charts_json_callback( ")) { 42 | result = result.substring(29, result.length() - 2); 43 | } 44 | } catch (IOException | NullPointerException e) { 45 | e.printStackTrace(); 46 | } 47 | return gson.fromJson(result, FinanceChartCallBack.class); 48 | } 49 | 50 | public static List getPlottingLables(Context context, List series) { 51 | List lables = new ArrayList<>(); 52 | 53 | for (Series series1 : series) { 54 | 55 | try { 56 | SimpleDateFormat srcFormat = new SimpleDateFormat("yyyyMMdd"); 57 | String date = android.text.format.DateFormat.getMediumDateFormat(context). 58 | format(srcFormat.parse(series1.getDate())); 59 | lables.add(date); 60 | 61 | } catch (ParseException ignored) { 62 | 63 | } 64 | } 65 | return lables; 66 | } 67 | 68 | public static List getPlottingValues(List series) { 69 | 70 | List values = new ArrayList<>(); 71 | 72 | for (Series series1 : series) { 73 | values.add(series1.getClose()); 74 | } 75 | 76 | return values; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/androidTest/java/rajan/udacity/stock/hawk/MainActivityTest.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.RuleChain; 6 | import org.junit.rules.TestRule; 7 | import org.junit.runner.RunWith; 8 | 9 | import android.content.Intent; 10 | import android.support.test.InstrumentationRegistry; 11 | import android.support.test.rule.ActivityTestRule; 12 | import android.support.test.runner.AndroidJUnit4; 13 | 14 | import java.util.List; 15 | 16 | import rajan.udacity.stock.hawk.data.model.multiple.Stocks; 17 | import rajan.udacity.stock.hawk.test.common.TestComponentRule; 18 | import rajan.udacity.stock.hawk.test.common.TestDataFactory; 19 | import rajan.udacity.stock.hawk.ui.main.MainActivity; 20 | 21 | @RunWith(AndroidJUnit4.class) 22 | public class MainActivityTest { 23 | 24 | public final TestComponentRule component = 25 | new TestComponentRule(InstrumentationRegistry.getTargetContext()); 26 | public final ActivityTestRule main = 27 | new ActivityTestRule(MainActivity.class, false, false) { 28 | @Override 29 | protected Intent getActivityIntent() { 30 | // Override the default intent so we pass a false flag for syncing so it doesn't 31 | // start a sync service in the background that would affect the behaviour of 32 | // this test. 33 | return MainActivity.getStartIntent( 34 | InstrumentationRegistry.getTargetContext(), false); 35 | } 36 | }; 37 | 38 | // TestComponentRule needs to go first to make sure the Dagger ApplicationTestComponent is set 39 | // in the Application before any Activity is launched. 40 | @Rule 41 | public final TestRule chain = RuleChain.outerRule(component).around(main); 42 | 43 | @Test 44 | public void listOfStocksShows() { 45 | List testDataStocks = TestDataFactory.makeListRibots(20); 46 | /* when(component.getMockDataManager().getRibots()) 47 | .thenReturn(Observable.just(testDataRibots));*/ 48 | 49 | main.launchActivity(null); 50 | 51 | int position = 0; 52 | /*for (Stocks stock : testDataStocks) { 53 | onView(withId(R.id.recycler_view)) 54 | .perform(RecyclerViewActions.scrollToPosition(position)); 55 | String name = String.format("%s %s", stock.profile().name().first(), 56 | stock.profile().name().last()); 57 | onView(withText(name)) 58 | .check(matches(isDisplayed())); 59 | onView(withText(stock.profile().email())) 60 | .check(matches(isDisplayed())); 61 | position++; 62 | }*/ 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/local/DatabaseHelper.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.local; 2 | 3 | import com.raizlabs.android.dbflow.sql.language.Delete; 4 | import com.raizlabs.android.dbflow.sql.language.SQLite; 5 | 6 | import java.util.List; 7 | 8 | import javax.inject.Inject; 9 | import javax.inject.Singleton; 10 | 11 | import rajan.udacity.stock.hawk.data.model.Quote; 12 | import rajan.udacity.stock.hawk.data.model.Quote_Table; 13 | import rajan.udacity.stock.hawk.data.model.multiple.Stocks; 14 | import rajan.udacity.stock.hawk.data.model.single.Stock; 15 | import rx.Observable; 16 | import rx.functions.Action1; 17 | import rx.functions.Func0; 18 | 19 | @Singleton 20 | public class DatabaseHelper { 21 | 22 | @Inject 23 | public DatabaseHelper() { 24 | } 25 | 26 | 27 | public Observable setStocks(final Stocks stock) { 28 | return Observable.defer(new Func0>() { 29 | @Override 30 | public Observable call() { 31 | 32 | List quote = stock.getQuery().getResult().getQuote(); 33 | Observable.from(quote) 34 | .subscribe(new Action1() { 35 | @Override 36 | public void call(Quote quote) { 37 | quote.save(); 38 | } 39 | }); 40 | 41 | return Observable.just(stock); 42 | } 43 | }); 44 | } 45 | 46 | public Observable setStock(final Stock stock) { 47 | return Observable.defer(new Func0>() { 48 | @Override 49 | public Observable call() { 50 | 51 | Quote quote = stock.getQuery().getResult().getQuote(); 52 | quote.save(); 53 | 54 | return Observable.just(stock); 55 | } 56 | }); 57 | } 58 | 59 | public Observable getStocks() { 60 | return Observable.defer(new Func0>() { 61 | @Override 62 | public Observable call() { 63 | 64 | List quotes = SQLite.select() 65 | .from(Quote.class) 66 | .queryList(); 67 | 68 | Stocks stock = new Stocks(); 69 | stock.getQuery().getResult().setQuote(quotes); 70 | 71 | return Observable.just(stock); 72 | } 73 | }); 74 | } 75 | 76 | public Observable deleteStock(final String symbol) { 77 | return Observable.defer(new Func0>() { 78 | @Override 79 | public Observable call() { 80 | 81 | Delete.table(Quote.class, 82 | Quote_Table.msymbol.eq(symbol)); 83 | 84 | return getStocks(); 85 | } 86 | }); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/DataManager.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | import okhttp3.ResponseBody; 7 | import rajan.udacity.stock.hawk.data.local.DatabaseHelper; 8 | import rajan.udacity.stock.hawk.data.local.PreferencesHelper; 9 | import rajan.udacity.stock.hawk.data.model.multiple.Stocks; 10 | import rajan.udacity.stock.hawk.data.model.single.Stock; 11 | import rajan.udacity.stock.hawk.data.remote.StocksService; 12 | import rx.Observable; 13 | import rx.functions.Func1; 14 | 15 | @Singleton 16 | public class DataManager { 17 | 18 | private final StocksService mStocksService; 19 | private final DatabaseHelper mDatabaseHelper; 20 | private final PreferencesHelper mPreferencesHelper; 21 | 22 | @Inject 23 | public DataManager(StocksService stocksService, 24 | PreferencesHelper preferencesHelper, 25 | DatabaseHelper databaseHelper) { 26 | mStocksService = stocksService; 27 | mPreferencesHelper = preferencesHelper; 28 | mDatabaseHelper = databaseHelper; 29 | } 30 | 31 | public PreferencesHelper getPreferencesHelper() { 32 | return mPreferencesHelper; 33 | } 34 | 35 | public Observable syncStocks(String query) { 36 | return mStocksService.getStocks(query) 37 | .concatMap(new Func1>() { 38 | @Override 39 | public Observable call(Stocks stock) { 40 | return mDatabaseHelper.setStocks(stock); 41 | } 42 | }); 43 | } 44 | 45 | public Observable syncStock(String query) { 46 | return mStocksService.getStock(query) 47 | .concatMap(new Func1>() { 48 | @Override 49 | public Observable call(Stock stock) { 50 | if (stock.getQuery().getResult().getQuote().getAsk() == null) { 51 | return Observable.just(stock); 52 | } else { 53 | return mDatabaseHelper.setStock(stock); 54 | } 55 | } 56 | }); 57 | } 58 | 59 | public Observable getStocks() { 60 | return mDatabaseHelper.getStocks(); 61 | } 62 | 63 | public Observable deleteStock(String symbol) { 64 | return mDatabaseHelper.deleteStock(symbol); 65 | } 66 | 67 | public Observable getChangeInPercentInPref() { 68 | return getPreferencesHelper().getChangeInPercent(); 69 | } 70 | 71 | public Observable updateChangeInPercentInPref() { 72 | return getPreferencesHelper().updateChangeInPercentInPref(); 73 | } 74 | 75 | public Observable getFinanceChartData(String symbol) { 76 | return mStocksService.getFinanceChartData(symbol); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Stock Hawk 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests. 6 | 7 | Stock Hawk is an open source project and we love to receive contributions from our community — you! There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests or writing code which can be incorporated into Stock Hawk itself. 8 | 9 | # Ground Rules 10 | 11 | ## Responsibilities 12 | 13 | * Create issues for any major changes and enhancements that you wish to make. Discuss things transparently and get community feedback. 14 | * Keep changes as small as possible. 15 | * Be welcoming to newcomers and encourage diverse new contributors from all backgrounds. 16 | * Make sure to follow our [code quality guidelines](https://github.com/therajanmaurya/Stock-Hawk#code-quality). 17 | 18 | # Your First Contribution 19 | 20 | Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). 21 | 22 | At this point, you're ready to make your changes! Feel free to ask for help; everyone is a beginner at first :smile_cat: 23 | 24 | If a maintainer asks you to "rebase" your PR, they're saying that a lot of code has changed, and that you need to update your branch so it's easier to merge. 25 | 26 | ## Getting started 27 | 28 | Take a look at the general overview of the [architecture](https://github.com/therajanmaurya/Stock-Hawk#architecture). 29 | 30 | Unsure where to begin contributing? You can start by looking through [our help-wanted issues](https://github.com/therajanmaurya/Stock-Hawk/labels/help%20wanted). 31 | 32 | ## Before opening your pull request 33 | 34 | Make sure [our tests](https://github.com/therajanmaurya/Stock-Hawk#tests) and [our code analysis](https://github.com/therajanmaurya/Stock-Hawk#code-analysis-tools) are passing. You can run both with the following: 35 | 36 | ./gradlew check 37 | 38 | # How to report a bug 39 | 40 | When filing an issue, make sure to answer these five questions: 41 | 42 | 1. What version of Stock Hawk are you using? 43 | 2. What did you do? 44 | 3. What did you expect to see? 45 | 4. What did you see instead? 46 | 47 | # How to suggest a feature or enhancement 48 | 49 | If you find yourself wishing for a feature that doesn't exist in Stock Hawk, you are probably not alone. There are bound to be others out there with similar needs. Many of the features that Stock Hawk has today have been added because our users saw the need. [Open an issue on our issues list on GitHub](https://github.com/therajanmaurya/Stock-Hawk/issues) which describes the feature you would like to see, why you need it, and how it should work. 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/financechart/FinanceChartCallBack.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.financechart; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by Rajan Maurya on 31/08/16. 13 | */ 14 | public class FinanceChartCallBack implements Parcelable { 15 | 16 | @SerializedName("meta") 17 | Meta mMeta; 18 | 19 | @SerializedName("Date") 20 | Date mDate; 21 | 22 | @SerializedName("labels") 23 | List mLabels = new ArrayList<>(); 24 | 25 | @SerializedName("series") 26 | List mSeries = new ArrayList<>(); 27 | 28 | public Meta getMeta() { 29 | return mMeta; 30 | } 31 | 32 | public void setMeta(Meta meta) { 33 | mMeta = meta; 34 | } 35 | 36 | public Date getDate() { 37 | return mDate; 38 | } 39 | 40 | public void setDate(Date date) { 41 | mDate = date; 42 | } 43 | 44 | public List getLabels() { 45 | return mLabels; 46 | } 47 | 48 | public void setLabels(List labels) { 49 | mLabels = labels; 50 | } 51 | 52 | public List getSeries() { 53 | return mSeries; 54 | } 55 | 56 | public void setSeries(List series) { 57 | mSeries = series; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "FinanceChartCallBack{" + 63 | "mMeta=" + mMeta + 64 | ", mDate=" + mDate + 65 | ", mLabels=" + mLabels + 66 | ", mSeries=" + mSeries + 67 | '}'; 68 | } 69 | 70 | 71 | public FinanceChartCallBack() { 72 | } 73 | 74 | @Override 75 | public int describeContents() { 76 | return 0; 77 | } 78 | 79 | @Override 80 | public void writeToParcel(Parcel dest, int flags) { 81 | dest.writeParcelable(this.mMeta, flags); 82 | dest.writeParcelable(this.mDate, flags); 83 | dest.writeList(this.mLabels); 84 | dest.writeTypedList(this.mSeries); 85 | } 86 | 87 | protected FinanceChartCallBack(Parcel in) { 88 | this.mMeta = in.readParcelable(Meta.class.getClassLoader()); 89 | this.mDate = in.readParcelable(Date.class.getClassLoader()); 90 | this.mLabels = new ArrayList(); 91 | in.readList(this.mLabels, Double.class.getClassLoader()); 92 | this.mSeries = in.createTypedArrayList(Series.CREATOR); 93 | } 94 | 95 | public static final Creator CREATOR = 96 | new Creator() { 97 | @Override 98 | public FinanceChartCallBack createFromParcel(Parcel source) { 99 | return new FinanceChartCallBack(source); 100 | } 101 | 102 | @Override 103 | public FinanceChartCallBack[] newArray(int size) { 104 | return new FinanceChartCallBack[size]; 105 | } 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.concurrent.atomic.AtomicLong; 9 | 10 | import rajan.udacity.stock.hawk.StockHawkApplication; 11 | import rajan.udacity.stock.hawk.injection.component.ActivityComponent; 12 | import rajan.udacity.stock.hawk.injection.component.ConfigPersistentComponent; 13 | import rajan.udacity.stock.hawk.injection.component.DaggerConfigPersistentComponent; 14 | import rajan.udacity.stock.hawk.injection.module.ActivityModule; 15 | import timber.log.Timber; 16 | 17 | /** 18 | * Abstract activity that every other Activity in this application must implement. It handles 19 | * creation of Dagger components and makes sure that instances of ConfigPersistentComponent survive 20 | * across configuration changes. 21 | */ 22 | public class BaseActivity extends AppCompatActivity { 23 | 24 | private static final String KEY_ACTIVITY_ID = "KEY_ACTIVITY_ID"; 25 | private static final AtomicLong NEXT_ID = new AtomicLong(0); 26 | private static final Map sComponentsMap = new HashMap<>(); 27 | 28 | private ActivityComponent mActivityComponent; 29 | private long mActivityId; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | 35 | // Create the ActivityComponent and reuses cached ConfigPersistentComponent if this is 36 | // being called after a configuration change. 37 | mActivityId = savedInstanceState != null ? 38 | savedInstanceState.getLong(KEY_ACTIVITY_ID) : NEXT_ID.getAndIncrement(); 39 | ConfigPersistentComponent configPersistentComponent; 40 | if (!sComponentsMap.containsKey(mActivityId)) { 41 | Timber.i("Creating new ConfigPersistentComponent id=%d", mActivityId); 42 | configPersistentComponent = DaggerConfigPersistentComponent.builder() 43 | .applicationComponent(StockHawkApplication.get(this).getComponent()) 44 | .build(); 45 | sComponentsMap.put(mActivityId, configPersistentComponent); 46 | } else { 47 | Timber.i("Reusing ConfigPersistentComponent id=%d", mActivityId); 48 | configPersistentComponent = sComponentsMap.get(mActivityId); 49 | } 50 | mActivityComponent = configPersistentComponent.activityComponent(new ActivityModule(this)); 51 | } 52 | 53 | @Override 54 | protected void onSaveInstanceState(Bundle outState) { 55 | super.onSaveInstanceState(outState); 56 | outState.putLong(KEY_ACTIVITY_ID, mActivityId); 57 | } 58 | 59 | @Override 60 | protected void onDestroy() { 61 | if (!isChangingConfigurations()) { 62 | Timber.i("Clearing ConfigPersistentComponent id=%d", mActivityId); 63 | sComponentsMap.remove(mActivityId); 64 | } 65 | super.onDestroy(); 66 | } 67 | 68 | public ActivityComponent activityComponent() { 69 | return mActivityComponent; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /app/src/test/java/rajan/udacity/stock/hawk/DataManagerTest.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk; 2 | 3 | import org.junit.Before; 4 | import org.junit.runner.RunWith; 5 | import org.mockito.Mock; 6 | import org.mockito.runners.MockitoJUnitRunner; 7 | 8 | import rajan.udacity.stock.hawk.data.DataManager; 9 | import rajan.udacity.stock.hawk.data.local.DatabaseHelper; 10 | import rajan.udacity.stock.hawk.data.local.PreferencesHelper; 11 | import rajan.udacity.stock.hawk.data.remote.StocksService; 12 | 13 | /** 14 | * This test class performs local unit tests without dependencies on the Android framework 15 | * For testing methods in the DataManager follow this approach: 16 | * 1. Stub mock helper classes that your method relies on. e.g. RetrofitServices or DatabaseHelper 17 | * 2. Test the Observable using TestSubscriber 18 | * 3. Optionally write a SEPARATE test that verifies that your method is calling the right helper 19 | * using Mockito.verify() 20 | */ 21 | @RunWith(MockitoJUnitRunner.class) 22 | public class DataManagerTest { 23 | 24 | @Mock DatabaseHelper mMockDatabaseHelper; 25 | @Mock PreferencesHelper mMockPreferencesHelper; 26 | @Mock 27 | StocksService mMockRibotsService; 28 | private DataManager mDataManager; 29 | 30 | @Before 31 | public void setUp() { 32 | mDataManager = new DataManager(mMockRibotsService, mMockPreferencesHelper, 33 | mMockDatabaseHelper); 34 | } 35 | 36 | /*@Test 37 | public void syncRibotsEmitsValues() { 38 | List ribots = Arrays.asList(TestDataFactory.makeRibot("r1"), 39 | TestDataFactory.makeRibot("r2")); 40 | stubSyncRibotsHelperCalls(ribots); 41 | 42 | TestSubscriber result = new TestSubscriber<>(); 43 | mDataManager.syncRibots().subscribe(result); 44 | result.assertNoErrors(); 45 | result.assertReceivedOnNext(ribots); 46 | } 47 | 48 | @Test 49 | public void syncRibotsCallsApiAndDatabase() { 50 | List ribots = Arrays.asList(TestDataFactory.makeRibot("r1"), 51 | TestDataFactory.makeRibot("r2")); 52 | stubSyncRibotsHelperCalls(ribots); 53 | 54 | mDataManager.syncRibots().subscribe(); 55 | // Verify right calls to helper methods 56 | verify(mMockRibotsService).getStocks(); 57 | verify(mMockDatabaseHelper).setStocks(ribots); 58 | } 59 | 60 | @Test 61 | public void syncRibotsDoesNotCallDatabaseWhenApiFails() { 62 | when(mMockRibotsService.getStocks()) 63 | .thenReturn(Observable.>error(new RuntimeException())); 64 | 65 | mDataManager.syncRibots().subscribe(new TestSubscriber()); 66 | // Verify right calls to helper methods 67 | verify(mMockRibotsService).getStocks(); 68 | verify(mMockDatabaseHelper, never()).setStocks(anyListOf(Stocks.class)); 69 | } 70 | 71 | private void stubSyncRibotsHelperCalls(List ribots) { 72 | // Stub calls to the ribot service and database helper. 73 | when(mMockRibotsService.getStocks()) 74 | .thenReturn(Observable.just(ribots)); 75 | when(mMockDatabaseHelper.setStocks(ribots)) 76 | .thenReturn(Observable.from(ribots)); 77 | }*/ 78 | 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/financechart/Series.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.financechart; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | /** 9 | * Created by Rajan Maurya on 31/08/16. 10 | */ 11 | public class Series implements Parcelable { 12 | 13 | @SerializedName("Date") 14 | String mDate; 15 | 16 | @SerializedName("close") 17 | Float mClose; 18 | 19 | @SerializedName("high") 20 | Double mHigh; 21 | 22 | @SerializedName("low") 23 | Double mLow; 24 | 25 | @SerializedName("mOpen") 26 | Double mOpen; 27 | 28 | @SerializedName("volume") 29 | Double mVolume; 30 | 31 | public String getDate() { 32 | return mDate; 33 | } 34 | 35 | public void setDate(String date) { 36 | mDate = date; 37 | } 38 | 39 | public Float getClose() { 40 | return mClose; 41 | } 42 | 43 | public void setClose(Float close) { 44 | mClose = close; 45 | } 46 | 47 | public Double getHigh() { 48 | return mHigh; 49 | } 50 | 51 | public void setHigh(Double high) { 52 | mHigh = high; 53 | } 54 | 55 | public Double getLow() { 56 | return mLow; 57 | } 58 | 59 | public void setLow(Double low) { 60 | mLow = low; 61 | } 62 | 63 | public Double getOpen() { 64 | return mOpen; 65 | } 66 | 67 | public void setOpen(Double open) { 68 | this.mOpen = open; 69 | } 70 | 71 | public Double getVolume() { 72 | return mVolume; 73 | } 74 | 75 | public void setVolume(Double volume) { 76 | mVolume = volume; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return "Series{" + 82 | "mDate=" + mDate + 83 | ", mClose=" + mClose + 84 | ", mHigh=" + mHigh + 85 | ", mLow=" + mLow + 86 | ", mOpen=" + mOpen + 87 | ", mVolume=" + mVolume + 88 | '}'; 89 | } 90 | 91 | 92 | @Override 93 | public int describeContents() { 94 | return 0; 95 | } 96 | 97 | @Override 98 | public void writeToParcel(Parcel dest, int flags) { 99 | dest.writeValue(this.mDate); 100 | dest.writeValue(this.mClose); 101 | dest.writeValue(this.mHigh); 102 | dest.writeValue(this.mLow); 103 | dest.writeValue(this.mOpen); 104 | dest.writeValue(this.mVolume); 105 | } 106 | 107 | public Series() { 108 | } 109 | 110 | protected Series(Parcel in) { 111 | this.mDate = (String) in.readValue(String.class.getClassLoader()); 112 | this.mClose = (Float) in.readValue(Float.class.getClassLoader()); 113 | this.mHigh = (Double) in.readValue(Double.class.getClassLoader()); 114 | this.mLow = (Double) in.readValue(Double.class.getClassLoader()); 115 | this.mOpen = (Double) in.readValue(Double.class.getClassLoader()); 116 | this.mVolume = (Double) in.readValue(Double.class.getClassLoader()); 117 | } 118 | 119 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 120 | @Override 121 | public Series createFromParcel(Parcel source) { 122 | return new Series(source); 123 | } 124 | 125 | @Override 126 | public Series[] newArray(int size) { 127 | return new Series[size]; 128 | } 129 | }; 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/SyncService.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data; 2 | 3 | import android.app.Service; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.net.ConnectivityManager; 8 | import android.os.IBinder; 9 | 10 | import javax.inject.Inject; 11 | 12 | import rajan.udacity.stock.hawk.StockHawkApplication; 13 | import rajan.udacity.stock.hawk.data.model.multiple.Stocks; 14 | import rajan.udacity.stock.hawk.util.AndroidComponentUtil; 15 | import rajan.udacity.stock.hawk.util.NetworkUtil; 16 | import rajan.udacity.stock.hawk.util.Utils; 17 | import rx.Subscriber; 18 | import rx.Subscription; 19 | import rx.android.schedulers.AndroidSchedulers; 20 | import rx.schedulers.Schedulers; 21 | import timber.log.Timber; 22 | 23 | public class SyncService extends Service { 24 | 25 | @Inject 26 | DataManager mDataManager; 27 | 28 | private Subscription mSubscription; 29 | 30 | public static Intent getStartIntent(Context context) { 31 | return new Intent(context, SyncService.class); 32 | } 33 | 34 | public static boolean isRunning(Context context) { 35 | return AndroidComponentUtil.isServiceRunning(context, SyncService.class); 36 | } 37 | 38 | @Override 39 | public void onCreate() { 40 | super.onCreate(); 41 | StockHawkApplication.get(this).getComponent().inject(this); 42 | } 43 | 44 | @Override 45 | public int onStartCommand(Intent intent, int flags, final int startId) { 46 | Timber.i("Starting sync..."); 47 | 48 | if (!NetworkUtil.isNetworkConnected(this)) { 49 | Timber.i("Sync canceled, connection not available"); 50 | AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable.class, true); 51 | stopSelf(startId); 52 | return START_NOT_STICKY; 53 | } 54 | 55 | if (mSubscription != null && !mSubscription.isUnsubscribed()) mSubscription.unsubscribe(); 56 | mSubscription = mDataManager.syncStocks(Utils.getYahooStocksQuery()) 57 | .observeOn(AndroidSchedulers.mainThread()) 58 | .subscribeOn(Schedulers.io()) 59 | .subscribe(new Subscriber() { 60 | @Override 61 | public void onCompleted() { 62 | Timber.i("Synced successfully!"); 63 | stopSelf(startId); 64 | } 65 | 66 | @Override 67 | public void onError(Throwable e) { 68 | Timber.w(e, "Error syncing."); 69 | stopSelf(startId); 70 | } 71 | 72 | @Override 73 | public void onNext(Stocks stocks) { 74 | } 75 | }); 76 | 77 | return START_STICKY; 78 | } 79 | 80 | @Override 81 | public void onDestroy() { 82 | if (mSubscription != null) mSubscription.unsubscribe(); 83 | super.onDestroy(); 84 | } 85 | 86 | @Override 87 | public IBinder onBind(Intent intent) { 88 | return null; 89 | } 90 | 91 | public static class SyncOnConnectionAvailable extends BroadcastReceiver { 92 | 93 | @Override 94 | public void onReceive(Context context, Intent intent) { 95 | if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION) 96 | && NetworkUtil.isNetworkConnected(context)) { 97 | Timber.i("Connection is now available, triggering sync..."); 98 | AndroidComponentUtil.toggleComponent(context, this.getClass(), false); 99 | context.startService(getStartIntent(context)); 100 | } 101 | } 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/widget/ListRemoteViewFactory.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.widget; 2 | 3 | import android.appwidget.AppWidgetManager; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.util.Log; 8 | import android.widget.RemoteViews; 9 | import android.widget.RemoteViewsService; 10 | import android.widget.Toast; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import javax.inject.Inject; 16 | 17 | import rajan.udacity.stock.hawk.R; 18 | import rajan.udacity.stock.hawk.StockHawkApplication; 19 | import rajan.udacity.stock.hawk.data.model.Quote; 20 | 21 | /** 22 | * Created by Rajan Maurya on 30/08/16. 23 | */ 24 | public class ListRemoteViewFactory implements RemoteViewsService.RemoteViewsFactory, 25 | ListRemoteViewFactoryMvpView { 26 | 27 | private static final String LOG_TAG = ListRemoteViewFactory.class.getSimpleName(); 28 | private final Object mObject; 29 | @Inject 30 | ListRemoteViewFactoryPresenter mListRemoteViewFactoryPresenter; 31 | private Context mContext; 32 | private int mAppWidgetId; 33 | private List mQuoteList; 34 | 35 | public ListRemoteViewFactory(Context applicationContext, Intent intent) { 36 | mContext = applicationContext; 37 | mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 38 | AppWidgetManager.INVALID_APPWIDGET_ID); 39 | mQuoteList = new ArrayList<>(); 40 | mObject = new Object(); 41 | } 42 | 43 | @Override 44 | public void onCreate() { 45 | StockHawkApplication.get(mContext).getComponent().inject(this); 46 | mListRemoteViewFactoryPresenter.attachView(this); 47 | } 48 | 49 | @Override 50 | public void onDataSetChanged() { 51 | mListRemoteViewFactoryPresenter.loadStocks(); 52 | synchronized (mObject) { 53 | try { 54 | // Calling wait() will block this thread until another thread 55 | // calls notify() on the object. 56 | mObject.wait(); 57 | } catch (InterruptedException e) { 58 | // Happens if someone interrupts your thread. 59 | } 60 | } 61 | } 62 | 63 | @Override 64 | public RemoteViews getViewAt(int position) { 65 | 66 | Quote quote = mQuoteList.get(position); 67 | 68 | final int itemId = R.layout.appwidget_collection_item; 69 | RemoteViews rv = new RemoteViews(mContext.getPackageName(), itemId); 70 | rv.setTextViewText(R.id.stock_symbol, quote.getMsymbol()); 71 | rv.setTextViewText(R.id.bid_price, String.valueOf(quote.getBid())); 72 | rv.setTextViewText(R.id.change, quote.getChangeinPercent()); 73 | 74 | final Intent viewIntent = new Intent(); 75 | final Bundle extras = new Bundle(); 76 | extras.putString("Symbol", quote.getMsymbol()); 77 | viewIntent.putExtras(extras); 78 | rv.setOnClickFillInIntent(R.id.widget_item, viewIntent); 79 | 80 | Log.d(LOG_TAG, quote.getMsymbol()); 81 | 82 | return rv; 83 | } 84 | 85 | @Override 86 | public void showStocks(List quoteList) { 87 | mQuoteList = quoteList; 88 | synchronized (mObject) { 89 | mObject.notify(); 90 | } 91 | } 92 | 93 | @Override 94 | public void showStocksEmpty() { 95 | 96 | } 97 | 98 | @Override 99 | public void showError() { 100 | Toast.makeText(mContext, mContext.getString(R.string.error_loading_stocks), 101 | Toast.LENGTH_SHORT).show(); 102 | } 103 | 104 | @Override 105 | public void onDestroy() { 106 | mListRemoteViewFactoryPresenter.detachView(); 107 | } 108 | 109 | @Override 110 | public int getCount() { 111 | return mQuoteList.size(); 112 | } 113 | 114 | @Override 115 | public RemoteViews getLoadingView() { 116 | return null; 117 | } 118 | 119 | @Override 120 | public int getViewTypeCount() { 121 | return 1; 122 | } 123 | 124 | @Override 125 | public long getItemId(int position) { 126 | return position; 127 | } 128 | 129 | @Override 130 | public boolean hasStableIds() { 131 | return false; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/main/StockAdapter.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.main; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import javax.inject.Inject; 13 | 14 | import butterknife.BindView; 15 | import butterknife.ButterKnife; 16 | import rajan.udacity.stock.hawk.R; 17 | import rajan.udacity.stock.hawk.data.model.Quote; 18 | import rajan.udacity.stock.hawk.touch_helper.ItemTouchHelperAdapter; 19 | import rajan.udacity.stock.hawk.touch_helper.ItemTouchHelperViewHolder; 20 | 21 | public class StockAdapter extends RecyclerView.Adapter 22 | implements ItemTouchHelperAdapter { 23 | 24 | private List mQuoteList; 25 | 26 | private DismissAndOnClickItemStockListener mDismissAndOnClickItemStockListener; 27 | 28 | private Boolean changeInPercent = false; 29 | 30 | @Inject 31 | public StockAdapter() { 32 | mQuoteList = new ArrayList<>(); 33 | } 34 | 35 | @Override 36 | public StockViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 | View itemView = LayoutInflater.from(parent.getContext()) 38 | .inflate(R.layout.item_stock, parent, false); 39 | return new StockViewHolder(itemView); 40 | } 41 | 42 | @Override 43 | public void onBindViewHolder(final StockViewHolder holder, int position) { 44 | final Quote quote = mQuoteList.get(position); 45 | 46 | holder.tv_stock_symbol.setText(quote.getMsymbol()); 47 | holder.tv_bid_price.setText(String.valueOf(quote.getBid())); 48 | 49 | if (changeInPercent) { 50 | holder.tv_change.setText(quote.getChangeinPercent()); 51 | } else { 52 | holder.tv_change.setText(String.valueOf(quote.getChange())); 53 | } 54 | 55 | holder.itemView.setOnClickListener(new View.OnClickListener() { 56 | @Override 57 | public void onClick(View view) { 58 | mDismissAndOnClickItemStockListener.onItemClick(quote.getMsymbol()); 59 | } 60 | }); 61 | 62 | } 63 | 64 | @Override 65 | public int getItemCount() { 66 | return mQuoteList.size(); 67 | } 68 | 69 | @Override 70 | public void onItemDismiss(int position) { 71 | mDismissAndOnClickItemStockListener.onStockDismiss(mQuoteList.get(position).getMsymbol()); 72 | notifyItemRemoved(position); 73 | } 74 | 75 | public void setStock(Quote quote) { 76 | mQuoteList.add(quote); 77 | notifyDataSetChanged(); 78 | } 79 | 80 | public List getStocks() { 81 | return mQuoteList; 82 | } 83 | 84 | public String getStockSymbol(int position) { 85 | return mQuoteList.get(position).getMsymbol(); 86 | } 87 | 88 | public void setStocks(List quotes) { 89 | mQuoteList = quotes; 90 | } 91 | 92 | public StockAdapter setOnDismissStockListener(DismissAndOnClickItemStockListener listener) { 93 | mDismissAndOnClickItemStockListener = listener; 94 | return this; 95 | } 96 | 97 | public void setChangeInPercent(Boolean changeInPercent) { 98 | this.changeInPercent = changeInPercent; 99 | notifyDataSetChanged(); 100 | } 101 | 102 | public interface DismissAndOnClickItemStockListener { 103 | void onStockDismiss(String symbol); 104 | void onItemClick(String symbol); 105 | } 106 | 107 | class StockViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder { 108 | 109 | @BindView(R.id.stock_symbol) 110 | TextView tv_stock_symbol; 111 | @BindView(R.id.bid_price) 112 | TextView tv_bid_price; 113 | @BindView(R.id.change) 114 | TextView tv_change; 115 | 116 | public StockViewHolder(View itemView) { 117 | super(itemView); 118 | ButterKnife.bind(this, itemView); 119 | } 120 | 121 | @Override 122 | public void onItemSelected() { 123 | //itemView.setBackgroundColor(Color.LTGRAY); 124 | } 125 | 126 | @Override 127 | public void onItemClear() { 128 | //itemView.setBackgroundColor(0); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/data/model/financechart/Meta.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.data.model.financechart; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | /** 9 | * Created by Rajan Maurya on 31/08/16. 10 | */ 11 | public class Meta implements Parcelable { 12 | 13 | @SerializedName("uri") 14 | String mUri; 15 | 16 | @SerializedName("ticker") 17 | String mTicker; 18 | 19 | @SerializedName("Company-Name") 20 | String mCompanyName; 21 | 22 | @SerializedName("Exchange-Name") 23 | String mExchangeName; 24 | 25 | @SerializedName("unit") 26 | String mUnit; 27 | 28 | @SerializedName("timestamp") 29 | String mTimestamp; 30 | 31 | @SerializedName("first-trade") 32 | Double mFirstTrade; 33 | 34 | @SerializedName("last-trade") 35 | Double mLastTrade; 36 | 37 | @SerializedName("currency") 38 | String mCurrency; 39 | 40 | @SerializedName("previous_close_price") 41 | Double mPreviousClosePrice; 42 | 43 | public String getUri() { 44 | return mUri; 45 | } 46 | 47 | public void setUri(String uri) { 48 | mUri = uri; 49 | } 50 | 51 | public String getTicker() { 52 | return mTicker; 53 | } 54 | 55 | public void setTicker(String ticker) { 56 | mTicker = ticker; 57 | } 58 | 59 | public String getCompanyName() { 60 | return mCompanyName; 61 | } 62 | 63 | public void setCompanyName(String companyName) { 64 | mCompanyName = companyName; 65 | } 66 | 67 | public String getExchangeName() { 68 | return mExchangeName; 69 | } 70 | 71 | public void setExchangeName(String exchangeName) { 72 | mExchangeName = exchangeName; 73 | } 74 | 75 | public String getUnit() { 76 | return mUnit; 77 | } 78 | 79 | public void setUnit(String unit) { 80 | mUnit = unit; 81 | } 82 | 83 | public String getTimestamp() { 84 | return mTimestamp; 85 | } 86 | 87 | public void setTimestamp(String timestamp) { 88 | mTimestamp = timestamp; 89 | } 90 | 91 | public Double getFirstTrade() { 92 | return mFirstTrade; 93 | } 94 | 95 | public void setFirstTrade(Double firstTrade) { 96 | mFirstTrade = firstTrade; 97 | } 98 | 99 | public Double getLastTrade() { 100 | return mLastTrade; 101 | } 102 | 103 | public void setLastTrade(Double lastTrade) { 104 | mLastTrade = lastTrade; 105 | } 106 | 107 | public String getCurrency() { 108 | return mCurrency; 109 | } 110 | 111 | public void setCurrency(String currency) { 112 | mCurrency = currency; 113 | } 114 | 115 | public Double getPreviousClosePrice() { 116 | return mPreviousClosePrice; 117 | } 118 | 119 | public void setPreviousClosePrice(Double previousClosePrice) { 120 | mPreviousClosePrice = previousClosePrice; 121 | } 122 | 123 | @Override 124 | public String toString() { 125 | return "Meta{" + 126 | "mUri='" + mUri + '\'' + 127 | ", mTicker='" + mTicker + '\'' + 128 | ", mCompanyName='" + mCompanyName + '\'' + 129 | ", mExchangeName='" + mExchangeName + '\'' + 130 | ", mUnit='" + mUnit + '\'' + 131 | ", mTimestamp='" + mTimestamp + '\'' + 132 | ", mFirstTrade=" + mFirstTrade + 133 | ", mLastTrade=" + mLastTrade + 134 | ", mCurrency='" + mCurrency + '\'' + 135 | ", mPreviousClosePrice=" + mPreviousClosePrice + 136 | '}'; 137 | } 138 | 139 | 140 | @Override 141 | public int describeContents() { 142 | return 0; 143 | } 144 | 145 | @Override 146 | public void writeToParcel(Parcel dest, int flags) { 147 | dest.writeString(this.mUri); 148 | dest.writeString(this.mTicker); 149 | dest.writeString(this.mCompanyName); 150 | dest.writeString(this.mExchangeName); 151 | dest.writeString(this.mUnit); 152 | dest.writeString(this.mTimestamp); 153 | dest.writeValue(this.mFirstTrade); 154 | dest.writeValue(this.mLastTrade); 155 | dest.writeString(this.mCurrency); 156 | dest.writeValue(this.mPreviousClosePrice); 157 | } 158 | 159 | public Meta() { 160 | } 161 | 162 | protected Meta(Parcel in) { 163 | this.mUri = in.readString(); 164 | this.mTicker = in.readString(); 165 | this.mCompanyName = in.readString(); 166 | this.mExchangeName = in.readString(); 167 | this.mUnit = in.readString(); 168 | this.mTimestamp = in.readString(); 169 | this.mFirstTrade = (Double) in.readValue(Double.class.getClassLoader()); 170 | this.mLastTrade = (Double) in.readValue(Double.class.getClassLoader()); 171 | this.mCurrency = in.readString(); 172 | this.mPreviousClosePrice = (Double) in.readValue(Double.class.getClassLoader()); 173 | } 174 | 175 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 176 | @Override 177 | public Meta createFromParcel(Parcel source) { 178 | return new Meta(source); 179 | } 180 | 181 | @Override 182 | public Meta[] newArray(int size) { 183 | return new Meta[size]; 184 | } 185 | }; 186 | } 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stock Hawk Udacity Project-3 2 | 3 | This Android App is the Udacity Nanodegree project-3 Stock Hawk. This application uses the yahoo API of stocks and giving ability to view the stocks and other core functionlity that help to visualize the stock . This project following the [Ribot](https://github.com/ribot/android-guidelines) guide lines. 4 | 5 | ##Screenshots 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | Libraries and tools included: 17 | 18 | - Support libraries 19 | - RecyclerViews and CardViews 20 | - [RxJava](https://github.com/ReactiveX/RxJava) and [RxAndroid](https://github.com/ReactiveX/RxAndroid) 21 | - [Retrofit 2](http://square.github.io/retrofit/) 22 | - [Dagger 2](http://google.github.io/dagger/) 23 | - [SqlBrite](https://github.com/square/sqlbrite) 24 | - [Butterknife](https://github.com/JakeWharton/butterknife) 25 | - [Timber](https://github.com/JakeWharton/timber) 26 | - [Glide](https://github.com/bumptech/glide) 27 | - Functional tests with [Espresso](https://google.github.io/android-testing-support-library/docs/espresso/index.html) 28 | - [Robolectric](http://robolectric.org/) 29 | - [Mockito](http://mockito.org/) 30 | - [Checkstyle](http://checkstyle.sourceforge.net/), [PMD](https://pmd.github.io/) and [Findbugs](http://findbugs.sourceforge.net/) for code analysis 31 | 32 | ## Requirements 33 | 34 | - JDK 1.8 35 | - [Android SDK](http://developer.android.com/sdk/index.html). 36 | - Android N [(API 24) ](http://developer.android.com/tools/revisions/platforms.html). 37 | - Latest Android SDK Tools and build tools. 38 | 39 | ## Contributing 40 | 41 | If you would like to contribute to Stock Hawk, the [contributing guide](https://github.com/therajanmaurya/Stock-Hawk/blob/master/CONTRIBUTING.md) is a good place to start. If you have questions, feel free to ask. 42 | 43 | ## Architecture 44 | 45 | This project follows ribot's Android architecture guidelines that are based on [MVP (Model View Presenter)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter). Read more about them [here](https://github.com/ribot/android-guidelines/blob/master/architecture_guidelines/android_architecture.md). 46 | 47 | ![](https://github.com/ribot/android-guidelines/raw/master/architecture_guidelines/architecture_diagram.png) 48 | 49 | ## Code Quality 50 | 51 | This project integrates a combination of unit tests, functional test and code analysis tools. 52 | 53 | ### Tests 54 | 55 | To run **unit** tests on your machine: 56 | 57 | ``` 58 | ./gradlew test 59 | ``` 60 | 61 | To run **functional** tests on connected devices: 62 | 63 | ``` 64 | ./gradlew connectedAndroidTest 65 | ``` 66 | 67 | Note: For Android Studio to use syntax highlighting for Automated tests and Unit tests you **must** switch the Build Variant to the desired mode. 68 | 69 | ### Code Analysis tools 70 | 71 | The following code analysis tools are set up on this project: 72 | 73 | * [PMD](https://pmd.github.io/): It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. See [this project's PMD ruleset](config/quality/pmd/pmd-ruleset.xml). 74 | 75 | ``` 76 | ./gradlew pmd 77 | ``` 78 | 79 | * [Findbugs](http://findbugs.sourceforge.net/): This tool uses static analysis to find bugs in Java code. Unlike PMD, it uses compiled Java bytecode instead of source code. 80 | 81 | ``` 82 | ./gradlew findbugs 83 | ``` 84 | 85 | * [Checkstyle](http://checkstyle.sourceforge.net/): It ensures that the code style follows [our Android code guidelines](https://github.com/ribot/android-guidelines/blob/master/project_and_code_guidelines.md#2-code-guidelines). See our [checkstyle config file](config/quality/checkstyle/checkstyle-config.xml). 86 | 87 | ``` 88 | ./gradlew checkstyle 89 | ``` 90 | 91 | ### The check task 92 | 93 | To ensure that your code is valid and stable use check: 94 | 95 | ``` 96 | ./gradlew check 97 | ``` 98 | 99 | This will run all the code analysis tools and unit tests in the following order: 100 | 101 | ![Check Diagram](images/check-task-diagram.png) 102 | 103 | ## Distribution 104 | 105 | The project can be distributed using either [Crashlytics](http://support.crashlytics.com/knowledgebase/articles/388925-beta-distributions-with-gradle) or the [Google Play Store](https://github.com/Triple-T/gradle-play-publisher). 106 | 107 | ### Play Store 108 | 109 | We use the __Gradle Play Publisher__ plugin. Once set up correctly, you will be able to push new builds to 110 | the Alpha, Beta or production channels like this 111 | 112 | ``` 113 | ./gradlew publishApkRelease 114 | ``` 115 | Read [plugin documentation](https://github.com/Triple-T/gradle-play-publisher) for more info. 116 | 117 | ### Crashlytics 118 | 119 | You can also use Fabric's Crashlytics for distributing beta releases. Remember to add your fabric 120 | account details to `app/src/fabric.properties`. 121 | 122 | To upload a release build to Crashlytics run: 123 | 124 | ``` 125 | ./gradlew assembleRelease crashlyticsUploadDistributionRelease 126 | ``` 127 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/main/MainPresenter.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.main; 2 | 3 | import java.util.List; 4 | 5 | import javax.inject.Inject; 6 | 7 | import rajan.udacity.stock.hawk.data.DataManager; 8 | import rajan.udacity.stock.hawk.data.model.Quote; 9 | import rajan.udacity.stock.hawk.data.model.multiple.Stocks; 10 | import rajan.udacity.stock.hawk.data.model.single.Stock; 11 | import rajan.udacity.stock.hawk.injection.ConfigPersistent; 12 | import rajan.udacity.stock.hawk.ui.base.BasePresenter; 13 | import rajan.udacity.stock.hawk.util.Utils; 14 | import rx.Observable; 15 | import rx.Subscriber; 16 | import rx.android.schedulers.AndroidSchedulers; 17 | import rx.functions.Action1; 18 | import rx.functions.Func1; 19 | import rx.schedulers.Schedulers; 20 | import rx.subscriptions.CompositeSubscription; 21 | import timber.log.Timber; 22 | 23 | @ConfigPersistent 24 | public class MainPresenter extends BasePresenter { 25 | 26 | private final DataManager mDataManager; 27 | private CompositeSubscription mSubscriptions; 28 | 29 | private Boolean stockExist = false; 30 | 31 | @Inject 32 | public MainPresenter(DataManager dataManager) { 33 | mDataManager = dataManager; 34 | mSubscriptions = new CompositeSubscription(); 35 | } 36 | 37 | @Override 38 | public void attachView(MainMvpView mvpView) { 39 | super.attachView(mvpView); 40 | } 41 | 42 | @Override 43 | public void detachView() { 44 | super.detachView(); 45 | mSubscriptions.unsubscribe(); 46 | } 47 | 48 | public void loadStocks() { 49 | checkViewAttached(); 50 | mSubscriptions.add(mDataManager.getStocks() 51 | .observeOn(AndroidSchedulers.mainThread()) 52 | .subscribeOn(Schedulers.io()) 53 | .subscribe(new Subscriber() { 54 | @Override 55 | public void onCompleted() { 56 | } 57 | 58 | @Override 59 | public void onError(Throwable e) { 60 | Timber.e(e, "There was an error loading the stocks."); 61 | getMvpView().showError(); 62 | } 63 | 64 | @Override 65 | public void onNext(Stocks stocks) { 66 | showStocks(stocks); 67 | } 68 | })); 69 | } 70 | 71 | public void deleteStock(String symbol) { 72 | checkViewAttached(); 73 | mSubscriptions.add(mDataManager.deleteStock(symbol) 74 | .observeOn(AndroidSchedulers.mainThread()) 75 | .subscribeOn(Schedulers.io()) 76 | .subscribe(new Subscriber() { 77 | @Override 78 | public void onCompleted() { 79 | 80 | } 81 | 82 | @Override 83 | public void onError(Throwable e) { 84 | Timber.e(e, "There was an error deleting the stock"); 85 | getMvpView().showError(); 86 | } 87 | 88 | @Override 89 | public void onNext(Stocks stock) { 90 | showStocks(stock); 91 | } 92 | }) 93 | ); 94 | } 95 | 96 | public void loadStock(String symbol) { 97 | checkViewAttached(); 98 | mSubscriptions.add(mDataManager.syncStock(Utils.getSingleStockQuery(symbol)) 99 | .observeOn(AndroidSchedulers.mainThread()) 100 | .subscribeOn(Schedulers.io()) 101 | .subscribe(new Subscriber() { 102 | @Override 103 | public void onCompleted() { 104 | 105 | } 106 | 107 | @Override 108 | public void onError(Throwable e) { 109 | Timber.e(e, "Failed to load single stock"); 110 | } 111 | 112 | @Override 113 | public void onNext(Stock stock) { 114 | showStock(stock.getQuery().getResult().getQuote()); 115 | } 116 | })); 117 | 118 | } 119 | 120 | public void loadChangeInPercent() { 121 | checkViewAttached(); 122 | mSubscriptions.add(mDataManager.getChangeInPercentInPref() 123 | .observeOn(AndroidSchedulers.mainThread()) 124 | .subscribeOn(Schedulers.io()) 125 | .subscribe(new Action1() { 126 | @Override 127 | public void call(Boolean aBoolean) { 128 | getMvpView().showChangeInPercent(aBoolean); 129 | } 130 | }) 131 | ); 132 | } 133 | 134 | public void updateChangeInPercent() { 135 | checkViewAttached(); 136 | mSubscriptions.add(mDataManager.updateChangeInPercentInPref() 137 | .observeOn(AndroidSchedulers.mainThread()) 138 | .subscribeOn(Schedulers.io()) 139 | .subscribe(new Action1() { 140 | @Override 141 | public void call(Boolean aBoolean) { 142 | getMvpView().updateChangeInPercent(aBoolean); 143 | } 144 | }) 145 | ); 146 | } 147 | 148 | public void showStocks(Stocks stocks) { 149 | if (stocks == null) { 150 | getMvpView().showStocksEmpty(); 151 | } else { 152 | getMvpView().showStocks(stocks.getQuery().getResult().getQuote()); 153 | } 154 | } 155 | 156 | public void showStock(Quote quote) { 157 | if (quote.getAsk() == null) { 158 | getMvpView().showStockDoesNotExist(); 159 | } else { 160 | getMvpView().showStock(quote); 161 | } 162 | } 163 | 164 | public Boolean checkStocksExistOrNot(final String symbol, List quoteList) { 165 | stockExist = false; 166 | Observable.from(quoteList) 167 | .filter(new Func1() { 168 | @Override 169 | public Boolean call(Quote quote) { 170 | return (quote.getMsymbol().equals(symbol)); 171 | } 172 | }) 173 | .subscribe(new Action1() { 174 | @Override 175 | public void call(Quote quote) { 176 | stockExist = true; 177 | } 178 | }); 179 | return stockExist; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /config/quality/checkstyle/checkstyle-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 77 | 79 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 92 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 117 | 118 | 119 | 120 | 121 | 123 | 124 | 125 | 126 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 137 | 138 | 139 | 140 | 141 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 151 | 152 | 153 | 154 | 155 | 157 | 158 | 159 | 160 | 161 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply from: '../config/quality/quality.gradle' 3 | apply plugin: 'com.github.triplet.play' 4 | apply plugin: 'com.neenbedankt.android-apt' 5 | //TODO uncomment line below after adding fabric api secret and key to fabric.properties 6 | //apply plugin: 'io.fabric' 7 | 8 | android { 9 | compileSdkVersion 24 10 | buildToolsVersion '24' 11 | 12 | defaultConfig { 13 | applicationId 'rajan.udacity.stock.hawk' 14 | minSdkVersion 16 15 | targetSdkVersion 24 16 | testInstrumentationRunner "${applicationId}.runner.RxAndroidJUnitRunner" 17 | versionCode 1000 18 | // Major -> Millions, Minor -> Thousands, Bugfix -> Hundreds. E.g 1.3.72 == 1,003,072 19 | versionName '0.1.0' 20 | } 21 | 22 | signingConfigs { 23 | // You must set up an environment var before release signing 24 | // Run: export APP_KEY={password} 25 | // TODO Add your release keystore in /keystore folder 26 | release { 27 | storeFile file('keystore/release.keystore') 28 | keyAlias 'alias' 29 | storePassword "$System.env.APP_KEY" 30 | keyPassword "$System.env.APP_KEY" 31 | } 32 | 33 | debug { 34 | storeFile file('keystore/debug.keystore') 35 | keyAlias 'androiddebugkey' 36 | storePassword 'android' 37 | keyPassword 'android' 38 | } 39 | } 40 | 41 | buildTypes { 42 | release { 43 | signingConfig signingConfigs.release 44 | 45 | minifyEnabled true 46 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 47 | 48 | ext.betaDistributionReleaseNotesFilePath = 49 | file('../crashlytics_release_notes.txt').absolutePath 50 | } 51 | 52 | debug { 53 | versionNameSuffix " Debug" 54 | debuggable true 55 | } 56 | } 57 | 58 | sourceSets { 59 | def commonTestDir = 'src/commonTest/java' 60 | test { 61 | java.srcDir commonTestDir 62 | } 63 | androidTest { 64 | java.srcDir commonTestDir 65 | } 66 | } 67 | 68 | //Needed because of this https://github.com/square/okio/issues/58 69 | lintOptions { 70 | warning 'InvalidPackage' 71 | } 72 | } 73 | 74 | play { 75 | serviceAccountEmail = 'your-service-account-email' 76 | pk12File = file('key.p12') 77 | // By default publishes to Alpha channel 78 | track = 'alpha' 79 | } 80 | 81 | dependencies { 82 | final PLAY_SERVICES_VERSION = '9.2.0' 83 | final SUPPORT_LIBRARY_VERSION = '24.0.0' 84 | final RETROFIT_VERSION = '2.1.0' 85 | final OKHTTP_VERSION = '3.3.0' 86 | final DAGGER_VERSION = '2.5' 87 | final DEXMAKER_VERSION = '1.4' 88 | final HAMCREST_VERSION = '1.3' 89 | final ESPRESSO_VERSION = '2.2.1' 90 | final RUNNER_VERSION = '0.4' 91 | final BUTTERKNIFE_VERSION = '8.1.0' 92 | final AUTO_VALUE_VERSION = '1.2' 93 | final DBFLOW_VERSION = '3.1.1' 94 | 95 | def daggerCompiler = "com.google.dagger:dagger-compiler:$DAGGER_VERSION" 96 | def jUnit = "junit:junit:4.12" 97 | def mockito = "org.mockito:mockito-core:1.10.19" 98 | 99 | // App Dependencies 100 | compile "com.google.android.gms:play-services-gcm:$PLAY_SERVICES_VERSION" 101 | compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION" 102 | compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION" 103 | compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION" 104 | compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION" 105 | compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION" 106 | 107 | compile 'com.squareup.sqlbrite:sqlbrite:0.5.0' 108 | compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION" 109 | compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION" 110 | compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION" 111 | compile "com.squareup.okhttp3:okhttp:$OKHTTP_VERSION" 112 | compile "com.squareup.okhttp3:logging-interceptor:$OKHTTP_VERSION" 113 | 114 | compile 'com.github.bumptech.glide:glide:3.7.0' 115 | compile 'io.reactivex:rxandroid:1.2.1' 116 | compile 'io.reactivex:rxjava:1.1.6' 117 | compile('com.crashlytics.sdk.android:crashlytics:2.5.7@aar') { 118 | transitive = true; 119 | } 120 | 121 | //DBFlow Sqlite orm 122 | apt "com.github.Raizlabs.DBFlow:dbflow-processor:$DBFLOW_VERSION" 123 | compile "com.github.Raizlabs.DBFlow:dbflow-core:$DBFLOW_VERSION" 124 | compile "com.github.Raizlabs.DBFlow:dbflow:$DBFLOW_VERSION" 125 | 126 | compile('com.github.afollestad.material-dialogs:core:0.8.5.7@aar') { 127 | transitive = true 128 | } 129 | 130 | //Facebook stetho 131 | compile 'com.facebook.stetho:stetho:1.3.1' 132 | compile 'com.facebook.stetho:stetho-okhttp3:1.3.1' 133 | compile 'com.github.PhilJay:MPAndroidChart:v2.2.4' 134 | 135 | compile 'com.jakewharton.timber:timber:4.1.2' 136 | compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION" 137 | apt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION" 138 | 139 | // Replace provided dependency below with official AutoValue once this issue is fixed 140 | // https://github.com/google/auto/issues/268 141 | provided 'com.jakewharton.auto.value:auto-value-annotations:1.2-update1' 142 | apt "com.google.auto.value:auto-value:$AUTO_VALUE_VERSION" 143 | apt 'com.ryanharter.auto.value:auto-value-parcel:0.2.3-rc2' 144 | apt 'com.ryanharter.auto.value:auto-value-gson:0.3.2-rc1' 145 | apt 'com.squareup:javapoet:1.7.0' // https://github.com/rharter/auto-value-parcel/issues/64 146 | 147 | compile "com.google.dagger:dagger:$DAGGER_VERSION" 148 | provided 'org.glassfish:javax.annotation:10.0-b28' //Required by Dagger2 149 | apt daggerCompiler 150 | testApt daggerCompiler 151 | androidTestApt daggerCompiler 152 | 153 | // Instrumentation test dependencies 154 | androidTestCompile jUnit 155 | androidTestCompile mockito 156 | androidTestCompile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION" 157 | androidTestCompile("com.android.support.test.espresso:espresso-contrib:$ESPRESSO_VERSION") { 158 | exclude group: 'com.android.support', module: 'appcompat' 159 | exclude group: 'com.android.support', module: 'support-v4' 160 | exclude group: 'com.android.support', module: 'recyclerview-v7' 161 | } 162 | androidTestCompile "com.android.support.test.espresso:espresso-core:$ESPRESSO_VERSION" 163 | androidTestCompile "com.android.support.test:runner:$RUNNER_VERSION" 164 | androidTestCompile "com.android.support.test:rules:$RUNNER_VERSION" 165 | androidTestCompile "com.crittercism.dexmaker:dexmaker:$DEXMAKER_VERSION" 166 | androidTestCompile "com.crittercism.dexmaker:dexmaker-dx:$DEXMAKER_VERSION" 167 | androidTestCompile "com.crittercism.dexmaker:dexmaker-mockito:$DEXMAKER_VERSION" 168 | 169 | // Unit tests dependencies 170 | testCompile jUnit 171 | testCompile mockito 172 | testCompile "org.hamcrest:hamcrest-core:$HAMCREST_VERSION" 173 | testCompile "org.hamcrest:hamcrest-library:$HAMCREST_VERSION" 174 | testCompile "org.hamcrest:hamcrest-integration:$HAMCREST_VERSION" 175 | testCompile 'org.robolectric:robolectric:3.1' 176 | } 177 | 178 | // Log out test results to console 179 | tasks.matching {it instanceof Test}.all { 180 | testLogging.events = ["failed", "passed", "skipped"] 181 | } 182 | 183 | 184 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/stockgraph/StockGraphFragment.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.stockgraph; 2 | 3 | import com.github.mikephil.charting.charts.LineChart; 4 | import com.github.mikephil.charting.data.Entry; 5 | import com.github.mikephil.charting.data.LineData; 6 | import com.github.mikephil.charting.data.LineDataSet; 7 | import com.github.mikephil.charting.highlight.Highlight; 8 | import com.github.mikephil.charting.listener.ChartTouchListener; 9 | import com.github.mikephil.charting.listener.OnChartGestureListener; 10 | import com.github.mikephil.charting.listener.OnChartValueSelectedListener; 11 | 12 | import android.graphics.Color; 13 | import android.os.Bundle; 14 | import android.support.annotation.Nullable; 15 | import android.support.v4.app.Fragment; 16 | import android.view.LayoutInflater; 17 | import android.view.MotionEvent; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.widget.ProgressBar; 21 | import android.widget.Toast; 22 | 23 | import java.io.Serializable; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | import javax.inject.Inject; 28 | 29 | import butterknife.BindView; 30 | import butterknife.ButterKnife; 31 | import rajan.udacity.stock.hawk.R; 32 | import rajan.udacity.stock.hawk.data.model.financechart.FinanceChartCallBack; 33 | import rajan.udacity.stock.hawk.ui.base.BaseActivity; 34 | import rajan.udacity.stock.hawk.util.Constants; 35 | import rajan.udacity.stock.hawk.util.Utils; 36 | 37 | /** 38 | * Created by Rajan Maurya on 30/08/16. 39 | */ 40 | public class StockGraphFragment extends Fragment implements StockGraphMvpView, 41 | OnChartGestureListener, OnChartValueSelectedListener { 42 | 43 | private static final String LOG_TAG = StockGraphFragment.class.getSimpleName(); 44 | 45 | @BindView(R.id.linechart) 46 | LineChart mChart; 47 | 48 | @BindView(R.id.progressbar) 49 | ProgressBar mProgressBar; 50 | 51 | @Inject 52 | StockGraphPresenter mStockGraphPresenter; 53 | 54 | private String mSymbol; 55 | private List lables; 56 | private List values; 57 | 58 | public static StockGraphFragment newInstance(String symbol) { 59 | StockGraphFragment fragment = new StockGraphFragment(); 60 | Bundle args = new Bundle(); 61 | args.putString(Constants.SYMBOL, symbol); 62 | fragment.setArguments(args); 63 | return fragment; 64 | } 65 | 66 | @Override 67 | public void onCreate(@Nullable Bundle savedInstanceState) { 68 | super.onCreate(savedInstanceState); 69 | ((BaseActivity)getActivity()).activityComponent().inject(this); 70 | lables = new ArrayList<>(); 71 | values = new ArrayList<>(); 72 | if (getArguments() != null) { 73 | mSymbol = getArguments().getString(Constants.SYMBOL); 74 | } 75 | } 76 | 77 | @Override 78 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 79 | Bundle savedInstanceState) { 80 | View rootView = inflater.inflate(R.layout.fragment_stocks_graph, container, false); 81 | 82 | ButterKnife.bind(this, rootView); 83 | mStockGraphPresenter.attachView(this); 84 | 85 | initGraph(); 86 | 87 | mStockGraphPresenter.loadFinanceChartData(mSymbol); 88 | 89 | return rootView; 90 | } 91 | 92 | @SuppressWarnings(value = "unchecked") 93 | @Override 94 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 95 | super.onActivityCreated(savedInstanceState); 96 | if (savedInstanceState != null) { 97 | lables = savedInstanceState.getStringArrayList(Constants.GRAPH_LABLES); 98 | values = (List) savedInstanceState.getSerializable(Constants.GRAPH_VALUES); 99 | } 100 | } 101 | 102 | @Override 103 | public void initGraph() { 104 | mChart.setOnChartGestureListener(this); 105 | mChart.setOnChartValueSelectedListener(this); 106 | mChart.setDrawGridBackground(false); 107 | mChart.setTouchEnabled(true); 108 | mChart.setDragEnabled(true); 109 | mChart.setScaleEnabled(true); 110 | mChart.setVisibleXRangeMaximum(20); 111 | mChart.setPinchZoom(true); 112 | mChart.getAxisRight().setEnabled(false); 113 | mChart.setDescription(""); 114 | mChart.setNoDataText(""); 115 | mChart.setNoDataTextDescription(""); 116 | mChart.invalidate(); 117 | } 118 | 119 | @Override 120 | public void showFinanceChartData(FinanceChartCallBack financeChartCallBack) { 121 | getActivity().setTitle(financeChartCallBack.getMeta().getCompanyName()); 122 | lables = Utils.getPlottingLables(getActivity(), financeChartCallBack.getSeries()); 123 | values = Utils.getPlottingValues(financeChartCallBack.getSeries()); 124 | 125 | getActivity().runOnUiThread(new Runnable() { 126 | @Override 127 | public void run() { 128 | 129 | ArrayList val = new ArrayList(); 130 | for (int i = 0; i < lables.size(); i++) { 131 | val.add(new Entry(values.get(i), i)); 132 | } 133 | 134 | LineDataSet set = new LineDataSet(val, "Stocks Data"); 135 | 136 | set.enableDashedLine(10f, 5f, 0f); 137 | set.enableDashedHighlightLine(10f, 5f, 0f); 138 | set.setColor(Color.BLACK); 139 | set.setCircleColor(Color.BLACK); 140 | set.setLineWidth(1f); 141 | set.setCircleRadius(3f); 142 | set.setDrawCircleHole(false); 143 | set.setValueTextSize(9f); 144 | set.setDrawFilled(true); 145 | 146 | ArrayList dates = new ArrayList(); 147 | for (int i = 0; i < lables.size(); i++) { 148 | dates.add(i, lables.get(i)); 149 | } 150 | LineData data = new LineData(lables, set); 151 | mChart.setData(data); 152 | mChart.invalidate(); 153 | 154 | } 155 | }); 156 | } 157 | 158 | 159 | @Override 160 | public void showProgressBar(Boolean show) { 161 | if (show) { 162 | mProgressBar.setVisibility(View.VISIBLE); 163 | } else { 164 | mProgressBar.setVisibility(View.GONE); 165 | } 166 | } 167 | 168 | @Override 169 | public void showError() { 170 | Toast.makeText(getActivity(), getResources() 171 | .getString(R.string.failed_to_load_finanace_chart_data), Toast.LENGTH_SHORT).show(); 172 | } 173 | 174 | @Override 175 | public void onDestroyView() { 176 | super.onDestroyView(); 177 | mStockGraphPresenter.detachView(); 178 | 179 | } 180 | 181 | @Override 182 | public void onSaveInstanceState(Bundle savedInstanceState) { 183 | super.onSaveInstanceState(savedInstanceState); 184 | savedInstanceState.putStringArrayList(Constants.GRAPH_LABLES, (ArrayList) lables); 185 | savedInstanceState.putSerializable(Constants.GRAPH_VALUES, (Serializable) values); 186 | 187 | 188 | } 189 | 190 | @Override 191 | public void onChartGestureStart(MotionEvent me, 192 | ChartTouchListener.ChartGesture lastPerformedGesture) { 193 | 194 | } 195 | 196 | @Override 197 | public void onChartGestureEnd(MotionEvent me, 198 | ChartTouchListener.ChartGesture lastPerformedGesture) { 199 | 200 | } 201 | 202 | @Override 203 | public void onChartLongPressed(MotionEvent me) { 204 | 205 | } 206 | 207 | @Override 208 | public void onChartDoubleTapped(MotionEvent me) { 209 | 210 | } 211 | 212 | @Override 213 | public void onChartSingleTapped(MotionEvent me) { 214 | 215 | } 216 | 217 | @Override 218 | public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) { 219 | 220 | } 221 | 222 | @Override 223 | public void onChartScale(MotionEvent me, float scaleX, float scaleY) { 224 | 225 | } 226 | 227 | @Override 228 | public void onChartTranslate(MotionEvent me, float dX, float dY) { 229 | 230 | } 231 | 232 | @Override 233 | public void onValueSelected(Entry e, int dataSetIndex, Highlight h) { 234 | 235 | } 236 | 237 | @Override 238 | public void onNothingSelected() { 239 | 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /app/src/main/java/rajan/udacity/stock/hawk/ui/main/MainActivity.java: -------------------------------------------------------------------------------- 1 | package rajan.udacity.stock.hawk.ui.main; 2 | 3 | import com.afollestad.materialdialogs.MaterialDialog; 4 | import com.afollestad.materialdialogs.Theme; 5 | 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.graphics.Color; 9 | import android.os.Bundle; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.support.v7.widget.helper.ItemTouchHelper; 13 | import android.text.InputType; 14 | import android.view.Gravity; 15 | import android.view.Menu; 16 | import android.view.MenuItem; 17 | import android.widget.Toast; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import javax.inject.Inject; 23 | 24 | import butterknife.BindView; 25 | import butterknife.ButterKnife; 26 | import butterknife.OnClick; 27 | import rajan.udacity.stock.hawk.R; 28 | import rajan.udacity.stock.hawk.data.SyncService; 29 | import rajan.udacity.stock.hawk.data.model.Quote; 30 | import rajan.udacity.stock.hawk.touch_helper.SimpleItemTouchHelperCallback; 31 | import rajan.udacity.stock.hawk.ui.base.BaseActivity; 32 | import rajan.udacity.stock.hawk.ui.stockgraph.StockGraphActivity; 33 | import rajan.udacity.stock.hawk.util.Constants; 34 | import rajan.udacity.stock.hawk.util.DialogFactory; 35 | import rajan.udacity.stock.hawk.util.NetworkUtil; 36 | 37 | public class MainActivity extends BaseActivity implements 38 | MainMvpView, StockAdapter.DismissAndOnClickItemStockListener { 39 | 40 | private static final String EXTRA_TRIGGER_SYNC_FLAG = 41 | "rajan.udacity.stock.hawk.ui.main.MainActivity.EXTRA_TRIGGER_SYNC_FLAG"; 42 | 43 | @Inject 44 | MainPresenter mMainPresenter; 45 | 46 | @Inject 47 | StockAdapter mStocksAdapter; 48 | 49 | @BindView(R.id.recycler_view) 50 | RecyclerView mRecyclerView; 51 | 52 | /** 53 | * Return an Intent to start this Activity. 54 | * triggerDataSyncOnCreate allows disabling the background sync service onCreate. Should 55 | * only be set to false during testing. 56 | */ 57 | public static Intent getStartIntent(Context context, boolean triggerDataSyncOnCreate) { 58 | Intent intent = new Intent(context, MainActivity.class); 59 | intent.putExtra(EXTRA_TRIGGER_SYNC_FLAG, triggerDataSyncOnCreate); 60 | return intent; 61 | } 62 | 63 | @Override 64 | public void onItemClick(String symbol) { 65 | Intent intent = new Intent(this, StockGraphActivity.class); 66 | intent.putExtra(Constants.SYMBOL, symbol); 67 | startActivity(intent); 68 | } 69 | 70 | @Override 71 | protected void onCreate(Bundle savedInstanceState) { 72 | super.onCreate(savedInstanceState); 73 | activityComponent().inject(this); 74 | setContentView(R.layout.activity_main); 75 | ButterKnife.bind(this); 76 | 77 | mRecyclerView.setAdapter(mStocksAdapter); 78 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 79 | mStocksAdapter.setOnDismissStockListener(this); 80 | mMainPresenter.attachView(this); 81 | mMainPresenter.loadStocks(); 82 | 83 | ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(mStocksAdapter); 84 | ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback); 85 | itemTouchHelper.attachToRecyclerView(mRecyclerView); 86 | 87 | if (getIntent().getBooleanExtra(EXTRA_TRIGGER_SYNC_FLAG, true)) { 88 | startService(SyncService.getStartIntent(this)); 89 | } 90 | } 91 | 92 | @Override 93 | protected void onStart() { 94 | super.onStart(); 95 | mMainPresenter.loadChangeInPercent(); 96 | } 97 | 98 | @OnClick(R.id.fb_add_stock) 99 | void onClickAddStock() { 100 | if (NetworkUtil.isNetworkConnected(this)) { 101 | showMaterialDialogAddStock(); 102 | } else { 103 | Toast.makeText(this, getResources().getString(R.string.network_toast), 104 | Toast.LENGTH_SHORT).show(); 105 | } 106 | } 107 | 108 | 109 | @Override 110 | public void onStockDismiss(String symbol) { 111 | mMainPresenter.deleteStock(symbol); 112 | } 113 | 114 | @Override 115 | protected void onDestroy() { 116 | super.onDestroy(); 117 | mMainPresenter.detachView(); 118 | } 119 | 120 | /***** MVP View methods implementation *****/ 121 | 122 | @Override 123 | public void showStocks(List stocks) { 124 | mStocksAdapter.setStocks(stocks); 125 | mStocksAdapter.notifyDataSetChanged(); 126 | } 127 | 128 | @Override 129 | public void showError() { 130 | DialogFactory.createGenericErrorDialog(this, getString(R.string.error_loading_stocks)) 131 | .show(); 132 | } 133 | 134 | @Override 135 | public void showStocksEmpty() { 136 | List quotes = new ArrayList<>(); 137 | mStocksAdapter.setStocks(quotes); 138 | mStocksAdapter.notifyDataSetChanged(); 139 | Toast.makeText(this, R.string.empty_stocks, Toast.LENGTH_LONG).show(); 140 | } 141 | 142 | @Override 143 | public void showStock(Quote quote) { 144 | mStocksAdapter.setStock(quote); 145 | } 146 | 147 | @Override 148 | public void showStockDoesNotExist() { 149 | Toast toast = Toast.makeText(this, getResources().getString(R.string.stock_does_not_exist), 150 | Toast.LENGTH_LONG); 151 | toast.setGravity(Gravity.CENTER, Gravity.CENTER, 0); 152 | toast.show(); 153 | } 154 | 155 | @Override 156 | public void showMaterialDialogAddStock() { 157 | new MaterialDialog.Builder(this).title(R.string.symbol_search) 158 | .content(R.string.content_test) 159 | .positiveColorRes(R.color.white) 160 | .positiveColor(Color.WHITE) 161 | .theme(Theme.DARK) 162 | .backgroundColorRes(R.color.material_blue_grey_800) 163 | .inputType(InputType.TYPE_CLASS_TEXT) 164 | .input(R.string.input_hint, R.string.input_prefill, 165 | new MaterialDialog.InputCallback() { 166 | @Override 167 | public void onInput(MaterialDialog dialog, CharSequence input) { 168 | // On FAB click, receive user input. Make sure the stock doesn't 169 | // already exist in the DB and proceed accordingly 170 | if (checkSymbolExistOrNot(input.toString(), 171 | mStocksAdapter.getStocks())) { 172 | showStockAlreadyExist(); 173 | } else if (!input.toString().isEmpty()) { 174 | mMainPresenter.loadStock(input.toString()); 175 | } 176 | } 177 | }) 178 | .show(); 179 | } 180 | 181 | @Override 182 | public Boolean checkSymbolExistOrNot(String symbol, List stock) { 183 | return mMainPresenter.checkStocksExistOrNot(symbol, stock); 184 | } 185 | 186 | @Override 187 | public void showStockAlreadyExist() { 188 | Toast toast = Toast.makeText(this, getResources().getString(R.string.stocks_already_exist), 189 | Toast.LENGTH_LONG); 190 | toast.setGravity(Gravity.CENTER, Gravity.CENTER, 0); 191 | toast.show(); 192 | } 193 | 194 | @Override 195 | public void showChangeInPercent(Boolean changeInPercent) { 196 | mStocksAdapter.setChangeInPercent(changeInPercent); 197 | } 198 | 199 | @Override 200 | public void updateChangeInPercent(Boolean changeInPercent) { 201 | mStocksAdapter.setChangeInPercent(changeInPercent); 202 | } 203 | 204 | @Override 205 | public boolean onCreateOptionsMenu(Menu menu) { 206 | getMenuInflater().inflate(R.menu.menu_main, menu); 207 | return true; 208 | } 209 | 210 | @Override 211 | public boolean onOptionsItemSelected(MenuItem item) { 212 | // Handle action bar item clicks here. The action bar will 213 | // automatically handle clicks on the Home/Up button, so long 214 | // as you specify a parent activity in AndroidManifest.xml. 215 | int id = item.getItemId(); 216 | 217 | if (id == R.id.action_change_units) { 218 | mMainPresenter.updateChangeInPercent(); 219 | } 220 | 221 | return super.onOptionsItemSelected(item); 222 | } 223 | } 224 | --------------------------------------------------------------------------------