├── .github └── workflows │ └── test.yaml ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── proguard-rules.pro ├── release │ ├── app.aab │ └── output.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── currencyinfo.json │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── binarybricks │ │ └── coinbit │ │ ├── CoinBitApplication.kt │ │ ├── data │ │ ├── CoinBitCache.kt │ │ ├── PreferenceManager.kt │ │ └── database │ │ │ ├── BigDecimalConverter.kt │ │ │ ├── CoinBitDatabase.kt │ │ │ ├── dao │ │ │ ├── CoinTransactionDao.kt │ │ │ ├── ExchangeDao.kt │ │ │ └── WatchedCoinDao.kt │ │ │ └── entities │ │ │ ├── Coin.kt │ │ │ ├── CoinTransaction.kt │ │ │ ├── Exchange.kt │ │ │ └── WatchedCoin.kt │ │ ├── epoxymodels │ │ ├── AddCoinItemView.kt │ │ ├── AddCoinTransactionItemView.kt │ │ ├── ChipGroupItemView.kt │ │ ├── CoinAboutItemView.kt │ │ ├── CoinHistoricalChartItemView.kt │ │ ├── CoinInfoItemView.kt │ │ ├── CoinItemView.kt │ │ ├── CoinNewsItemView.kt │ │ ├── CoinPositionItemView.kt │ │ ├── CoinSearchItemView.kt │ │ ├── CoinStatsticsItemView.kt │ │ ├── CoinTickerItemView.kt │ │ ├── CoinTickerView.kt │ │ ├── CoinTransactionHistoryItemView.kt │ │ ├── DashboardHeaderItemView.kt │ │ ├── EmptyCoinItemView.kt │ │ ├── ExchangePairItemView.kt │ │ ├── ExpandedNewsItemView.kt │ │ ├── GenericFooterItemView.kt │ │ ├── LabelItemView.kt │ │ ├── NewsItemView.kt │ │ ├── ShortNewsItemView.kt │ │ └── TopCardItemView.kt │ │ ├── featurecomponents │ │ ├── ModuleItem.kt │ │ ├── cointickermodule │ │ │ ├── CoinTickerContract.kt │ │ │ ├── CoinTickerPresenter.kt │ │ │ └── CoinTickerRepository.kt │ │ ├── cryptonewsmodule │ │ │ ├── CryptoNewsContract.kt │ │ │ ├── CryptoNewsPresenter.kt │ │ │ └── CryptoNewsRepository.kt │ │ └── historicalchartmodule │ │ │ ├── ChartRepository.kt │ │ │ └── HistoricalChartAdapter.kt │ │ ├── features │ │ ├── BasePresenter.kt │ │ ├── BaseView.kt │ │ ├── CryptoCompareRepository.kt │ │ ├── HomeActivity.kt │ │ ├── coin │ │ │ ├── CoinContract.kt │ │ │ ├── CoinFragment.kt │ │ │ └── CoinPresenter.kt │ │ ├── coindetails │ │ │ ├── CoinDetailPagerPresenter.kt │ │ │ ├── CoinDetailPresenter.kt │ │ │ ├── CoinDetailsActivity.kt │ │ │ ├── CoinDetailsContract.kt │ │ │ ├── CoinDetailsPagerActivity.kt │ │ │ ├── CoinDetailsPagerAdapter.kt │ │ │ ├── CoinDetailsPagerContract.kt │ │ │ └── CoinDetailsPagerRepository.kt │ │ ├── coinsearch │ │ │ ├── CoinDiscoveryContract.kt │ │ │ ├── CoinDiscoveryFragment.kt │ │ │ ├── CoinDiscoveryPresenter.kt │ │ │ ├── CoinSearchActivity.kt │ │ │ ├── CoinSearchContract.kt │ │ │ └── CoinSearchPresenter.kt │ │ ├── dashboard │ │ │ ├── CoinDashboardContract.kt │ │ │ ├── CoinDashboardFragment.kt │ │ │ ├── CoinDashboardPresenter.kt │ │ │ └── DashboardRepository.kt │ │ ├── exchangesearch │ │ │ └── ExchangeSearchActivity.kt │ │ ├── launch │ │ │ ├── IntroFragment.kt │ │ │ ├── LaunchActivity.kt │ │ │ ├── LaunchContract.kt │ │ │ └── LaunchPresenter.kt │ │ ├── newslist │ │ │ └── NewsListActivity.kt │ │ ├── pairsearch │ │ │ └── PairSearchActivity.kt │ │ ├── settings │ │ │ ├── SettingsContract.kt │ │ │ ├── SettingsFragment.kt │ │ │ └── SettingsPresenter.kt │ │ ├── ticker │ │ │ └── CoinTickerActivity.kt │ │ └── transaction │ │ │ ├── CoinTransactionActivity.kt │ │ │ ├── CoinTransactionContract.kt │ │ │ └── CoinTransactionPresenter.kt │ │ ├── network │ │ ├── NetworkConstants.kt │ │ ├── api │ │ │ ├── API.kt │ │ │ └── CryptoAPI.kt │ │ └── models │ │ │ ├── CCCoin.kt │ │ │ ├── CoinPair.kt │ │ │ ├── CoinPrice.kt │ │ │ ├── CryptoCompareHistoricalResponse.kt │ │ │ ├── CryptoCompareNews.kt │ │ │ ├── CryptoPanicNews.kt │ │ │ ├── CryptoTicker.kt │ │ │ └── ExchangePair.kt │ │ └── utils │ │ ├── CoinBitExtendedCurrency.kt │ │ ├── Constants.kt │ │ ├── Extensions.kt │ │ ├── Formaters.kt │ │ ├── Parser.kt │ │ ├── Utils.kt │ │ ├── resourcemanager │ │ ├── AndroidResourceManager.kt │ │ └── AndroidResourceManagerImpl.kt │ │ └── ui │ │ ├── IntroPageTransformer.kt │ │ └── OnVerticalScrollListener.kt │ └── res │ ├── animator │ └── card_down_animation.xml │ ├── color │ ├── bottom_nav_button_forg.xml │ └── chip_color.xml │ ├── drawable-hdpi │ └── menu_search.png │ ├── drawable-mdpi │ └── menu_search.png │ ├── drawable-xhdpi │ ├── add_new.png │ ├── menu_search.png │ └── news_icon.png │ ├── drawable-xxhdpi │ ├── add_new.png │ ├── menu_search.png │ └── news_icon.png │ ├── drawable-xxxhdpi │ ├── add_new.png │ ├── menu_search.png │ └── news_icon.png │ ├── drawable │ ├── background_rounded_all.xml │ ├── button_back.xml │ ├── divider_thin_horizontal.xml │ ├── ic_notification.xml │ ├── ic_refresh.xml │ ├── ic_watch.xml │ ├── ic_watching.xml │ ├── ic_watchlist.xml │ ├── range_radio_btn_selector.xml │ ├── range_radio_btn_selector_background.xml │ ├── ripple_background.xml │ ├── ripple_background_rounded_bottom.xml │ ├── ripple_background_rounded_top.xml │ ├── search_icon.xml │ ├── settings_icon.xml │ └── top_card_background.xml │ ├── layout │ ├── activity_coin_details.xml │ ├── activity_coin_search.xml │ ├── activity_coin_ticker_list.xml │ ├── activity_coin_transaction.xml │ ├── activity_exchange_pair_search.xml │ ├── activity_home.xml │ ├── activity_launch.xml │ ├── activity_news_list.xml │ ├── activity_pager_coin_details.xml │ ├── carousal_module.xml │ ├── chip_group_module.xml │ ├── coin_about_module.xml │ ├── coin_add_transaction_module.xml │ ├── coin_info_module.xml │ ├── coin_label_module.xml │ ├── coin_news_module.xml │ ├── coin_position_card_module.xml │ ├── coin_search_item.xml │ ├── coin_statistic_module.xml │ ├── coin_ticker_module.xml │ ├── coin_transaction_module.xml │ ├── dashboard_coin_list_header_module.xml │ ├── dashboard_coin_module.xml │ ├── dashboard_empty_card_module.xml │ ├── dashboard_header_module.xml │ ├── dashboard_new_coin_module.xml │ ├── dashboard_news_module.xml │ ├── discovery_news_module.xml │ ├── exchange_pair_search_item.xml │ ├── fragment_coin_details.xml │ ├── fragment_dashboard.xml │ ├── fragment_discovery.xml │ ├── fragment_settings.xml │ ├── generic_footer_module.xml │ ├── historical_chart_module.xml │ ├── intro_fragment_layout.xml │ ├── news_item.xml │ ├── ticker_item.xml │ ├── toolbar.xml │ └── top_card_module.xml │ ├── menu │ ├── bottom_navigation_main.xml │ ├── coin_details_menu.xml │ └── home_menu.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── raw │ ├── bell.json │ ├── graph.json │ ├── lock.json │ └── smiley_stack.json │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── styles.xml ├── attribution.md ├── build.gradle ├── coinbit_privacy_policy.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── variant_1 │ ├── 0.jpg │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ └── 4.jpg ├── variant_3 │ ├── 0.jpg │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ └── 4.jpg └── variant_main │ ├── 0.jpg │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ └── 4.jpg └── settings.gradle /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Testing Workflow 2 | 3 | # TODO add option to upload to firebase, add option to upload artifact, push to google play when merging to master 4 | # Step 1: Choose the branch or branches you want to run this workflow 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | testing: 15 | name: Lint Check and Testing 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Clone Repo 20 | uses: actions/checkout@v2 21 | 22 | - name: Set up JDK 1.8 23 | uses: actions/setup-java@v1 24 | with: 25 | java-version: 1.8 26 | 27 | # Run ktLint to ensure formatting. 28 | - name: Running ktLint 29 | run: ./gradlew lintKotlin 30 | 31 | # Check the code with Android linter 32 | - name: Run Android Linter 33 | run: ./gradlew lintDebug 34 | 35 | # Running unit test 36 | - name: Run Unit Tests 37 | run: ./gradlew testDebugUnitTest 38 | 39 | # Assemble debug apk to send to firebase test lab 40 | - name: Assemble Debug APK 41 | run: ./gradlew assembleDebug -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | 43 | # Keystore files 44 | *.jks 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | 49 | 50 | # Freeline 51 | freeline.py 52 | freeline/ 53 | freeline_project_description.json 54 | 55 | target 56 | test-output 57 | .project 58 | .settings 59 | .classpath 60 | .springBeans 61 | .metadata 62 | .idea/ 63 | .DS_Store 64 | **/src/generated/* 65 | resourceVersions.properties 66 | Servers/ 67 | .gradle 68 | /local.properties 69 | /.idea/workspace.xml 70 | /build 71 | /app/src/main/res/values/com_crashlytics_export_strings.xml 72 | /app/src/main/assets/crashlytics-build.properties 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Testing Workflow](https://github.com/pranayairan/CoinBit/workflows/Testing%20Workflow/badge.svg) 2 | 3 | # CoinBit 4 | CoinBit is a beautiful CryptoCurrency app, completely open sourced and 100% in kotlin. It supports following features 5 | 6 | * Track prices of over 3000+ currencies over 100+ exchanges 7 | * Get top coins, top pairs, top exchanges by volume. 8 | * Track latest news for all the coins and crypto community in general 9 | * Completely secure, your data never leaves your device. 10 | * Choose your home currency and track prices in it. 11 | * Made with ❤️ and help from [open source community](https://github.com/pranayairan/CoinBit/blob/master/attribution.md) 12 | * Open for contribution, please send a pull request. 13 | 14 | App available on Google Play: https://play.google.com/store/apps/details?id=com.binarybricks.coinbit 15 | 16 | ## Work in progress 17 | 18 | * Ability to add transactions 19 | * Ability to change exchanges 20 | * Autorefresh of prices 21 | * Candle charts 22 | 23 | # App Architecture 24 | 25 | Currently the app is using MVP with a repository. We have 2 data source, Room and in memory cache. Data flow is like this 26 | 27 | Fragments/Activities -> Presenter -> Repo -> Network/Cache (room/in memory) 28 | 29 | In coming days I would like to remove inmemory cache and make everything come from Room. Network will keep the Room cache updated. This will give app some offline abilities. 30 | 31 | I am also using a ton of recycler view with [Adapter Delegate Pattern](http://hannesdorfmann.com/android/adapter-delegates). This enables me to plug and play the screens like Lego blocks. I am thinking to replace this with Epoxy in coming days. 32 | 33 | 34 | # Screenshots 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "577274068401", 4 | "firebase_url": "https://coinbit-4d26f.firebaseio.com", 5 | "project_id": "coinbit-4d26f", 6 | "storage_bucket": "coinbit-4d26f.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:577274068401:android:f806e4e77b98053f", 12 | "android_client_info": { 13 | "package_name": "com.binarybricks.coinbit" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "577274068401-7cm0ud0b8v96lsq35icvmoncsehvbupm.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyD-mv2zb2UYQ1LbJWzKEd0WZrU1CCfSIEM" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "577274068401-7cm0ud0b8v96lsq35icvmoncsehvbupm.apps.googleusercontent.com", 32 | "client_type": 3 33 | } 34 | ] 35 | } 36 | } 37 | } 38 | ], 39 | "configuration_version": "1" 40 | } -------------------------------------------------------------------------------- /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 /Applications/sdk/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 | -keepclassmembers class * implements android.os.Parcelable { 20 | static ** CREATOR; 21 | } 22 | 23 | #this is for crashlytics logs which will deofubscate errors 24 | -keepattributes *Annotation* 25 | -keepattributes SourceFile,LineNumberTable 26 | -keep public class * extends java.lang.Exception 27 | 28 | 29 | ## --------------- Proguard configuration for Okhttp ---------------------- 30 | # JSR 305 annotations are for embedding nullability information. 31 | -dontwarn javax.annotation.** 32 | 33 | # A resource is loaded with a relative path so the package of this class must be preserved. 34 | -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase 35 | 36 | # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. 37 | -dontwarn org.codehaus.mojo.animal_sniffer.* 38 | 39 | # OkHttp platform used only on JVM and when Conscrypt dependency is available. 40 | -dontwarn okhttp3.internal.platform.ConscryptPlatform 41 | 42 | ## --------------- Proguard configuration for Retrofit ---------------------- 43 | # don't obfuscate data model objects because GSON needs to rebuild them using reflection 44 | -keep class com.binarybricks.coinbit.network.models.** { *; } 45 | -keepclassmembers enum com.binarybricks.coinbit.network.models.** { *; } 46 | 47 | -keep class com.binarybricks.coinbit.data.database.entities.** { *; } 48 | -keepclassmembers enum com.binarybricks.coinbit.data.database.entities.** { *; } 49 | 50 | # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and 51 | # EnclosingMethod is required to use InnerClasses. 52 | -keepattributes Signature, InnerClasses, EnclosingMethod 53 | 54 | # Retain service method parameters when optimizing. 55 | -keepclassmembers,allowshrinking,allowobfuscation interface * { 56 | @retrofit2.http.* ; 57 | } 58 | 59 | # Ignore annotation used for build tooling. 60 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 61 | 62 | # Guarded by a NoClassDefFoundError try/catch and only used when on the classpath. 63 | -dontwarn kotlin.Unit 64 | 65 | # Top-level functions that can only be used by Kotlin. 66 | -dontwarn retrofit2.-KotlinExtensions 67 | 68 | ### Glide, Glide Okttp Module, Glide Transformations 69 | -keep public class * implements com.bumptech.glide.module.GlideModule 70 | -keep public class * extends com.bumptech.glide.module.AppGlideModule 71 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 72 | **[] $VALUES; 73 | public *; 74 | } 75 | 76 | # Gson specific classes 77 | -keep class sun.misc.Unsafe { *; } -------------------------------------------------------------------------------- /app/release/app.aab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranayairan/CoinBit/ce44fc26aafdc180f0b1db7d93b7a141673cb14c/app/release/app.aab -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":6,"versionName":"1.3","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranayairan/CoinBit/ce44fc26aafdc180f0b1db7d93b7a141673cb14c/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/CoinBitApplication.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.util.Log 6 | import androidx.room.Room 7 | import com.binarybricks.coinbit.data.database.CoinBitDatabase 8 | import com.facebook.stetho.Stetho 9 | import com.google.firebase.crashlytics.FirebaseCrashlytics 10 | import timber.log.Timber 11 | import timber.log.Timber.DebugTree 12 | 13 | /** 14 | Created by Pranay Airan 15 | */ 16 | 17 | class CoinBitApplication : Application() { 18 | 19 | companion object { 20 | 21 | private const val DATABASE_NAME = "coinbit.db" 22 | 23 | private lateinit var appContext: Context 24 | var database: CoinBitDatabase? = null 25 | 26 | @JvmStatic 27 | fun getGlobalAppContext(): Context { 28 | return appContext 29 | } 30 | } 31 | 32 | override fun onCreate() { 33 | super.onCreate() 34 | 35 | appContext = applicationContext 36 | 37 | if (BuildConfig.DEBUG) { 38 | Timber.plant(DebugTree()) 39 | Stetho.initializeWithDefaults(this) 40 | } else { 41 | Timber.plant(CrashReportingTree()) 42 | } 43 | 44 | database = Room.databaseBuilder(this, CoinBitDatabase::class.java, DATABASE_NAME).build() 45 | } 46 | 47 | /** A tree which logs important information for crash reporting. */ 48 | private class CrashReportingTree : Timber.Tree() { 49 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { 50 | if (priority == Log.VERBOSE || priority == Log.DEBUG) { 51 | return 52 | } 53 | if (priority == Log.ERROR) { 54 | FirebaseCrashlytics.getInstance().log("E/$tag:$message") 55 | } else if (priority == Log.WARN) { 56 | FirebaseCrashlytics.getInstance().log("W/$tag:$message") 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/CoinBitCache.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data 2 | 3 | import com.binarybricks.coinbit.network.models.* 4 | 5 | /** 6 | * Created by Pragya Agrawal 7 | * 8 | * In memory cache for certain objects that we want to cache only for the app session 9 | */ 10 | 11 | object CoinBitCache { 12 | 13 | // cache the news since we don't want to overload the server. 14 | var newsMap: MutableMap = hashMapOf() 15 | 16 | // crypto compare news 17 | var cyrptoCompareNews: MutableList = ArrayList() 18 | 19 | var coinPriceMap: HashMap = hashMapOf() 20 | 21 | var coinExchangeMap: HashMap> = hashMapOf() 22 | 23 | var topCoinsByTotalVolume: ArrayList = ArrayList() 24 | 25 | var topPairsByVolume: ArrayList = ArrayList() 26 | 27 | var topCoinsByTotalVolume24Hours: ArrayList = ArrayList() 28 | 29 | var ticker: MutableMap> = hashMapOf() 30 | 31 | fun updateCryptoCompareNews(cryptoNews: CryptoCompareNews) { 32 | cyrptoCompareNews.remove(cryptoNews) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/database/BigDecimalConverter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data.database 2 | 3 | import androidx.room.TypeConverter 4 | import java.math.BigDecimal 5 | 6 | /** 7 | * Created by Pranay Airan 8 | */ 9 | class BigDecimalConverter { 10 | @TypeConverter 11 | fun fromString(value: String?): BigDecimal { 12 | return if (value == null) BigDecimal.ZERO else BigDecimal(value) 13 | } 14 | 15 | @TypeConverter 16 | fun amountToString(bigDecimal: BigDecimal): String { 17 | return bigDecimal.toPlainString() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/database/CoinBitDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data.database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import com.binarybricks.coinbit.data.database.dao.CoinTransactionDao 7 | import com.binarybricks.coinbit.data.database.dao.ExchangeDao 8 | import com.binarybricks.coinbit.data.database.dao.WatchedCoinDao 9 | import com.binarybricks.coinbit.data.database.entities.CoinTransaction 10 | import com.binarybricks.coinbit.data.database.entities.Exchange 11 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 12 | 13 | /** 14 | * Created by Pragya Agrawal 15 | */ 16 | @Database(entities = [Exchange::class, WatchedCoin::class, CoinTransaction::class], version = 1, exportSchema = false) 17 | @TypeConverters(BigDecimalConverter::class) 18 | abstract class CoinBitDatabase : RoomDatabase() { 19 | 20 | abstract fun exchangeDao(): ExchangeDao 21 | abstract fun watchedCoinDao(): WatchedCoinDao 22 | abstract fun coinTransactionDao(): CoinTransactionDao 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/database/dao/CoinTransactionDao.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data.database.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.binarybricks.coinbit.data.database.entities.CoinTransaction 8 | import kotlinx.coroutines.flow.Flow 9 | 10 | /** 11 | * Created by Pragya Agrawal 12 | * 13 | * Add queries to read/update coinSymbol transaction data from database. 14 | */ 15 | @Dao 16 | interface CoinTransactionDao { 17 | 18 | @Query("select * from cointransaction") 19 | fun getAllCoinTransaction(): Flow> 20 | 21 | @Insert(onConflict = OnConflictStrategy.REPLACE) 22 | suspend fun insertTransaction(coinTransaction: CoinTransaction) 23 | 24 | @Query("SELECT * FROM cointransaction WHERE coinSymbol = :coinSymbol ORDER BY transactionTime ASC") 25 | fun getTransactionsForCoin(coinSymbol: String): Flow> 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/database/dao/ExchangeDao.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data.database.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.binarybricks.coinbit.data.database.entities.Exchange 8 | 9 | /** 10 | * Created by Pragya Agrawal 11 | * 12 | * Add queries to read/update exchange data from database. 13 | */ 14 | @Dao 15 | interface ExchangeDao { 16 | 17 | @Query("select * from exchange") 18 | suspend fun getAllExchanges(): List 19 | 20 | @Insert(onConflict = OnConflictStrategy.REPLACE) 21 | suspend fun insertExchanges(list: List) 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/database/dao/WatchedCoinDao.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data.database.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 8 | import kotlinx.coroutines.flow.Flow 9 | import java.math.BigDecimal 10 | 11 | /** 12 | * Created by Pragya Agrawal 13 | * 14 | * Add queries to read/update coinSymbol data from database. 15 | */ 16 | @Dao 17 | interface WatchedCoinDao { 18 | 19 | @Query("select * from WatchedCoin where purchaseQuantity > 0 OR watched = :watched order by sortOrder") 20 | fun getAllWatchedCoins(watched: Boolean = true): Flow> 21 | 22 | @Query("select * from WatchedCoin where purchaseQuantity > 0 OR watched = :watched order by sortOrder") 23 | suspend fun getAllWatchedCoinsOnetime(watched: Boolean = true): List // this method should be removed 24 | 25 | @Query("select * from WatchedCoin where isTrading = :isTrue order by sortOrder") 26 | fun getAllCoins(isTrue: Boolean = true): Flow> 27 | 28 | @Query("select * from WatchedCoin where symbol = :symbol") 29 | suspend fun getSingleWatchedCoin(symbol: String): List 30 | 31 | @Insert(onConflict = OnConflictStrategy.REPLACE) 32 | suspend fun insertCoinListIntoWatchList(list: List) 33 | 34 | @Insert(onConflict = OnConflictStrategy.REPLACE) 35 | suspend fun insertCoinIntoWatchList(watchedCoin: WatchedCoin) 36 | 37 | @Query("update WatchedCoin set purchaseQuantity = purchaseQuantity + :quantity where symbol=:symbol") 38 | suspend fun addPurchaseQuantityForCoin(quantity: BigDecimal, symbol: String): Int 39 | 40 | @Query("UPDATE WatchedCoin SET watched = :watched WHERE coinId = :coinId") 41 | suspend fun makeCoinWatched(watched: Boolean, coinId: String) 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/database/entities/Coin.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data.database.entities 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.Index 7 | import androidx.room.PrimaryKey 8 | import kotlinx.android.parcel.Parcelize 9 | 10 | /** 11 | * Created by Pragya Agrawal 12 | */ 13 | @Entity(indices = [(Index("coinId", unique = true))]) 14 | @Parcelize 15 | data class Coin( 16 | @ColumnInfo(name = "coinId") var id: String, 17 | @ColumnInfo(name = "url") var url: String, 18 | @ColumnInfo(name = "imageUrl") var imageUrl: String?, 19 | @ColumnInfo(name = "name") var name: String, 20 | @ColumnInfo(name = "symbol") var symbol: String, 21 | @ColumnInfo(name = "coinName") var coinName: String, 22 | @ColumnInfo(name = "fullName") var fullName: String, 23 | @ColumnInfo(name = "algorithm") var algorithm: String?, 24 | @ColumnInfo(name = "proofType") var proofType: String?, 25 | @ColumnInfo(name = "fullyPremined") var fullyPremined: String?, 26 | @ColumnInfo(name = "totalCoinSupply") var totalCoinSupply: String?, 27 | @ColumnInfo(name = "preMinedValue") var preMinedValue: String?, 28 | @ColumnInfo(name = "totalCoinsFreeFloat") var totalCoinsFreeFloat: String?, 29 | @ColumnInfo(name = "sortOrder") var sortOrder: Int?, 30 | @ColumnInfo(name = "sponsored") var sponsored: Boolean = false, 31 | @ColumnInfo(name = "isTrading") var isTrading: Boolean = false, 32 | @ColumnInfo(name = "description") var description: String?, 33 | @ColumnInfo(name = "twitter") var twitter: String?, 34 | @ColumnInfo(name = "website") var website: String?, 35 | @ColumnInfo(name = "reddit") var reddit: String?, 36 | @ColumnInfo(name = "forum") var forum: String?, 37 | @ColumnInfo(name = "github") var github: String?, 38 | @ColumnInfo(name = "id") @PrimaryKey(autoGenerate = true) var idKey: Long = 0 39 | ) : Parcelable 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/database/entities/CoinTransaction.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data.database.entities 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.Index 7 | import androidx.room.PrimaryKey 8 | import kotlinx.android.parcel.Parcelize 9 | import java.math.BigDecimal 10 | 11 | /** 12 | * Created by Pragya Agrawal 13 | */ 14 | @Entity(indices = [(Index("id", unique = true))]) 15 | @Parcelize 16 | data class CoinTransaction( 17 | @ColumnInfo(name = "transactionType") var transactionType: Int, 18 | @ColumnInfo(name = "coinSymbol") var coinSymbol: String, 19 | @ColumnInfo(name = "pair") var pair: String, 20 | @ColumnInfo(name = "buyprice") var buyPrice: BigDecimal, 21 | @ColumnInfo(name = "buypriceHomeCurrency") var buypriceHomeCurrency: BigDecimal, 22 | @ColumnInfo(name = "quantity") var quantity: BigDecimal, 23 | @ColumnInfo(name = "transactionTime") var transactionTime: String, 24 | @ColumnInfo(name = "cost") var cost: String, 25 | @ColumnInfo(name = "exchange") var exchange: String, 26 | @ColumnInfo(name = "exchangeFees") var exchangeFees: BigDecimal, 27 | @ColumnInfo(name = "id") @PrimaryKey(autoGenerate = true) var idKey: Long = 0 28 | ) : Parcelable 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/database/entities/Exchange.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data.database.entities 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.Index 7 | import androidx.room.PrimaryKey 8 | import kotlinx.android.parcel.Parcelize 9 | 10 | /** 11 | * Created by Pragya Agrawal 12 | */ 13 | @Entity(indices = [(Index("exchangeID", unique = true))]) 14 | @Parcelize 15 | data class Exchange( 16 | @ColumnInfo(name = "exchangeID") var id: String, 17 | @ColumnInfo(name = "name") var name: String, 18 | @ColumnInfo(name = "url") var url: String?, 19 | @ColumnInfo(name = "logoUrl") var logoUrl: String?, 20 | @ColumnInfo(name = "itemType") var itemType: String?, 21 | @ColumnInfo(name = "internalName") var internalName: String?, 22 | @ColumnInfo(name = "affiliateUrl") var affiliateUrl: String?, 23 | @ColumnInfo(name = "country") var country: String?, 24 | @ColumnInfo(name = "orderBook") var orderBook: Boolean = false, 25 | @ColumnInfo(name = "trades") var trades: Boolean = false, 26 | @ColumnInfo(name = "recommended") var recommended: Boolean = false, 27 | @ColumnInfo(name = "sponsored") var sponsored: Boolean = false, 28 | @ColumnInfo(name = "id") @PrimaryKey(autoGenerate = true) var idKey: Long = 0 29 | ) : Parcelable 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/data/database/entities/WatchedCoin.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.data.database.entities 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Embedded 6 | import androidx.room.Entity 7 | import androidx.room.Index 8 | import androidx.room.PrimaryKey 9 | import kotlinx.android.parcel.Parcelize 10 | import java.math.BigDecimal 11 | 12 | /** 13 | * Created by Pragya Agrawal 14 | */ 15 | @Entity(indices = [(Index("watched_id", unique = true))]) 16 | @Parcelize 17 | data class WatchedCoin( 18 | @Embedded 19 | val coin: Coin, 20 | var exchange: String, 21 | var fromCurrency: String, 22 | var purchaseQuantity: BigDecimal = BigDecimal.ZERO, 23 | var watched: Boolean = false, 24 | @ColumnInfo(name = "watched_id") @PrimaryKey(autoGenerate = true) var idKey: Long = 0 25 | ) : Parcelable 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/AddCoinItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import androidx.constraintlayout.widget.ConstraintLayout 7 | import com.airbnb.epoxy.CallbackProp 8 | import com.airbnb.epoxy.ModelView 9 | import com.binarybricks.coinbit.R 10 | import com.binarybricks.coinbit.featurecomponents.ModuleItem 11 | 12 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 13 | class AddCoinItemView @JvmOverloads constructor( 14 | context: Context, 15 | attributeSet: AttributeSet? = null, 16 | defStyle: Int = 0, 17 | ) : ConstraintLayout(context, attributeSet, defStyle) { 18 | 19 | private val addCoinCard: View 20 | 21 | init { 22 | View.inflate(context, R.layout.dashboard_new_coin_module, this) 23 | addCoinCard = findViewById(R.id.addCoinCard) 24 | } 25 | 26 | @CallbackProp 27 | fun setAddCoinClickListener(listener: OnClickListener?) { 28 | addCoinCard.setOnClickListener(listener) 29 | } 30 | 31 | object AddCoinModuleItem : ModuleItem 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/AddCoinTransactionItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.Button 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | import com.airbnb.epoxy.CallbackProp 9 | import com.airbnb.epoxy.ModelView 10 | import com.binarybricks.coinbit.R 11 | import com.binarybricks.coinbit.featurecomponents.ModuleItem 12 | 13 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 14 | class AddCoinTransactionItemView @JvmOverloads constructor( 15 | context: Context, 16 | attributeSet: AttributeSet? = null, 17 | defStyle: Int = 0, 18 | ) : ConstraintLayout(context, attributeSet, defStyle) { 19 | 20 | private val btnAddTransaction: Button 21 | 22 | init { 23 | View.inflate(context, R.layout.coin_add_transaction_module, this) 24 | btnAddTransaction = findViewById(R.id.btnAddTransaction) 25 | } 26 | 27 | @CallbackProp 28 | fun setItemClickListener(listener: OnClickListener?) { 29 | btnAddTransaction.setOnClickListener(listener) 30 | } 31 | 32 | object AddCoinTransactionModuleItem : ModuleItem 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/ChipGroupItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import androidx.constraintlayout.widget.ConstraintLayout 7 | import androidx.core.content.ContextCompat 8 | import com.airbnb.epoxy.CallbackProp 9 | import com.airbnb.epoxy.ModelProp 10 | import com.airbnb.epoxy.ModelView 11 | import com.binarybricks.coinbit.R 12 | import com.binarybricks.coinbit.featurecomponents.ModuleItem 13 | import com.binarybricks.coinbit.network.models.CoinPair 14 | import com.google.android.material.chip.Chip 15 | import com.google.android.material.chip.ChipGroup 16 | 17 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 18 | class ChipGroupItemView @JvmOverloads constructor( 19 | context: Context, 20 | attributeSet: AttributeSet? = null, 21 | defStyle: Int = 0, 22 | ) : ConstraintLayout(context, attributeSet, defStyle) { 23 | 24 | private val chipGroupFirst: ChipGroup 25 | private val chipGroupSecond: ChipGroup 26 | private val chipGroupThird: ChipGroup 27 | 28 | private var chipItemClickedListener: OnChipItemClickedListener? = null 29 | 30 | init { 31 | View.inflate(context, R.layout.chip_group_module, this) 32 | chipGroupFirst = findViewById(R.id.chipGroupFirst) 33 | chipGroupSecond = findViewById(R.id.chipGroupSecond) 34 | chipGroupThird = findViewById(R.id.chipGroupThird) 35 | } 36 | 37 | @ModelProp 38 | fun setChipData(chipGroupModuleData: ChipGroupModuleData) { 39 | val chunkedList = chipGroupModuleData.data.chunked(15) 40 | 41 | var i = 0 42 | chunkedList.forEach { 43 | val chipGroup = when (i) { 44 | 1 -> chipGroupFirst 45 | 2 -> chipGroupSecond 46 | else -> chipGroupThird 47 | } 48 | it.forEach { coinPair -> 49 | chipGroup.addView(getChip(chipGroup.context, coinPair)) 50 | } 51 | i++ 52 | } 53 | } 54 | 55 | private fun getChip(context: Context, coinPair: CoinPair): Chip { 56 | val chip = Chip(context) 57 | chip.text = coinPair.fullName 58 | chip.setTextColor(ContextCompat.getColor(context, R.color.primaryTextColor)) 59 | chip.isClickable = true 60 | chip.isCheckable = false 61 | chip.chipBackgroundColor = ContextCompat.getColorStateList(context, R.color.chip_color) 62 | 63 | chip.setOnClickListener { 64 | if (coinPair.symbol != null) { 65 | chipItemClickedListener?.onChipClicked(coinPair.symbol) 66 | } 67 | } 68 | return chip 69 | } 70 | 71 | @CallbackProp 72 | fun setChipClickListener(chipItemClickedListener: OnChipItemClickedListener?) { 73 | this.chipItemClickedListener = chipItemClickedListener 74 | } 75 | 76 | interface OnChipItemClickedListener { 77 | fun onChipClicked(coinSymbol: String) 78 | } 79 | 80 | data class ChipGroupModuleData(val data: List) : ModuleItem 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/CoinInfoItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.TextView 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | import com.airbnb.epoxy.ModelProp 9 | import com.airbnb.epoxy.ModelView 10 | import com.binarybricks.coinbit.R 11 | import com.binarybricks.coinbit.featurecomponents.ModuleItem 12 | import com.binarybricks.coinbit.utils.getDefaultExchangeText 13 | 14 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 15 | class CoinInfoItemView @JvmOverloads constructor( 16 | context: Context, 17 | attributeSet: AttributeSet? = null, 18 | defStyle: Int = 0, 19 | ) : ConstraintLayout(context, attributeSet, defStyle) { 20 | 21 | private val tvFirstTxnTimeAndExchange: TextView 22 | private val tvAlgorithmName: TextView 23 | private val tvProofTypeName: TextView 24 | 25 | init { 26 | View.inflate(context, R.layout.coin_info_module, this) 27 | tvFirstTxnTimeAndExchange = findViewById(R.id.tvFirstTxnTimeAndExchange) 28 | tvAlgorithmName = findViewById(R.id.tvAlgorithmName) 29 | tvProofTypeName = findViewById(R.id.tvProofTypeName) 30 | } 31 | 32 | @ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode]) 33 | fun setExchange(coinInfoModuleData: CoinInfoModuleData) { 34 | var exchange = coinInfoModuleData.exchange 35 | exchange = getDefaultExchangeText(exchange, context) 36 | tvFirstTxnTimeAndExchange.text = exchange 37 | tvAlgorithmName.text = coinInfoModuleData.algorithm 38 | tvProofTypeName.text = coinInfoModuleData.proofType 39 | } 40 | 41 | data class CoinInfoModuleData(val exchange: String, val algorithm: String?, val proofType: String?) : ModuleItem 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/CoinSearchItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import androidx.appcompat.widget.SwitchCompat 9 | import androidx.constraintlayout.widget.ConstraintLayout 10 | import coil.load 11 | import coil.transform.CircleCropTransformation 12 | import com.airbnb.epoxy.CallbackProp 13 | import com.airbnb.epoxy.ModelProp 14 | import com.airbnb.epoxy.ModelView 15 | import com.binarybricks.coinbit.R 16 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 17 | import com.binarybricks.coinbit.network.BASE_CRYPTOCOMPARE_IMAGE_URL 18 | import java.math.BigDecimal 19 | 20 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 21 | class CoinSearchItemView @JvmOverloads constructor( 22 | context: Context, 23 | attributeSet: AttributeSet? = null, 24 | defStyle: Int = 0, 25 | ) : ConstraintLayout(context, attributeSet, defStyle) { 26 | 27 | private val tvCoinName: TextView 28 | private val tvCoinSymbol: TextView 29 | private val ivCoin: ImageView 30 | private val cbWatched: SwitchCompat 31 | private val clCoinInfo: View 32 | 33 | private val cropCircleTransformation by lazy { 34 | CircleCropTransformation() 35 | } 36 | 37 | interface OnSearchItemClickListener { 38 | fun onItemWatchedClicked(watched: Boolean) 39 | } 40 | 41 | init { 42 | View.inflate(context, R.layout.coin_search_item, this) 43 | tvCoinName = findViewById(R.id.tvCoinPercentChange) 44 | tvCoinSymbol = findViewById(R.id.tvCoinName) 45 | ivCoin = findViewById(R.id.ivCoin) 46 | cbWatched = findViewById(R.id.scWatched) 47 | clCoinInfo = findViewById(R.id.clCoinInfo) 48 | } 49 | 50 | @ModelProp 51 | fun setWatchedCoin(watchedCoin: WatchedCoin) { 52 | tvCoinName.text = watchedCoin.coin.coinName 53 | tvCoinSymbol.text = watchedCoin.coin.symbol 54 | 55 | ivCoin.load(BASE_CRYPTOCOMPARE_IMAGE_URL + "${watchedCoin.coin.imageUrl}?width=50") { 56 | crossfade(true) 57 | error(R.mipmap.ic_launcher_round) 58 | transformations(cropCircleTransformation) 59 | } 60 | 61 | val purchaseQuantity = watchedCoin.purchaseQuantity 62 | 63 | cbWatched.isChecked = purchaseQuantity > BigDecimal.ZERO || watchedCoin.watched 64 | } 65 | 66 | @CallbackProp 67 | fun setItemClickListener(listener: OnClickListener?) { 68 | clCoinInfo.setOnClickListener(listener) 69 | } 70 | 71 | @CallbackProp 72 | fun setOnWatchedChecked(listener: OnSearchItemClickListener?) { 73 | cbWatched.setOnClickListener { 74 | listener?.onItemWatchedClicked(cbWatched.isChecked) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/CoinTickerView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import androidx.constraintlayout.widget.ConstraintLayout 9 | import coil.clear 10 | import coil.load 11 | import coil.transform.CircleCropTransformation 12 | import com.airbnb.epoxy.* 13 | import com.binarybricks.coinbit.R 14 | import com.binarybricks.coinbit.network.BASE_CRYPTOCOMPARE_IMAGE_URL 15 | import com.binarybricks.coinbit.network.models.CryptoTicker 16 | 17 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 18 | class CoinTickerView @JvmOverloads constructor( 19 | context: Context, 20 | attributeSet: AttributeSet? = null, 21 | defStyle: Int = 0, 22 | ) : ConstraintLayout(context, attributeSet, defStyle) { 23 | 24 | private val ivExchange: ImageView 25 | private val tvFromCoin: TextView 26 | private val tvToPrice: TextView 27 | private val tvExchange: TextView 28 | private val tvPrice: TextView 29 | private val tvVolume: TextView 30 | private val clMarket: View 31 | 32 | private val cropCircleTransformation by lazy { 33 | CircleCropTransformation() 34 | } 35 | 36 | init { 37 | View.inflate(context, R.layout.ticker_item, this) 38 | ivExchange = findViewById(R.id.ivExchange) 39 | tvFromCoin = findViewById(R.id.tvFromCoin) 40 | tvToPrice = findViewById(R.id.tvToPrice) 41 | tvExchange = findViewById(R.id.tvExchange) 42 | tvPrice = findViewById(R.id.tvPrice) 43 | tvVolume = findViewById(R.id.tvVolume) 44 | clMarket = findViewById(R.id.clMarket) 45 | } 46 | 47 | @ModelProp 48 | fun setTicker(ticker: CryptoTicker) { 49 | tvFromCoin.text = ticker.base 50 | tvToPrice.text = ticker.target 51 | tvExchange.text = ticker.marketName 52 | if (ticker.imageUrl.isNotEmpty()) { 53 | ivExchange.load(BASE_CRYPTOCOMPARE_IMAGE_URL + ticker.imageUrl) { 54 | crossfade(true) 55 | error(R.mipmap.ic_launcher_round) 56 | transformations(cropCircleTransformation) 57 | } 58 | } else { 59 | ivExchange.load(R.mipmap.ic_launcher_round) 60 | } 61 | } 62 | 63 | @TextProp 64 | fun setTickerPrice(price: CharSequence) { 65 | tvPrice.text = price 66 | } 67 | 68 | @TextProp 69 | fun setTickerVolume(volume: CharSequence) { 70 | tvVolume.text = volume 71 | } 72 | 73 | @OnViewRecycled 74 | fun viewRecycled() { 75 | ivExchange.clear() 76 | } 77 | 78 | @CallbackProp 79 | fun setItemClickListener(listener: OnClickListener?) { 80 | clMarket.setOnClickListener(listener) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/EmptyCoinItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.Button 7 | import android.widget.TextView 8 | import androidx.constraintlayout.widget.ConstraintLayout 9 | import com.airbnb.epoxy.CallbackProp 10 | import com.airbnb.epoxy.ModelProp 11 | import com.airbnb.epoxy.ModelView 12 | import com.binarybricks.coinbit.R 13 | import com.binarybricks.coinbit.featurecomponents.ModuleItem 14 | 15 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 16 | class EmptyCoinItemView @JvmOverloads constructor( 17 | context: Context, 18 | attributeSet: AttributeSet? = null, 19 | defStyle: Int = 0, 20 | ) : ConstraintLayout(context, attributeSet, defStyle) { 21 | 22 | private val tvEmptyMessage: TextView 23 | private val btnAddTransaction: Button 24 | 25 | init { 26 | View.inflate(context, R.layout.dashboard_empty_card_module, this) 27 | tvEmptyMessage = findViewById(R.id.tvEmptyMessage) 28 | btnAddTransaction = findViewById(R.id.btnAddTransaction) 29 | } 30 | 31 | @ModelProp 32 | fun setEmptyCardData(dashboardEmptyCoinModuleData: DashboardEmptyCoinModuleData) { 33 | tvEmptyMessage.text = dashboardEmptyCoinModuleData.emptySpaceText 34 | 35 | if (dashboardEmptyCoinModuleData.ctaButtonText.isNotEmpty()) { 36 | btnAddTransaction.text = dashboardEmptyCoinModuleData.ctaButtonText 37 | } 38 | } 39 | 40 | @CallbackProp 41 | fun setItemClickListener(listener: OnClickListener?) { 42 | btnAddTransaction.setOnClickListener(listener) 43 | } 44 | 45 | data class DashboardEmptyCoinModuleData(val emptySpaceText: String, val ctaButtonText: String = "") : ModuleItem 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/ExchangePairItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.TextView 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | import com.airbnb.epoxy.CallbackProp 9 | import com.airbnb.epoxy.ModelView 10 | import com.airbnb.epoxy.TextProp 11 | import com.binarybricks.coinbit.R 12 | 13 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 14 | class ExchangePairItemView @JvmOverloads constructor( 15 | context: Context, 16 | attributeSet: AttributeSet? = null, 17 | defStyle: Int = 0, 18 | ) : ConstraintLayout(context, attributeSet, defStyle) { 19 | 20 | private val tvSearchItemName: TextView 21 | 22 | init { 23 | View.inflate(context, R.layout.exchange_pair_search_item, this) 24 | tvSearchItemName = findViewById(R.id.tvArticleTitle) 25 | } 26 | 27 | @TextProp 28 | fun setExchangeName(exchnageName: CharSequence) { 29 | tvSearchItemName.text = exchnageName 30 | } 31 | 32 | @CallbackProp 33 | fun setItemClickListener(listener: OnClickListener?) { 34 | setOnClickListener(listener) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/ExpandedNewsItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import androidx.constraintlayout.widget.ConstraintLayout 9 | import coil.load 10 | import coil.transform.RoundedCornersTransformation 11 | import com.airbnb.epoxy.ModelProp 12 | import com.airbnb.epoxy.ModelView 13 | import com.binarybricks.coinbit.R 14 | import com.binarybricks.coinbit.featurecomponents.ModuleItem 15 | import com.binarybricks.coinbit.network.models.CryptoCompareNews 16 | import com.binarybricks.coinbit.utils.Formaters 17 | import com.binarybricks.coinbit.utils.openCustomTab 18 | import com.binarybricks.coinbit.utils.resourcemanager.AndroidResourceManager 19 | import com.binarybricks.coinbit.utils.resourcemanager.AndroidResourceManagerImpl 20 | 21 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 22 | class ExpandedNewsItemView @JvmOverloads constructor( 23 | context: Context, 24 | attributeSet: AttributeSet? = null, 25 | defStyle: Int = 0, 26 | ) : ConstraintLayout(context, attributeSet, defStyle) { 27 | 28 | private val tvSource: TextView 29 | private val tvHeadlines: TextView 30 | private val tvTimePeriod: TextView 31 | private val ivNewsCover: ImageView 32 | private val clNewsArticleContainer: View 33 | 34 | private val androidResourceManager: AndroidResourceManager by lazy { 35 | AndroidResourceManagerImpl(context) 36 | } 37 | 38 | init { 39 | View.inflate(context, R.layout.discovery_news_module, this) 40 | tvSource = findViewById(R.id.tvSource) 41 | tvHeadlines = findViewById(R.id.tvHeadlines) 42 | tvTimePeriod = findViewById(R.id.tvTimePeriod) 43 | ivNewsCover = findViewById(R.id.ivNewsCover) 44 | clNewsArticleContainer = findViewById(R.id.clNewsArticleContainer) 45 | } 46 | 47 | @ModelProp 48 | fun setNews(discoveryNewsModuleData: DiscoveryNewsModuleData) { 49 | tvSource.text = discoveryNewsModuleData.coinNews.source 50 | tvHeadlines.text = discoveryNewsModuleData.coinNews.title 51 | if (discoveryNewsModuleData.coinNews.published_on != null) { 52 | tvTimePeriod.text = Formaters(androidResourceManager).formatTransactionDate(discoveryNewsModuleData.coinNews.published_on) 53 | } 54 | 55 | ivNewsCover.load(discoveryNewsModuleData.coinNews.imageurl) { 56 | crossfade(true) 57 | transformations(RoundedCornersTransformation(15f)) 58 | } 59 | 60 | clNewsArticleContainer.setOnClickListener { 61 | if (discoveryNewsModuleData.coinNews.url != null) { 62 | openCustomTab(discoveryNewsModuleData.coinNews.url, context) 63 | } 64 | } 65 | } 66 | 67 | data class DiscoveryNewsModuleData(val coinNews: CryptoCompareNews) : ModuleItem 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/GenericFooterItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.TextView 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | import com.airbnb.epoxy.ModelProp 9 | import com.airbnb.epoxy.ModelView 10 | import com.binarybricks.coinbit.R 11 | import com.binarybricks.coinbit.featurecomponents.ModuleItem 12 | import com.binarybricks.coinbit.utils.openCustomTab 13 | import kotlinx.android.synthetic.main.generic_footer_module.view.* 14 | 15 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 16 | class GenericFooterItemView @JvmOverloads constructor( 17 | context: Context, 18 | attributeSet: AttributeSet? = null, 19 | defStyle: Int = 0, 20 | ) : ConstraintLayout(context, attributeSet, defStyle) { 21 | 22 | private val tvFooter: TextView 23 | 24 | init { 25 | View.inflate(context, R.layout.generic_footer_module, this) 26 | tvFooter = findViewById(R.id.tvFooter) 27 | } 28 | 29 | @ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode]) 30 | fun setFooterContent(footerModuleData: FooterModuleData) { 31 | tvFooter.text = footerModuleData.footerText 32 | 33 | if (footerModuleData.footerUrlLink.isNotEmpty()) { 34 | clFooter.setOnClickListener { 35 | openCustomTab(footerModuleData.footerUrlLink, context) 36 | } 37 | } else { 38 | tvFooter.visibility = View.GONE 39 | } 40 | } 41 | 42 | data class FooterModuleData(val footerText: String = "", val footerUrlLink: String = "") : ModuleItem 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/LabelItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.TextView 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | import com.airbnb.epoxy.ModelView 9 | import com.airbnb.epoxy.TextProp 10 | import com.binarybricks.coinbit.R 11 | import com.binarybricks.coinbit.featurecomponents.ModuleItem 12 | 13 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 14 | class LabelItemView @JvmOverloads constructor( 15 | context: Context, 16 | attributeSet: AttributeSet? = null, 17 | defStyle: Int = 0, 18 | ) : ConstraintLayout(context, attributeSet, defStyle) { 19 | 20 | private val tvLabel: TextView 21 | 22 | init { 23 | View.inflate(context, R.layout.coin_label_module, this) 24 | tvLabel = findViewById(R.id.tvLabel) 25 | } 26 | 27 | @TextProp 28 | fun setLabel(label: CharSequence) { 29 | tvLabel.text = label 30 | } 31 | 32 | data class LabelModuleData(val coinLabel: String) : ModuleItem 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/NewsItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.TextView 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | import com.airbnb.epoxy.CallbackProp 9 | import com.airbnb.epoxy.ModelView 10 | import com.airbnb.epoxy.TextProp 11 | import com.binarybricks.coinbit.R 12 | 13 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 14 | class NewsItemView @JvmOverloads constructor( 15 | context: Context, 16 | attributeSet: AttributeSet? = null, 17 | defStyle: Int = 0, 18 | ) : ConstraintLayout(context, attributeSet, defStyle) { 19 | 20 | private val title: TextView 21 | private val date: TextView 22 | private val clArticle: View 23 | 24 | init { 25 | View.inflate(context, R.layout.news_item, this) 26 | title = findViewById(R.id.tvArticleTitle) 27 | date = findViewById(R.id.tvArticleTime) 28 | clArticle = findViewById(R.id.clArticle) 29 | } 30 | 31 | @TextProp 32 | fun setTitle(newsTitle: CharSequence) { 33 | title.text = newsTitle 34 | } 35 | 36 | @TextProp 37 | fun setNewsDate(formattedDate: CharSequence) { 38 | date.text = formattedDate 39 | } 40 | 41 | @CallbackProp 42 | fun setItemClickListener(listener: OnClickListener?) { 43 | clArticle.setOnClickListener(listener) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/epoxymodels/ShortNewsItemView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.epoxymodels 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.TextView 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | import androidx.core.widget.ContentLoadingProgressBar 9 | import com.airbnb.epoxy.CallbackProp 10 | import com.airbnb.epoxy.ModelView 11 | import com.airbnb.epoxy.TextProp 12 | import com.binarybricks.coinbit.R 13 | import com.binarybricks.coinbit.featurecomponents.ModuleItem 14 | import com.binarybricks.coinbit.network.models.CryptoCompareNews 15 | 16 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 17 | class ShortNewsItemView @JvmOverloads constructor( 18 | context: Context, 19 | attributeSet: AttributeSet? = null, 20 | defStyle: Int = 0, 21 | ) : ConstraintLayout(context, attributeSet, defStyle) { 22 | 23 | private val pbLoading: ContentLoadingProgressBar 24 | private val tvNewsTitle: TextView 25 | private val clNewsArticleContainer: View 26 | 27 | init { 28 | View.inflate(context, R.layout.dashboard_news_module, this) 29 | pbLoading = findViewById(R.id.pbLoading) 30 | tvNewsTitle = findViewById(R.id.tvNewsTitle) 31 | clNewsArticleContainer = findViewById(R.id.clNewsArticleContainer) 32 | } 33 | 34 | @TextProp 35 | fun setNewsDate(news: CharSequence) { 36 | pbLoading.visibility = View.GONE 37 | tvNewsTitle.text = news 38 | } 39 | 40 | @CallbackProp 41 | fun setItemClickListener(listener: OnClickListener?) { 42 | clNewsArticleContainer.setOnClickListener(listener) 43 | } 44 | 45 | data class ShortNewsModuleData(val news: CryptoCompareNews) : ModuleItem 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/featurecomponents/ModuleItem.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.featurecomponents 2 | 3 | /** 4 | * Created by Pranay Airan 5 | */ 6 | interface ModuleItem 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/featurecomponents/cointickermodule/CoinTickerContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.features.BaseView 2 | import com.binarybricks.coinbit.network.models.CryptoTicker 3 | 4 | /** 5 | * Created by Pranay Airan 6 | */ 7 | 8 | interface CoinTickerContract { 9 | 10 | interface View : BaseView { 11 | fun showOrHideLoadingIndicatorForTicker(showLoading: Boolean = true) 12 | fun onPriceTickersLoaded(tickerData: List) 13 | } 14 | 15 | interface Presenter { 16 | fun getCryptoTickers(coinName: String) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/featurecomponents/cointickermodule/CoinTickerPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.featurecomponents.cointickermodule 2 | 3 | import CoinTickerContract 4 | import com.binarybricks.coinbit.R 5 | import com.binarybricks.coinbit.features.BasePresenter 6 | import com.binarybricks.coinbit.utils.resourcemanager.AndroidResourceManager 7 | import kotlinx.coroutines.launch 8 | import timber.log.Timber 9 | 10 | /** 11 | * Created by Pranay Airan 12 | */ 13 | 14 | class CoinTickerPresenter( 15 | private val coinTickerRepository: CoinTickerRepository, 16 | private val androidResourceManager: AndroidResourceManager 17 | ) : BasePresenter(), CoinTickerContract.Presenter { 18 | 19 | /** 20 | * Load the crypto ticker from the crypto panic api 21 | */ 22 | override fun getCryptoTickers(coinName: String) { 23 | 24 | var updatedCoinName = coinName 25 | 26 | if (coinName.equals("XRP", true)) { 27 | updatedCoinName = "ripple" 28 | } 29 | 30 | currentView?.showOrHideLoadingIndicatorForTicker(true) 31 | 32 | launch { 33 | try { 34 | val cryptoTickers = coinTickerRepository.getCryptoTickers(updatedCoinName) 35 | if (cryptoTickers != null) { 36 | currentView?.onPriceTickersLoaded(cryptoTickers) 37 | } 38 | } catch (ex: Exception) { 39 | Timber.e(ex.localizedMessage) 40 | currentView?.onNetworkError(androidResourceManager.getString(R.string.error_ticker)) 41 | } finally { 42 | currentView?.showOrHideLoadingIndicatorForTicker(false) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/featurecomponents/cointickermodule/CoinTickerRepository.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.featurecomponents.cointickermodule 2 | 3 | import com.binarybricks.coinbit.data.CoinBitCache 4 | import com.binarybricks.coinbit.data.database.CoinBitDatabase 5 | import com.binarybricks.coinbit.data.database.entities.Exchange 6 | import com.binarybricks.coinbit.network.api.api 7 | import com.binarybricks.coinbit.network.models.CryptoTicker 8 | import com.binarybricks.coinbit.utils.getCoinTickerFromJson 9 | 10 | /** 11 | * Created by Pranay Airan 12 | * Repository that interact with coin gecko api to get ticker info. 13 | */ 14 | 15 | class CoinTickerRepository( 16 | private val coinBitDatabase: CoinBitDatabase? 17 | ) { 18 | 19 | /** 20 | * Get the ticker info from coin gecko 21 | */ 22 | suspend fun getCryptoTickers(coinName: String): List? { 23 | 24 | return if (CoinBitCache.ticker.containsKey(coinName)) { 25 | CoinBitCache.ticker[coinName] 26 | } else { 27 | val exchangeList = loadExchangeList() 28 | val coinTickerFromJson = getCoinTickerFromJson(api.getCoinTicker(coinName), exchangeList) 29 | if (coinTickerFromJson.isNotEmpty()) { 30 | CoinBitCache.ticker[coinName] = coinTickerFromJson 31 | coinTickerFromJson 32 | } else { 33 | null 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * Get list of all exchanges, this is needed for logo 40 | */ 41 | private suspend fun loadExchangeList(): List? { 42 | coinBitDatabase?.let { 43 | return it.exchangeDao().getAllExchanges() 44 | } 45 | return null 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/featurecomponents/cryptonewsmodule/CryptoNewsContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.features.BaseView 2 | import com.binarybricks.coinbit.network.models.CryptoPanicNews 3 | 4 | /** 5 | * Created by Pragya Agrawal 6 | */ 7 | 8 | interface CryptoNewsContract { 9 | 10 | interface View : BaseView { 11 | fun showOrHideLoadingIndicator(showLoading: Boolean = true) 12 | fun onNewsLoaded(cryptoPanicNews: CryptoPanicNews) 13 | } 14 | 15 | interface Presenter { 16 | fun getCryptoNews(coinSymbol: String) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/featurecomponents/cryptonewsmodule/CryptoNewsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.featurecomponents.cryptonewsmodule 2 | 3 | import CryptoNewsContract 4 | import com.binarybricks.coinbit.features.BasePresenter 5 | import kotlinx.coroutines.launch 6 | import timber.log.Timber 7 | 8 | /** 9 | * Created by Pragya Agrawal 10 | */ 11 | 12 | class CryptoNewsPresenter(private val cryptoNewsRepository: CryptoNewsRepository) : 13 | BasePresenter(), CryptoNewsContract.Presenter { 14 | 15 | /** 16 | * Load the crypto news from the crypto panic api 17 | */ 18 | override fun getCryptoNews(coinSymbol: String) { 19 | 20 | currentView?.showOrHideLoadingIndicator(true) 21 | 22 | launch { 23 | try { 24 | val cryptoPanicNews = cryptoNewsRepository.getCryptoPanicNews(coinSymbol) 25 | currentView?.onNewsLoaded(cryptoPanicNews) 26 | } catch (ex: Exception) { 27 | Timber.e(ex.localizedMessage) 28 | } finally { 29 | currentView?.showOrHideLoadingIndicator(false) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/featurecomponents/cryptonewsmodule/CryptoNewsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.featurecomponents.cryptonewsmodule 2 | 3 | import com.binarybricks.coinbit.data.CoinBitCache 4 | import com.binarybricks.coinbit.network.api.API 5 | import com.binarybricks.coinbit.network.api.cryptoCompareRetrofit 6 | import com.binarybricks.coinbit.network.models.CryptoPanicNews 7 | 8 | /** 9 | * Created by Pragya Agrawal 10 | * Repository that interact with crypto api to get news. 11 | */ 12 | 13 | class CryptoNewsRepository { 14 | 15 | /** 16 | * Get the top news for specific coin from cryptopanic 17 | */ 18 | suspend fun getCryptoPanicNews(coinSymbol: String): CryptoPanicNews { 19 | 20 | return if (CoinBitCache.newsMap.containsKey(coinSymbol)) { 21 | CoinBitCache.newsMap[coinSymbol]!! 22 | } else { 23 | cryptoCompareRetrofit.create(API::class.java) 24 | .getCryptoNewsForCurrency(coinSymbol, "important", true) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/featurecomponents/historicalchartmodule/ChartRepository.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.featurecomponents.historicalchartmodule 2 | 3 | import com.binarybricks.coinbit.network.* 4 | import com.binarybricks.coinbit.network.api.api 5 | import com.binarybricks.coinbit.network.models.CryptoCompareHistoricalResponse 6 | import timber.log.Timber 7 | 8 | /** 9 | Created by Pranay Airan 10 | * Repository that interact with crypto api to get charts. 11 | */ 12 | 13 | class ChartRepository { 14 | 15 | /** 16 | * Get the historical data for specific crypto currencies. [period] specifies what time period you 17 | * want data from. [fromCurrencySymbol] specifies what currencies data you want for example bitcoin.[toCurrencySymbol] 18 | * is which currency you want data in for like USD 19 | */ 20 | suspend fun getCryptoHistoricalData(period: String, fromCurrencySymbol: String?, toCurrencySymbol: String?): Pair, CryptoCompareHistoricalResponse.Data?> { 21 | 22 | val histoPeriod: String 23 | var limit = 30 24 | var aggregate = 1 25 | when (period) { 26 | HOUR -> { 27 | histoPeriod = HISTO_MINUTE 28 | limit = 60 29 | aggregate = 12 // this pulls for 12 hour 30 | } 31 | HOURS24 -> { 32 | histoPeriod = HISTO_HOUR 33 | limit = 24 // 1 day 34 | } 35 | WEEK -> { 36 | histoPeriod = HISTO_HOUR 37 | aggregate = 6 // 1 week limit is 128 hours default that is 38 | } 39 | MONTH -> { 40 | histoPeriod = HISTO_DAY 41 | limit = 30 // 30 days 42 | } 43 | MONTH3 -> { 44 | histoPeriod = HISTO_DAY 45 | limit = 90 // 90 days 46 | } 47 | YEAR -> { 48 | histoPeriod = HISTO_DAY 49 | aggregate = 13 // default limit is 30 so 30*12 365 days 50 | } 51 | ALL -> { 52 | histoPeriod = HISTO_DAY 53 | aggregate = 30 54 | limit = 2000 55 | } 56 | else -> { 57 | histoPeriod = HISTO_HOUR 58 | limit = 24 // 1 day 59 | } 60 | } 61 | 62 | val historicalData = api.getCryptoHistoricalData(histoPeriod, fromCurrencySymbol, toCurrencySymbol, limit, aggregate) 63 | Timber.d("Size of response %s", historicalData.data.size) 64 | val maxClosingValueFromHistoricalData = historicalData.data.maxBy { it.close.toFloat() } 65 | return Pair(historicalData.data, maxClosingValueFromHistoricalData) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/featurecomponents/historicalchartmodule/HistoricalChartAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.featurecomponents.historicalchartmodule 2 | 3 | import com.binarybricks.coinbit.network.models.CryptoCompareHistoricalResponse 4 | import com.robinhood.spark.SparkAdapter 5 | 6 | /** 7 | Created by Pranay Airan 1/13/18. 8 | */ 9 | 10 | class HistoricalChartAdapter(private val historicalData: List, private val baseLineValue: String?) : SparkAdapter() { 11 | 12 | override fun getY(index: Int): Float { 13 | return historicalData[index].close.toFloat() 14 | } 15 | 16 | override fun getItem(index: Int): CryptoCompareHistoricalResponse.Data { 17 | return historicalData[index] 18 | } 19 | 20 | override fun getCount(): Int { 21 | return historicalData.size 22 | } 23 | 24 | override fun hasBaseLine(): Boolean { 25 | return true 26 | } 27 | 28 | override fun getBaseLine(): Float { 29 | return baseLineValue?.toFloat() ?: super.getBaseLine() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/BasePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleObserver 5 | import androidx.lifecycle.OnLifecycleEvent 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.SupervisorJob 9 | import kotlin.coroutines.CoroutineContext 10 | 11 | /** 12 | * A base class for all our presenters. It provides the basics nuts & bolts of attaching/detaching a presenter to/from a 13 | * view, as well as the strings resolution class. 14 | */ 15 | 16 | open class BasePresenter(private val uiContext: CoroutineContext = Dispatchers.Main.immediate) : LifecycleObserver, CoroutineScope { 17 | 18 | protected var currentView: V? = null 19 | private val job = SupervisorJob() 20 | 21 | /** 22 | * Check if the view is attached. 23 | * This checking is only necessary when returning from an asynchronous call 24 | * 25 | * @return true if a view is attached to this presenter. false otherwise. 26 | */ 27 | protected val isViewAttached: Boolean get() = currentView != null 28 | 29 | fun attachView(view: V) { 30 | 31 | if (currentView != null) { 32 | currentView = null 33 | } 34 | currentView = view 35 | } 36 | 37 | fun detachView() { 38 | job.cancel() 39 | currentView = null 40 | } 41 | 42 | // cleanup 43 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 44 | fun cleanYourSelf() { 45 | detachView() 46 | } 47 | 48 | override val coroutineContext: CoroutineContext 49 | get() = uiContext + job 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/BaseView.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features 2 | 3 | /** 4 | * A base view interface used for all views, 5 | * and to signal network errors. 6 | */ 7 | interface BaseView { 8 | 9 | /** 10 | * Callback to signal a network error 11 | **/ 12 | fun onNetworkError(errorMessage: String) 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coin/CoinContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.data.database.entities.CoinTransaction 2 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 3 | import com.binarybricks.coinbit.features.BaseView 4 | import com.binarybricks.coinbit.network.models.CoinPrice 5 | import com.binarybricks.coinbit.network.models.CryptoCompareHistoricalResponse 6 | 7 | /** 8 | Created by Pranay Airan 9 | */ 10 | 11 | interface CoinContract { 12 | 13 | interface View : BaseView { 14 | fun onCoinPriceLoaded(coinPrice: CoinPrice?, watchedCoin: WatchedCoin) 15 | fun onRecentTransactionLoaded(coinTransactionList: List) 16 | fun onCoinWatchedStatusUpdated(watched: Boolean, coinSymbol: String) 17 | fun onHistoricalDataLoaded(period: String, historicalDataPair: Pair, CryptoCompareHistoricalResponse.Data?>) 18 | } 19 | 20 | interface Presenter { 21 | fun loadCurrentCoinPrice(watchedCoin: WatchedCoin, toCurrency: String) 22 | fun loadRecentTransaction(symbol: String) 23 | fun updateCoinWatchedStatus(watched: Boolean, coinID: String, coinSymbol: String) 24 | fun loadHistoricalData(period: String, fromCurrency: String, toCurrency: String) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coin/CoinPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.coin 2 | 3 | import CoinContract 4 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 5 | import com.binarybricks.coinbit.featurecomponents.historicalchartmodule.ChartRepository 6 | import com.binarybricks.coinbit.features.BasePresenter 7 | import com.binarybricks.coinbit.features.CryptoCompareRepository 8 | import kotlinx.coroutines.flow.catch 9 | import kotlinx.coroutines.flow.collect 10 | import kotlinx.coroutines.launch 11 | import timber.log.Timber 12 | 13 | /** 14 | Created by Pranay Airan 15 | */ 16 | 17 | class CoinPresenter( 18 | private val coinRepo: CryptoCompareRepository, 19 | private val chartRepo: ChartRepository 20 | ) : BasePresenter(), CoinContract.Presenter { 21 | 22 | /** 23 | * Get the current price of a coinSymbol say btc or eth 24 | */ 25 | override fun loadCurrentCoinPrice(watchedCoin: WatchedCoin, toCurrency: String) { 26 | launch { 27 | try { 28 | currentView?.onCoinPriceLoaded(coinRepo.getCoinPriceFull(watchedCoin.coin.symbol, toCurrency), watchedCoin) 29 | } catch (ex: Exception) { 30 | Timber.e(ex.localizedMessage) 31 | } 32 | } 33 | } 34 | 35 | override fun loadRecentTransaction(symbol: String) { 36 | launch { 37 | coinRepo.getRecentTransaction(symbol) 38 | ?.catch { 39 | Timber.e(it.localizedMessage) 40 | } 41 | ?.collect { coinTransactionsList -> 42 | coinTransactionsList?.let { 43 | currentView?.onRecentTransactionLoaded(it) 44 | } 45 | } 46 | } 47 | } 48 | 49 | override fun updateCoinWatchedStatus(watched: Boolean, coinID: String, coinSymbol: String) { 50 | 51 | launch { 52 | try { 53 | coinRepo.updateCoinWatchedStatus(watched, coinID) 54 | Timber.d("Coin status updated") 55 | currentView?.onCoinWatchedStatusUpdated(watched, coinSymbol) 56 | } catch (ex: Exception) { 57 | Timber.e(ex.localizedMessage) 58 | currentView?.onNetworkError(ex.localizedMessage ?: "Error") 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * Load historical data for the coin to show the chart. 65 | */ 66 | override fun loadHistoricalData(period: String, fromCurrency: String, toCurrency: String) { 67 | launch { 68 | try { 69 | currentView?.onHistoricalDataLoaded(period, chartRepo.getCryptoHistoricalData(period, fromCurrency, toCurrency)) 70 | } catch (ex: Exception) { 71 | Timber.e(ex.localizedMessage) 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coindetails/CoinDetailPagerPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.coindetails 2 | 3 | import CoinDetailsPagerContract 4 | import com.binarybricks.coinbit.features.BasePresenter 5 | import kotlinx.coroutines.launch 6 | import timber.log.Timber 7 | 8 | /** 9 | Created by Pranay Airan 10 | */ 11 | 12 | class CoinDetailPagerPresenter(private val coinDetailsPagerRepository: CoinDetailsPagerRepository) : 13 | BasePresenter(), CoinDetailsPagerContract.Presenter { 14 | override fun loadWatchedCoins() { 15 | launch { 16 | try { 17 | currentView?.onWatchedCoinsLoaded(coinDetailsPagerRepository.loadWatchedCoins()) 18 | } catch (ex: Exception) { 19 | Timber.e(ex.localizedMessage) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coindetails/CoinDetailPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.coindetails 2 | 3 | import CoinDetailsContract 4 | import com.binarybricks.coinbit.features.BasePresenter 5 | import com.binarybricks.coinbit.features.CryptoCompareRepository 6 | import kotlinx.coroutines.launch 7 | import timber.log.Timber 8 | 9 | /** 10 | Created by Pranay Airan 11 | */ 12 | 13 | class CoinDetailPresenter( 14 | private val coinRepo: CryptoCompareRepository 15 | ) : BasePresenter(), 16 | CoinDetailsContract.Presenter { 17 | 18 | override fun getWatchedCoinFromSymbol(symbol: String) { 19 | 20 | currentView?.showOrHideLoadingIndicator(true) 21 | 22 | launch { 23 | try { 24 | val singleCoin = coinRepo.getSingleCoin(symbol) 25 | Timber.d("watched coin loaded") 26 | currentView?.showOrHideLoadingIndicator(false) 27 | if (singleCoin != null) { 28 | currentView?.onWatchedCoinLoaded(singleCoin.first()) 29 | } else { 30 | currentView?.onWatchedCoinLoaded(null) 31 | } 32 | } catch (ex: Exception) { 33 | Timber.e(ex.localizedMessage) 34 | currentView?.onNetworkError(ex.localizedMessage) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coindetails/CoinDetailsContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 2 | import com.binarybricks.coinbit.features.BaseView 3 | 4 | /** 5 | Created by Pranay Airan 6 | */ 7 | 8 | interface CoinDetailsContract { 9 | 10 | interface View : BaseView { 11 | fun showOrHideLoadingIndicator(showLoading: Boolean = true) 12 | fun onWatchedCoinLoaded(coin: WatchedCoin?) 13 | } 14 | 15 | interface Presenter { 16 | fun getWatchedCoinFromSymbol(symbol: String) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coindetails/CoinDetailsPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.coindetails 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.fragment.app.FragmentStatePagerAdapter 6 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 7 | import com.binarybricks.coinbit.features.coin.CoinFragment 8 | 9 | /** 10 | * Created by pranay airan on 2/11/18. 11 | */ 12 | 13 | class CoinDetailsPagerAdapter(private val watchedCoinList: List?, fm: FragmentManager) : FragmentStatePagerAdapter(fm, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { 14 | override fun getItem(position: Int): Fragment { 15 | val coinDetailsFragment = CoinFragment() 16 | if (watchedCoinList != null) { 17 | coinDetailsFragment.arguments = CoinFragment.getArgumentBundle(watchedCoinList[position]) 18 | return coinDetailsFragment 19 | } 20 | return coinDetailsFragment 21 | } 22 | 23 | override fun getCount(): Int { 24 | return watchedCoinList?.size ?: 0 25 | } 26 | 27 | override fun getPageTitle(position: Int): CharSequence? { 28 | return watchedCoinList?.get(position)?.coin?.symbol ?: super.getPageTitle(position) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coindetails/CoinDetailsPagerContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 2 | import com.binarybricks.coinbit.features.BaseView 3 | 4 | /** 5 | Created by Pranay Airan 6 | */ 7 | 8 | interface CoinDetailsPagerContract { 9 | 10 | interface View : BaseView { 11 | fun onWatchedCoinsLoaded(watchedCoinList: List?) 12 | } 13 | 14 | interface Presenter { 15 | fun loadWatchedCoins() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coindetails/CoinDetailsPagerRepository.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.coindetails 2 | 3 | import com.binarybricks.coinbit.data.database.CoinBitDatabase 4 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 5 | 6 | /** 7 | Created by Pranay Airan 8 | * Repository that interact with crypto api and database for getting data. 9 | */ 10 | 11 | class CoinDetailsPagerRepository( 12 | private val coinBitDatabase: CoinBitDatabase? 13 | ) { 14 | 15 | /** 16 | * Get list of all coins that is added in watch list 17 | */ 18 | suspend fun loadWatchedCoins(): List? { 19 | 20 | coinBitDatabase?.let { 21 | return it.watchedCoinDao().getAllWatchedCoinsOnetime() 22 | } 23 | return null 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coinsearch/CoinDiscoveryContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.features.BaseView 2 | import com.binarybricks.coinbit.network.models.CoinPair 3 | import com.binarybricks.coinbit.network.models.CoinPrice 4 | import com.binarybricks.coinbit.network.models.CryptoCompareNews 5 | 6 | /** 7 | Created by Pranay Airan 8 | */ 9 | 10 | interface CoinDiscoveryContract { 11 | 12 | interface View : BaseView { 13 | fun onTopCoinsByTotalVolumeLoaded(topCoins: List) 14 | fun onTopCoinListByPairVolumeLoaded(topPair: List) 15 | fun onCoinNewsLoaded(coinNews: List) 16 | } 17 | 18 | interface Presenter { 19 | fun getTopCoinListByMarketCap(toCurrencySymbol: String) 20 | fun getTopCoinListByPairVolume() 21 | fun getCryptoCurrencyNews() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coinsearch/CoinDiscoveryPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.coinsearch 2 | 3 | import CoinDiscoveryContract 4 | import com.binarybricks.coinbit.features.BasePresenter 5 | import com.binarybricks.coinbit.features.CryptoCompareRepository 6 | import kotlinx.coroutines.launch 7 | import timber.log.Timber 8 | 9 | /** 10 | Created by Pranay Airan 11 | */ 12 | 13 | class CoinDiscoveryPresenter( 14 | private val coinRepo: CryptoCompareRepository 15 | ) : BasePresenter(), 16 | CoinDiscoveryContract.Presenter { 17 | 18 | override fun getTopCoinListByMarketCap(toCurrencySymbol: String) { 19 | launch { 20 | try { 21 | currentView?.onTopCoinsByTotalVolumeLoaded(coinRepo.getTopCoinsByTotalVolume(toCurrencySymbol)) 22 | } catch (ex: Exception) { 23 | Timber.e(ex.localizedMessage) 24 | } 25 | } 26 | } 27 | 28 | override fun getTopCoinListByPairVolume() { 29 | 30 | launch { 31 | try { 32 | currentView?.onTopCoinListByPairVolumeLoaded(coinRepo.getTopPairsByTotalVolume("BTC")) 33 | Timber.d("Top coins by pair Loaded") 34 | } catch (ex: Exception) { 35 | Timber.e(ex.localizedMessage) 36 | } 37 | } 38 | } 39 | 40 | override fun getCryptoCurrencyNews() { 41 | launch { 42 | try { 43 | val topNewsFromCryptoCompare = coinRepo.getTopNewsFromCryptoCompare() 44 | currentView?.onCoinNewsLoaded(topNewsFromCryptoCompare) 45 | Timber.d("All news Loaded") 46 | } catch (ex: Exception) { 47 | Timber.e(ex.localizedMessage) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coinsearch/CoinSearchContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 2 | import com.binarybricks.coinbit.features.BaseView 3 | 4 | /** 5 | Created by Pranay Airan 6 | */ 7 | 8 | interface CoinSearchContract { 9 | 10 | interface View : BaseView { 11 | fun showOrHideLoadingIndicator(showLoading: Boolean = true) 12 | fun onCoinsLoaded(coinList: List) 13 | fun onCoinWatchedStatusUpdated(watched: Boolean, coinSymbol: String) 14 | } 15 | 16 | interface Presenter { 17 | fun loadAllCoins() 18 | fun updateCoinWatchedStatus(watched: Boolean, coinID: String, coinSymbol: String) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/coinsearch/CoinSearchPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.coinsearch 2 | 3 | import CoinSearchContract 4 | import com.binarybricks.coinbit.features.BasePresenter 5 | import com.binarybricks.coinbit.features.CryptoCompareRepository 6 | import kotlinx.coroutines.flow.catch 7 | import kotlinx.coroutines.flow.collect 8 | import kotlinx.coroutines.launch 9 | import timber.log.Timber 10 | 11 | /** 12 | Created by Pranay Airan 13 | */ 14 | 15 | class CoinSearchPresenter( 16 | private val coinRepo: CryptoCompareRepository 17 | ) : BasePresenter(), 18 | CoinSearchContract.Presenter { 19 | 20 | override fun loadAllCoins() { 21 | currentView?.showOrHideLoadingIndicator(true) 22 | 23 | launch { 24 | coinRepo.getAllCoins() 25 | ?.catch { 26 | Timber.e(it) 27 | currentView?.onNetworkError(it.localizedMessage) 28 | } 29 | ?.collect { 30 | Timber.d("All Coins Loaded") 31 | currentView?.showOrHideLoadingIndicator(false) 32 | currentView?.onCoinsLoaded(it) 33 | } 34 | } 35 | } 36 | 37 | override fun updateCoinWatchedStatus(watched: Boolean, coinID: String, coinSymbol: String) { 38 | launch { 39 | try { 40 | coinRepo.updateCoinWatchedStatus(watched, coinID) 41 | Timber.d("Coin status updated") 42 | currentView?.onCoinWatchedStatusUpdated(watched, coinSymbol) 43 | } catch (ex: Exception) { 44 | Timber.e(ex.localizedMessage) 45 | currentView?.onNetworkError(ex.localizedMessage ?: "Error") 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/dashboard/CoinDashboardContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.data.database.entities.CoinTransaction 2 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 3 | import com.binarybricks.coinbit.features.BaseView 4 | import com.binarybricks.coinbit.network.models.CoinPrice 5 | import com.binarybricks.coinbit.network.models.CryptoCompareNews 6 | 7 | /** 8 | Created by Pranay Airan 9 | */ 10 | 11 | interface CoinDashboardContract { 12 | 13 | interface View : BaseView { 14 | fun onWatchedCoinsAndTransactionsLoaded(watchedCoinList: List, coinTransactionList: List) 15 | fun onCoinPricesLoaded(coinPriceListMap: HashMap) 16 | fun onTopCoinsByTotalVolumeLoaded(topCoins: List) 17 | fun onCoinNewsLoaded(coinNews: List) 18 | } 19 | 20 | interface Presenter { 21 | fun loadWatchedCoinsAndTransactions() 22 | fun loadCoinsPrices(fromCurrencySymbol: String, toCurrencySymbol: String) 23 | fun getTopCoinsByTotalVolume24hours(toCurrencySymbol: String) 24 | fun getLatestNewsFromCryptoCompare() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/dashboard/CoinDashboardPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.dashboard 2 | 3 | import CoinDashboardContract 4 | import com.binarybricks.coinbit.data.CoinBitCache 5 | import com.binarybricks.coinbit.features.BasePresenter 6 | import com.binarybricks.coinbit.features.CryptoCompareRepository 7 | import com.binarybricks.coinbit.network.models.CoinPrice 8 | import kotlinx.coroutines.flow.catch 9 | import kotlinx.coroutines.flow.collect 10 | import kotlinx.coroutines.flow.zip 11 | import kotlinx.coroutines.launch 12 | import timber.log.Timber 13 | 14 | /** 15 | Created by Pranay Airan 16 | */ 17 | 18 | class CoinDashboardPresenter( 19 | private val dashboardRepository: DashboardRepository, 20 | private val coinRepo: CryptoCompareRepository 21 | ) : BasePresenter(), 22 | CoinDashboardContract.Presenter { 23 | 24 | override fun loadWatchedCoinsAndTransactions() { 25 | val watchedCoins = dashboardRepository.loadWatchedCoins() 26 | val transactions = dashboardRepository.loadTransactions() 27 | 28 | if (watchedCoins != null && transactions != null) { 29 | launch { 30 | watchedCoins.zip(transactions) { watchedCoinList, transactionList -> 31 | Pair(watchedCoinList, transactionList) 32 | }.catch { 33 | Timber.e(it.localizedMessage) 34 | }.collect { 35 | currentView?.onWatchedCoinsAndTransactionsLoaded(it.first, it.second) 36 | } 37 | } 38 | } 39 | } 40 | 41 | override fun loadCoinsPrices(fromCurrencySymbol: String, toCurrencySymbol: String) { 42 | launch { 43 | try { 44 | val coinPriceList = dashboardRepository.getCoinPriceFull(fromCurrencySymbol, toCurrencySymbol) 45 | val coinPriceMap: HashMap = hashMapOf() 46 | coinPriceList.forEach { coinPrice -> 47 | coinPrice.fromSymbol?.let { fromCurrencySymbol -> coinPriceMap[fromCurrencySymbol.toUpperCase()] = coinPrice } 48 | } 49 | if (coinPriceMap.isNotEmpty()) { 50 | CoinBitCache.coinPriceMap.putAll(coinPriceMap) 51 | } 52 | 53 | currentView?.onCoinPricesLoaded(coinPriceMap) 54 | } catch (ex: Exception) { 55 | Timber.e(ex.localizedMessage) 56 | } 57 | } 58 | } 59 | 60 | override fun getTopCoinsByTotalVolume24hours(toCurrencySymbol: String) { 61 | launch { 62 | try { 63 | val topCoinsByTotalVolume24hours = coinRepo.getTopCoinsByTotalVolume24hours(toCurrencySymbol) 64 | currentView?.onTopCoinsByTotalVolumeLoaded(topCoinsByTotalVolume24hours) 65 | Timber.d("All Exchange Loaded") 66 | } catch (ex: Exception) { 67 | Timber.e(ex.localizedMessage) 68 | } 69 | } 70 | } 71 | 72 | override fun getLatestNewsFromCryptoCompare() { 73 | launch { 74 | try { 75 | val topNewsFromCryptoCompare = coinRepo.getTopNewsFromCryptoCompare() 76 | currentView?.onCoinNewsLoaded(topNewsFromCryptoCompare) 77 | Timber.d("All news Loaded") 78 | } catch (ex: Exception) { 79 | Timber.e(ex.localizedMessage) 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/dashboard/DashboardRepository.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.dashboard 2 | 3 | import com.binarybricks.coinbit.data.database.CoinBitDatabase 4 | import com.binarybricks.coinbit.data.database.entities.CoinTransaction 5 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 6 | import com.binarybricks.coinbit.network.api.api 7 | import com.binarybricks.coinbit.network.models.CoinPrice 8 | import com.binarybricks.coinbit.utils.getCoinPricesFromJson 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.distinctUntilChanged 11 | 12 | /** 13 | Created by Pranay Airan 14 | * Repository that interact with crypto api and database for getting data. 15 | */ 16 | 17 | class DashboardRepository( 18 | private val coinBitDatabase: CoinBitDatabase? 19 | ) { 20 | 21 | /** 22 | * Get list of all coins that is added in watch list 23 | */ 24 | fun loadWatchedCoins(): Flow>? { 25 | coinBitDatabase?.let { 26 | return it.watchedCoinDao().getAllWatchedCoins().distinctUntilChanged() 27 | } 28 | return null 29 | } 30 | 31 | /** 32 | * Get list of all coin transactions 33 | */ 34 | fun loadTransactions(): Flow>? { 35 | 36 | coinBitDatabase?.let { 37 | return it.coinTransactionDao().getAllCoinTransaction().distinctUntilChanged() 38 | } 39 | return null 40 | } 41 | 42 | /** 43 | * Get the price of a coin from the API 44 | * want data from. [fromCurrencySymbol] specifies what currencies data you want for example bitcoin. 45 | * [toCurrencySymbol] is which currency you want data in for like USD 46 | */ 47 | suspend fun getCoinPriceFull(fromCurrencySymbol: String, toCurrencySymbol: String): ArrayList { 48 | return getCoinPricesFromJson(api.getPricesFull(fromCurrencySymbol, toCurrencySymbol)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/launch/LaunchContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.features.BaseView 2 | 3 | /** 4 | Created by Pranay Airan 2/3/18. 5 | */ 6 | 7 | interface LaunchContract { 8 | 9 | interface View : BaseView { 10 | fun onCoinsLoaded() 11 | fun onAllSupportedCoinsLoaded() 12 | } 13 | 14 | interface Presenter { 15 | fun loadAllCoins() 16 | fun getAllSupportedCoins(defaultCurrency: String) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/launch/LaunchPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.launch 2 | 3 | import LaunchContract 4 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 5 | import com.binarybricks.coinbit.features.BasePresenter 6 | import com.binarybricks.coinbit.features.CryptoCompareRepository 7 | import com.binarybricks.coinbit.features.getTop5CoinsToWatch 8 | import com.binarybricks.coinbit.network.models.CCCoin 9 | import com.binarybricks.coinbit.network.models.CoinInfo 10 | import com.binarybricks.coinbit.network.models.getCoinFromCCCoin 11 | import com.binarybricks.coinbit.utils.defaultExchange 12 | import kotlinx.coroutines.launch 13 | import timber.log.Timber 14 | 15 | /** 16 | Created by Pranay Airan 17 | */ 18 | 19 | class LaunchPresenter( 20 | private val coinRepo: CryptoCompareRepository 21 | ) : BasePresenter(), LaunchContract.Presenter { 22 | 23 | private var coinList: ArrayList? = null 24 | private var coinInfoMap: Map? = null 25 | 26 | override fun loadAllCoins() { 27 | launch { 28 | try { 29 | val allCoinsFromAPI = coinRepo.getAllCoinsFromAPI(coinList, coinInfoMap) 30 | coinList = allCoinsFromAPI.first 31 | coinInfoMap = allCoinsFromAPI.second 32 | currentView?.onCoinsLoaded() 33 | } catch (ex: Exception) { 34 | Timber.e(ex.localizedMessage) 35 | } 36 | } 37 | 38 | loadExchangeFromAPI() 39 | } 40 | 41 | private fun loadExchangeFromAPI() { 42 | launch { 43 | try { 44 | coinRepo.insertExchangeIntoList(coinRepo.getExchangeInfo()) 45 | } catch (ex: Exception) { 46 | Timber.e(ex.localizedMessage) 47 | } 48 | } 49 | } 50 | 51 | override fun getAllSupportedCoins(defaultCurrency: String) { 52 | launch { 53 | try { 54 | val allCoinsFromAPI = coinRepo.getAllCoinsFromAPI(coinList, coinInfoMap) 55 | val coinList: MutableList = mutableListOf() 56 | val ccCoinList = allCoinsFromAPI.first 57 | 58 | ccCoinList.forEach { ccCoin -> 59 | val coinInfo = allCoinsFromAPI.second[ccCoin.symbol.toLowerCase()] 60 | coinList.add(getCoinFromCCCoin(ccCoin, defaultExchange, defaultCurrency, coinInfo)) 61 | } 62 | 63 | coinRepo.insertCoinsInWatchList(coinList) 64 | 65 | val top5CoinsToWatch = getTop5CoinsToWatch() 66 | top5CoinsToWatch.forEach { coinId -> 67 | coinRepo.updateCoinWatchedStatus(true, coinId) 68 | } 69 | 70 | Timber.d("Loaded all the coins and inserted in DB") 71 | currentView?.onAllSupportedCoinsLoaded() 72 | } catch (ex: Exception) { 73 | Timber.e(ex.localizedMessage) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/settings/SettingsContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.features.BaseView 2 | 3 | /** 4 | Created by Pranay Airan 5 | */ 6 | 7 | interface SettingsContract { 8 | 9 | interface View : BaseView { 10 | fun onCoinListRefreshed() 11 | fun onExchangeListRefreshed() 12 | } 13 | 14 | interface Presenter { 15 | fun refreshCoinList(defaultCurrency: String) 16 | fun refreshExchangeList() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/settings/SettingsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.settings 2 | 3 | import SettingsContract 4 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 5 | import com.binarybricks.coinbit.features.BasePresenter 6 | import com.binarybricks.coinbit.features.CryptoCompareRepository 7 | import com.binarybricks.coinbit.network.models.getCoinFromCCCoin 8 | import com.binarybricks.coinbit.utils.defaultExchange 9 | import kotlinx.coroutines.launch 10 | import timber.log.Timber 11 | 12 | /** 13 | Created by Pranay Airan 14 | */ 15 | 16 | class SettingsPresenter( 17 | private val coinRepo: CryptoCompareRepository 18 | ) : BasePresenter(), SettingsContract.Presenter { 19 | 20 | override fun refreshCoinList(defaultCurrency: String) { 21 | launch { 22 | try { 23 | val allCoinsFromAPI = coinRepo.getAllCoinsFromAPI() 24 | val coinList: MutableList = mutableListOf() 25 | val ccCoinList = allCoinsFromAPI.first 26 | ccCoinList.forEach { ccCoin -> 27 | val coinInfo = allCoinsFromAPI.second[ccCoin.symbol.toLowerCase()] 28 | coinList.add(getCoinFromCCCoin(ccCoin, defaultExchange, defaultCurrency, coinInfo)) 29 | } 30 | Timber.d("Inserted all coins in db with size ${coinList.size}") 31 | currentView?.onCoinListRefreshed() 32 | } catch (ex: Exception) { 33 | Timber.e(ex.localizedMessage) 34 | currentView?.onNetworkError(ex.localizedMessage ?: "") 35 | } 36 | } 37 | } 38 | 39 | override fun refreshExchangeList() { 40 | launch { 41 | try { 42 | coinRepo.insertExchangeIntoList(coinRepo.getExchangeInfo()) 43 | Timber.d("all exchanges loaded and inserted into db") 44 | currentView?.onExchangeListRefreshed() 45 | } catch (ex: Exception) { 46 | Timber.e(ex.localizedMessage) 47 | currentView?.onNetworkError(ex.localizedMessage ?: "") 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/transaction/CoinTransactionContract.kt: -------------------------------------------------------------------------------- 1 | import com.binarybricks.coinbit.data.database.entities.CoinTransaction 2 | import com.binarybricks.coinbit.features.BaseView 3 | import com.binarybricks.coinbit.network.models.ExchangePair 4 | import java.math.BigDecimal 5 | import java.util.HashMap 6 | 7 | /** 8 | Created by Pranay Airan 2/3/18. 9 | */ 10 | 11 | interface CoinTransactionContract { 12 | 13 | interface View : BaseView { 14 | fun onAllSupportedExchangesLoaded(exchangeCoinMap: HashMap>) 15 | fun onCoinPriceLoaded(prices: MutableMap) 16 | fun onTransactionAdded() 17 | } 18 | 19 | interface Presenter { 20 | fun getAllSupportedExchanges() 21 | fun getPriceForPair(fromCoin: String, toCoin: String, exchange: String, timeStamp: String) 22 | fun addTransaction(transaction: CoinTransaction) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/features/transaction/CoinTransactionPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.features.transaction 2 | 3 | import CoinTransactionContract 4 | import com.binarybricks.coinbit.data.database.entities.CoinTransaction 5 | import com.binarybricks.coinbit.features.BasePresenter 6 | import com.binarybricks.coinbit.features.CryptoCompareRepository 7 | import kotlinx.coroutines.launch 8 | import timber.log.Timber 9 | 10 | /** 11 | Created by Pranay Airan 12 | */ 13 | 14 | class CoinTransactionPresenter( 15 | private val coinRepo: CryptoCompareRepository 16 | ) : BasePresenter(), CoinTransactionContract.Presenter { 17 | 18 | override fun getAllSupportedExchanges() { 19 | launch { 20 | try { 21 | currentView?.onAllSupportedExchangesLoaded(coinRepo.getAllSupportedExchanges()) 22 | Timber.d("All Exchange Loaded") 23 | } catch (ex: Exception) { 24 | Timber.e(ex.localizedMessage) 25 | } 26 | } 27 | } 28 | 29 | // to coins is , separated multiple coin list. 30 | override fun getPriceForPair(fromCoin: String, toCoin: String, exchange: String, timeStamp: String) { 31 | if (exchange.isNotEmpty()) { 32 | launch { 33 | try { 34 | currentView?.onCoinPriceLoaded(coinRepo.getCoinPriceForTimeStamp(fromCoin, toCoin, exchange, timeStamp)) 35 | } catch (ex: Exception) { 36 | Timber.e(ex.localizedMessage) 37 | } 38 | } 39 | } 40 | } 41 | 42 | override fun addTransaction(transaction: CoinTransaction) { 43 | launch { 44 | try { 45 | coinRepo.insertTransaction(transaction) 46 | Timber.d("Coin Transaction Added") 47 | currentView?.onTransactionAdded() 48 | } catch (ex: Exception) { 49 | Timber.e(ex.localizedMessage) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/NetworkConstants.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("ConstantsKt") 2 | 3 | package com.binarybricks.coinbit.network 4 | 5 | const val BASE_CRYPTOCOMPARE_URL = "https://min-api.cryptocompare.com/data/" 6 | const val BASE_CRYPTOCOMPARE_IMAGE_URL = "https://www.cryptocompare.com" 7 | 8 | const val RAW = "RAW" 9 | 10 | const val USD = "USD" 11 | const val BTC = "BTC" 12 | const val ETH = "ETH" 13 | const val SNT = "SNT" 14 | 15 | const val TO = "to" 16 | const val DATA = "Data" 17 | const val TICKERS = "tickers" 18 | 19 | const val HISTO_MINUTE = "histominute" 20 | const val HISTO_HOUR = "histohour" 21 | const val HISTO_DAY = "histoday" 22 | 23 | const val HOUR = "12 hour" 24 | const val HOURS24 = "1 day" 25 | const val WEEK = "1 week" 26 | const val MONTH = "1 month" 27 | const val MONTH3 = "3 month" 28 | const val YEAR = "1 year" 29 | const val ALL = "All" 30 | 31 | const val APP_NAME = "CoinBit" 32 | const val API_KEY = "authorization: Apikey 525fa1933d3246c3bbd6a8ec96207baf3104c80d12b95a4b4cf9196ece4d3728" 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/api/CryptoAPI.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.network.api 2 | 3 | import com.binarybricks.coinbit.BuildConfig 4 | import com.binarybricks.coinbit.network.BASE_CRYPTOCOMPARE_URL 5 | import com.facebook.stetho.okhttp3.StethoInterceptor 6 | import okhttp3.OkHttpClient 7 | import okhttp3.logging.HttpLoggingInterceptor 8 | import retrofit2.Retrofit 9 | import retrofit2.converter.gson.GsonConverterFactory 10 | 11 | /** 12 | Created by Pranay Airan 13 | api provider for crypto compare and others. 14 | */ 15 | 16 | val cryptoCompareRetrofit: Retrofit by lazy { 17 | Retrofit.Builder() 18 | .baseUrl(BASE_CRYPTOCOMPARE_URL) 19 | .client(okHttpClient) 20 | .addConverterFactory(GsonConverterFactory.create()) 21 | .build() 22 | } 23 | 24 | val okHttpClient: OkHttpClient by lazy { 25 | val builder = OkHttpClient.Builder() 26 | if (BuildConfig.DEBUG) { 27 | val loggingInterceptor = HttpLoggingInterceptor() 28 | loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY 29 | builder.addInterceptor(loggingInterceptor) 30 | builder.addInterceptor(StethoInterceptor()) 31 | } 32 | 33 | builder.build() 34 | } 35 | 36 | val api: API by lazy { 37 | cryptoCompareRetrofit.create(API::class.java) 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/models/CCCoin.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.network.models 2 | 3 | import com.binarybricks.coinbit.data.database.entities.Coin 4 | import com.binarybricks.coinbit.data.database.entities.WatchedCoin 5 | import com.google.gson.annotations.SerializedName 6 | 7 | /** 8 | Created by Pranay Airan 1/15/18. 9 | * 10 | * Network object representing Coin from crypto compare 11 | */ 12 | data class CCCoin( 13 | 14 | @field:SerializedName("Id") val id: String = "", 15 | 16 | @field:SerializedName("Url") val url: String = "", 17 | 18 | @field:SerializedName("ImageUrl") val imageUrl: String = "", 19 | 20 | @field:SerializedName("Name") val name: String = "", 21 | 22 | @field:SerializedName("Symbol") val symbol: String = "", 23 | 24 | @field:SerializedName("CoinName") val coinName: String = "", 25 | 26 | @field:SerializedName("FullName") val fullName: String = "", 27 | 28 | @field:SerializedName("Algorithm") val algorithm: String = "", 29 | 30 | @field:SerializedName("ProofType") val proofType: String = "", 31 | 32 | @field:SerializedName("FullyPremined") val fullyPremined: String = "", 33 | 34 | @field:SerializedName("TotalCoinSupply") val totalCoinSupply: String = "", 35 | 36 | @field:SerializedName("PreMinedValue") val preMinedValue: String = "", 37 | 38 | @field:SerializedName("TotalCoinsFreeFloat") val totalCoinsFreeFloat: String = "", 39 | 40 | @field:SerializedName("SortOrder") val sortOrder: String = "", 41 | 42 | @field:SerializedName("Sponsored") val sponsored: Boolean = false, 43 | 44 | @field:SerializedName("IsTrading") val isTrading: Boolean = false 45 | ) 46 | 47 | data class CoinInfoWithCurrency(val currencyName: String, val info: CoinInfo) 48 | data class CoinInfo(val desc: String, val web: String?, val twt: String?, val reddit: String?, val forum: String?, val github: String?) 49 | 50 | fun getCoinFromCCCoin(ccCoin: CCCoin, defaultExchange: String, defaultCurrency: String, coinInfo: CoinInfo?): WatchedCoin { 51 | 52 | val coin = Coin( 53 | ccCoin.id, ccCoin.url, ccCoin.imageUrl, ccCoin.name, ccCoin.symbol, ccCoin.coinName, 54 | ccCoin.fullName, ccCoin.algorithm, ccCoin.proofType, ccCoin.fullyPremined, 55 | ccCoin.totalCoinSupply, ccCoin.preMinedValue, ccCoin.totalCoinsFreeFloat, ccCoin.sortOrder.toInt(), 56 | ccCoin.sponsored, ccCoin.isTrading, coinInfo?.desc, coinInfo?.twt, coinInfo?.web, coinInfo?.reddit, coinInfo?.forum, coinInfo?.github 57 | ) 58 | 59 | return WatchedCoin(coin, defaultExchange, defaultCurrency) 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/models/CoinPair.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.network.models 2 | 3 | import android.os.Parcelable 4 | import com.google.gson.annotations.SerializedName 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | /** 8 | Created by Pranay Airan 1/15/18. 9 | */ 10 | @Parcelize 11 | data class CoinPair( 12 | 13 | @field:SerializedName("SYMBOL") 14 | val symbol: String? = null, 15 | 16 | @field:SerializedName("SUPPLY") 17 | val supply: String? = null, 18 | 19 | @field:SerializedName("FULLNAME") 20 | val fullName: String? = null, 21 | 22 | @field:SerializedName("NAME") 23 | val name: String? = null, 24 | 25 | @field:SerializedName("VOLUME24HOURTO") 26 | val volume24Hours: String? = null 27 | 28 | ) : Parcelable 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/models/CoinPrice.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.network.models 2 | 3 | import android.os.Parcelable 4 | import com.google.gson.annotations.SerializedName 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | /** 8 | Created by Pranay Airan 1/15/18. 9 | */ 10 | @Parcelize 11 | data class CoinPrice( 12 | 13 | @field:SerializedName("LASTTRADEID") 14 | val lastTradedID: String? = null, 15 | 16 | @field:SerializedName("OPEN24HOUR") 17 | val open24Hour: String? = null, 18 | 19 | @field:SerializedName("LOW24HOUR") 20 | val low24Hour: String? = null, 21 | 22 | @field:SerializedName("HIGHDAY") 23 | val highDay: String? = null, 24 | 25 | @field:SerializedName("TOTALVOLUME24H") 26 | val totalVolume24Hour: String? = null, 27 | 28 | @field:SerializedName("TOTALVOLUME24HTO") 29 | val totalVolume24HoursTo: String? = null, 30 | 31 | @field:SerializedName("TOSYMBOL") 32 | val toSymbol: String? = null, 33 | 34 | @field:SerializedName("FROMSYMBOL") 35 | val fromSymbol: String? = null, 36 | 37 | @field:SerializedName("LASTVOLUME") 38 | val lastVolume: String? = null, 39 | 40 | @field:SerializedName("LASTMARKET") 41 | val lastMarket: String? = null, 42 | 43 | @field:SerializedName("MKTCAP") 44 | var marketCap: String? = null, 45 | 46 | @field:SerializedName("LASTUPDATE") 47 | val lastUpdateTime: Int? = null, 48 | 49 | @field:SerializedName("CHANGEDAY") 50 | val changeDay: String? = null, 51 | 52 | @field:SerializedName("FLAGS") 53 | val flags: String? = null, 54 | 55 | @field:SerializedName("SUPPLY") 56 | val supply: Int? = null, 57 | 58 | @field:SerializedName("TYPE") 59 | val type: String? = null, 60 | 61 | @field:SerializedName("VOLUMEDAY") 62 | val volumneDay: String? = null, 63 | 64 | @field:SerializedName("VOLUME24HOUR") 65 | val volume24Hour: String? = null, 66 | 67 | @field:SerializedName("MARKET") 68 | val market: String? = null, 69 | 70 | @field:SerializedName("PRICE") 71 | val price: String? = null, 72 | 73 | @field:SerializedName("CHANGEPCTDAY") 74 | val changePercentageDay: String? = null, 75 | 76 | @field:SerializedName("LASTVOLUMETO") 77 | val lastVolumeTo: String? = null, 78 | 79 | @field:SerializedName("CHANGEPCT24HOUR") 80 | val changePercentage24Hour: String? = null, 81 | 82 | @field:SerializedName("OPENDAY") 83 | val openDay: String? = null, 84 | 85 | @field:SerializedName("VOLUMEDAYTO") 86 | val volumeDayTo: String? = null, 87 | 88 | @field:SerializedName("CHANGE24HOUR") 89 | val change24Hours: String? = null, 90 | 91 | @field:SerializedName("HIGH24HOUR") 92 | val high24Hours: String? = null, 93 | 94 | @field:SerializedName("VOLUME24HOURTO") 95 | val volume24HoursTo: String? = null, 96 | 97 | @field:SerializedName("LOWDAY") 98 | val lowDay: String? = null 99 | ) : Parcelable 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/models/CryptoCompareHistoricalResponse.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.network.models 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | Created by Pranay Airan 1/10/18. 7 | */ 8 | 9 | data class CryptoCompareHistoricalResponse( 10 | @SerializedName("FirstValueInArray") val firstValueInArray: String, 11 | @SerializedName("Data") val data: List, 12 | @SerializedName("TimeFrom") val timeFrom: String, 13 | @SerializedName("Type") val type: String, 14 | @SerializedName("Response") val response: String, 15 | @SerializedName("ConversionType") val conversionType: ConversionType, 16 | @SerializedName("TimeTo") val timeTo: String, 17 | @SerializedName("Aggregated") val aggregated: String 18 | ) { 19 | 20 | data class ConversionType(val conversionSymbol: String, val type: String) 21 | 22 | data class Data( 23 | val open: String, 24 | val time: String, 25 | val volumeto: String, 26 | val volumefrom: String, 27 | val high: String, 28 | val low: String, 29 | val close: String 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/models/CryptoCompareNews.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.network.models 2 | 3 | import android.os.Parcelable 4 | import com.google.gson.annotations.SerializedName 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | /** 8 | Created by Pranay Airan 12/15/18. 9 | */ 10 | @Parcelize 11 | data class CryptoCompareNews( 12 | 13 | @field:SerializedName("id") 14 | val id: String? = null, 15 | 16 | @field:SerializedName("guid") 17 | val guid: String? = null, 18 | 19 | @field:SerializedName("published_on") 20 | val published_on: String? = null, 21 | 22 | @field:SerializedName("imageurl") 23 | val imageurl: String? = null, 24 | 25 | @field:SerializedName("title") 26 | val title: String? = null, 27 | 28 | @field:SerializedName("url") 29 | val url: String? = null, 30 | 31 | @field:SerializedName("source") 32 | val source: String? = null, 33 | 34 | @field:SerializedName("body") 35 | val body: String? = null, 36 | 37 | @field:SerializedName("tags") 38 | val tags: String? = null, 39 | 40 | @field:SerializedName("categories") 41 | val categories: String? = null 42 | ) : Parcelable 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/models/CryptoPanicNews.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.network.models 2 | 3 | import android.os.Parcelable 4 | import kotlinx.android.parcel.Parcelize 5 | 6 | /** 7 | * Created by Pragya Agrawal 8 | * 9 | * Data class representing news object 10 | */ 11 | 12 | @Parcelize 13 | data class CryptoPanicNews( 14 | val count: String = "", 15 | val results: List? 16 | ) : Parcelable 17 | 18 | @Parcelize 19 | data class Results( 20 | val id: String = "", 21 | 22 | val title: String = "", 23 | 24 | val source: Source = Source(), 25 | 26 | val domain: String = "", 27 | 28 | val created_at: String = "", 29 | 30 | val slug: String = "", 31 | 32 | val url: String = "", 33 | 34 | val published_at: String = "" 35 | ) : Parcelable 36 | 37 | @Parcelize 38 | data class Source(val title: String = "", val domain: String = "") : Parcelable 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/models/CryptoTicker.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.network.models 2 | 3 | import android.os.Parcelable 4 | import kotlinx.android.parcel.Parcelize 5 | 6 | /** 7 | * Created by Pranay Airan 8 | * 9 | * Data class representing price ticker object 10 | */ 11 | 12 | @Parcelize 13 | data class CryptoTicker( 14 | val base: String = "", 15 | 16 | val target: String = "", 17 | 18 | val marketName: String = "", 19 | 20 | val marketIdentifier: String = "", 21 | 22 | val last: String = "", 23 | 24 | val volume: String = "", 25 | 26 | val convertedVolumeUSD: String = "", 27 | 28 | val convertedVolumeBTC: String = "", 29 | 30 | val timestamp: String = "", 31 | 32 | val imageUrl: String = "", 33 | 34 | val exchangeUrl: String = "" 35 | 36 | ) : Parcelable 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/network/models/ExchangePair.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.network.models 2 | 3 | import android.os.Parcelable 4 | import kotlinx.android.parcel.Parcelize 5 | 6 | /** 7 | * Created by Pranay Airan 8 | */ 9 | 10 | @Parcelize 11 | data class ExchangePair(val exchangeName: String, val pairList: MutableList) : Parcelable 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.utils 2 | 3 | /** 4 | Created by Pranay Airan 1/14/18. 5 | */ 6 | 7 | const val chartAnimationDuration: Long = 250 8 | 9 | const val defaultExchange: String = "CCCAGG" 10 | const val defaultCurrency: String = "USD" 11 | 12 | const val TRANSACTION_TYPE_BUY: Int = 1 13 | const val TRANSACTION_TYPE_SELL: Int = 2 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/utils/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.utils 2 | 3 | import android.widget.RadioButton 4 | import android.widget.RadioGroup 5 | import androidx.annotation.ColorInt 6 | 7 | /** 8 | Created by Pranay Airan 1/14/18. 9 | */ 10 | 11 | fun RadioGroup.changeChildrenColor(@ColorInt color: Int) { 12 | val childCount = this.childCount 13 | var i = 0 14 | while (i < childCount) { 15 | val radioButton = this.getChildAt(i) as RadioButton 16 | if (!radioButton.isChecked) { 17 | radioButton.setTextColor(color) 18 | } 19 | i++ 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/utils/resourcemanager/AndroidResourceManager.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.utils.resourcemanager 2 | 3 | import android.graphics.drawable.Drawable 4 | import androidx.annotation.DrawableRes 5 | import androidx.annotation.PluralsRes 6 | import androidx.annotation.StringRes 7 | 8 | /** 9 | * Created by Pranay Airan 1/16/18. 10 | * 11 | * This is used to inject the resources for testing 12 | * https://medium.com/@daptronic/android-mvp-the-curious-case-of-resources-ddca39c1fccd 13 | */ 14 | interface AndroidResourceManager { 15 | 16 | fun getString(@StringRes resId: Int): String 17 | 18 | fun getString(@StringRes resId: Int, vararg formatArgs: Any): String 19 | 20 | fun getQuantityString(@PluralsRes resId: Int, quantity: Int): String 21 | 22 | fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any): String 23 | 24 | fun getColor(resId: Int): Int 25 | 26 | fun getDrawable(@DrawableRes resId: Int): Drawable? 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/utils/resourcemanager/AndroidResourceManagerImpl.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.utils.resourcemanager 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import androidx.annotation.PluralsRes 6 | import androidx.annotation.StringRes 7 | import androidx.core.content.ContextCompat 8 | 9 | /** 10 | * Created by Pranay Airan 1/16/18. 11 | */ 12 | class AndroidResourceManagerImpl(context: Context) : AndroidResourceManager { 13 | 14 | private val appContext: Context = context.applicationContext 15 | 16 | override fun getString(@StringRes resId: Int): String { 17 | return appContext.getString(resId) 18 | } 19 | 20 | override fun getString(@StringRes resId: Int, vararg formatArgs: Any): String { 21 | return appContext.getString(resId, *formatArgs) 22 | } 23 | 24 | override fun getQuantityString(@PluralsRes resId: Int, quantity: Int): String { 25 | return appContext.resources.getQuantityString(resId, quantity) 26 | } 27 | 28 | override fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any): String { 29 | return appContext.resources.getQuantityString(resId, quantity, *formatArgs) 30 | } 31 | 32 | override fun getColor(resId: Int): Int { 33 | return ContextCompat.getColor(appContext, resId) 34 | } 35 | 36 | override fun getDrawable(resId: Int): Drawable? { 37 | return ContextCompat.getDrawable(appContext, resId) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/binarybricks/coinbit/utils/ui/IntroPageTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.binarybricks.coinbit.utils.ui 2 | 3 | import android.view.View 4 | import android.widget.Button 5 | import android.widget.TextView 6 | import androidx.viewpager.widget.ViewPager 7 | import com.airbnb.lottie.LottieAnimationView 8 | import com.binarybricks.coinbit.R 9 | 10 | class IntroPageTransformer : ViewPager.PageTransformer { 11 | 12 | override fun transformPage(page: View, position: Float) { 13 | 14 | // Get the page index from the tag. This makes 15 | // it possible to know which page index you're 16 | // currently transforming - and that can be used 17 | // to make some important performance improvements. 18 | val pagePosition = page.tag as Int 19 | 20 | // Here you can do all kinds of stuff, like get the 21 | // width of the page and perform calculations based 22 | // on how far the user has swiped the page. 23 | val pageWidth = page.width 24 | val pageWidthTimesPosition = pageWidth * position 25 | val absPosition = Math.abs(position) 26 | 27 | // Now it's time for the effects 28 | if (position <= -1.0f || position >= 1.0f) { 29 | 30 | // The page is not visible. This is a good place to stop 31 | // any potential work / animations you may have running. 32 | } else if (position == 0.0f) { 33 | 34 | // The page is selected. This is a good time to reset Views 35 | // after animations as you can't always count on the PageTransformer 36 | // callbacks to match up perfectly. 37 | page.findViewById(R.id.animationView).playAnimation() 38 | } else { 39 | 40 | // The page is currently being scrolled / swiped. This is 41 | // a good place to show animations that react to the user's 42 | // swiping as it provides a good user experience. 43 | 44 | // Let's start by animating the title. 45 | // We want it to fade as it scrolls out 46 | val title = page.findViewById(R.id.tvTitle) 47 | title.translationX = ((-(1 - position) * 0.1 * pageWidthTimesPosition).toFloat()) 48 | 49 | // Now the description. We also want this one to 50 | // fade, but the animation should also slowly move 51 | // down and out of the screen 52 | val description = page.findViewById(R.id.tvSubTitle) 53 | description.translationX = (-(1 - position) * 0.2 * pageWidthTimesPosition).toFloat() 54 | description.alpha = 1.0f - absPosition 55 | 56 | // Now, we want the image to move to the right, 57 | // i.e. in the opposite direction of the rest of the 58 | // content while fading out 59 | val animation = page.findViewById(R.id.animationView) 60 | animation.translationX = ((-(1 - position) * .7 * pageWidthTimesPosition)).toFloat() 61 | 62 | val button = page.findViewById