From 26989ef525f4a4fad7bd0d21f15673495ad4ccb8 Mon Sep 17 00:00:00 2001 From: kimbsu00 <58127442+kimbsu00@users.noreply.github.com> Date: Wed, 30 Jun 2021 13:08:02 +0900 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 15 + .idea/.gitignore | 3 + .idea/codeStyles/Project.xml | 123 ++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/compiler.xml | 6 + .idea/gradle.xml | 22 + .idea/jarRepositories.xml | 30 + .idea/misc.xml | 9 + .idea/runConfigurations.xml | 10 + app/.gitignore | 1 + app/build.gradle | 55 ++ app/proguard-rules.pro | 21 + .../mobit/mobit/ExampleInstrumentedTest.kt | 24 + app/src/main/AndroidManifest.xml | 37 ++ app/src/main/ic_launcher_logo-playstore.png | Bin 0 -> 23397 bytes .../com/mobit/mobit/FirstSettingActivity.kt | 68 ++ .../java/com/mobit/mobit/FragmentAsset.kt | 130 ++++ .../main/java/com/mobit/mobit/FragmentBuy.kt | 292 +++++++++ .../java/com/mobit/mobit/FragmentChart.kt | 595 ++++++++++++++++++ .../java/com/mobit/mobit/FragmentCoinInfo.kt | 58 ++ .../java/com/mobit/mobit/FragmentCoinList.kt | 143 +++++ .../com/mobit/mobit/FragmentInvestment.kt | 49 ++ .../java/com/mobit/mobit/FragmentRecord.kt | 79 +++ .../main/java/com/mobit/mobit/FragmentSell.kt | 323 ++++++++++ .../java/com/mobit/mobit/FragmentSetting.kt | 94 +++ .../com/mobit/mobit/FragmentTransaction.kt | 236 +++++++ .../main/java/com/mobit/mobit/MainActivity.kt | 364 +++++++++++ .../java/com/mobit/mobit/MyProgressBar.kt | 51 ++ .../com/mobit/mobit/PopupBuySellActivity.kt | 71 +++ .../mobit/mobit/PopupResetConfirmActivity.kt | 38 ++ .../mobit/adapter/FragmentAssetAdapter.kt | 87 +++ .../mobit/adapter/FragmentCoinListAdapter.kt | 130 ++++ .../mobit/adapter/FragmentRecordAdapter.kt | 125 ++++ .../adapter/FragmentTransactionAdapter.kt | 91 +++ .../mobit/adapter/InvestmentStateAdapter.kt | 26 + .../main/java/com/mobit/mobit/data/Asset.kt | 109 ++++ .../main/java/com/mobit/mobit/data/Candle.kt | 12 + .../java/com/mobit/mobit/data/CoinAsset.kt | 11 + .../java/com/mobit/mobit/data/CoinInfo.kt | 52 ++ .../java/com/mobit/mobit/data/MyViewModel.kt | 107 ++++ .../java/com/mobit/mobit/data/OrderBook.kt | 12 + .../main/java/com/mobit/mobit/data/Price.kt | 39 ++ .../java/com/mobit/mobit/data/Transaction.kt | 18 + .../java/com/mobit/mobit/db/MyDBHelper.kt | 344 ++++++++++ .../com/mobit/mobit/network/UpbitAPICaller.kt | 249 ++++++++ .../mobit/mobit/service/UpbitAPIService.kt | 233 +++++++ .../color/coin_list_button_selector_color.xml | 5 + .../color/navigation_menu_selector_color.xml | 5 + .../transaction_buy_button_selector_color.xml | 5 + ...transaction_info_button_selector_color.xml | 5 + ...transaction_sell_button_selector_color.xml | 5 + .../drawable-v24/ic_launcher_foreground.xml | 30 + .../drawable/asset_linearlayout_border.xml | 22 + .../drawable/asset_linearlayout_border_2.xml | 24 + .../drawable/asset_linearlayout_border_3.xml | 22 + .../res/drawable/coin_list_button_border.xml | 4 + .../coin_list_button_border_checked.xml | 8 + .../coin_list_button_border_not_checked.xml | 8 + .../coin_list_linearlayout_border.xml | 23 + ...coin_list_recyclerview_item_background.xml | 23 + .../drawable/coin_list_searchview_border.xml | 18 + ...irst_setting_button_pressed_background.xml | 14 + .../main/res/drawable/ic_baseline_add_24.xml | 5 + .../drawable/ic_baseline_description_24.xml | 5 + .../main/res/drawable/ic_baseline_home_24.xml | 5 + .../drawable/ic_baseline_insert_chart_24.xml | 5 + .../ic_baseline_keyboard_arrow_down_24.xml | 5 + .../ic_baseline_keyboard_arrow_up_24.xml | 5 + .../res/drawable/ic_baseline_remove_24.xml | 5 + .../res/drawable/ic_baseline_search_24.xml | 5 + .../res/drawable/ic_baseline_settings_24.xml | 5 + .../res/drawable/ic_baseline_sync_alt_24.xml | 6 + .../res/drawable/ic_launcher_background.xml | 170 +++++ .../drawable/ic_launcher_logo_background.xml | 74 +++ .../drawable/ic_outline_description_24.xml | 5 + .../main/res/drawable/ic_outline_home_24.xml | 5 + .../drawable/ic_outline_insert_chart_24.xml | 5 + .../res/drawable/ic_outline_settings_24.xml | 5 + .../res/drawable/ic_outline_sync_alt_24.xml | 6 + .../main/res/drawable/ic_round_star_24.xml | 5 + .../res/drawable/ic_round_star_border_24.xml | 5 + .../main/res/drawable/menu_chart_selector.xml | 5 + .../res/drawable/menu_coinlist_selector.xml | 5 + .../res/drawable/menu_investment_selector.xml | 5 + .../res/drawable/menu_setting_selector.xml | 5 + .../main/res/drawable/menu_trade_selector.xml | 5 + .../navigation_menu_selector_color.xml | 5 + .../drawable/popup_buy_sell_button_border.xml | 23 + .../popup_buy_sell_linearlayout_border.xml | 23 + .../drawable/record_linearlayout_border.xml | 9 + app/src/main/res/drawable/splash.xml | 9 + app/src/main/res/drawable/splash_image.png | Bin 0 -> 4083 bytes .../transaction_button_background.xml | 4 + .../transaction_button_background_checked.xml | 4 + ...nsaction_button_background_not_checked.xml | 4 + .../transaction_linearlayout_border.xml | 8 + .../res/layout/activity_first_setting.xml | 191 ++++++ app/src/main/res/layout/activity_main.xml | 35 ++ .../res/layout/activity_popup_buy_sell.xml | 186 ++++++ .../layout/activity_popup_reset_confirm.xml | 90 +++ app/src/main/res/layout/fragment_asset.xml | 215 +++++++ app/src/main/res/layout/fragment_buy.xml | 287 +++++++++ app/src/main/res/layout/fragment_chart.xml | 191 ++++++ .../main/res/layout/fragment_coin_info.xml | 32 + .../main/res/layout/fragment_coin_list.xml | 127 ++++ .../main/res/layout/fragment_investment.xml | 40 ++ app/src/main/res/layout/fragment_record.xml | 40 ++ app/src/main/res/layout/fragment_sell.xml | 287 +++++++++ app/src/main/res/layout/fragment_setting.xml | 184 ++++++ .../main/res/layout/fragment_transaction.xml | 190 ++++++ app/src/main/res/layout/progress_loading.xml | 24 + .../res/layout/recyclerview_asset_item.xml | 236 +++++++ .../res/layout/recyclerview_coinlist_item.xml | 70 +++ .../res/layout/recyclerview_record_item.xml | 240 +++++++ .../layout/recyclerview_transaction_item.xml | 61 ++ app/src/main/res/layout/spinner_item.xml | 9 + .../res/menu/menu_bottomnavigationview.xml | 27 + app/src/main/res/menu/menu_chart_unit.xml | 25 + .../res/menu/menu_coinlist_item_clicked.xml | 9 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_logo.xml | 5 + .../ic_launcher_logo_round.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3593 bytes .../main/res/mipmap-hdpi/ic_launcher_logo.png | Bin 0 -> 1844 bytes .../ic_launcher_logo_foreground.png | Bin 0 -> 2641 bytes .../mipmap-hdpi/ic_launcher_logo_round.png | Bin 0 -> 3714 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5339 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2636 bytes .../main/res/mipmap-mdpi/ic_launcher_logo.png | Bin 0 -> 1232 bytes .../ic_launcher_logo_foreground.png | Bin 0 -> 1689 bytes .../mipmap-mdpi/ic_launcher_logo_round.png | Bin 0 -> 2334 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3388 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_logo.png | Bin 0 -> 2713 bytes .../ic_launcher_logo_foreground.png | Bin 0 -> 3743 bytes .../mipmap-xhdpi/ic_launcher_logo_round.png | Bin 0 -> 5423 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7472 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_logo.png | Bin 0 -> 4116 bytes .../ic_launcher_logo_foreground.png | Bin 0 -> 6301 bytes .../mipmap-xxhdpi/ic_launcher_logo_round.png | Bin 0 -> 8455 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11873 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_logo.png | Bin 0 -> 5873 bytes .../ic_launcher_logo_foreground.png | Bin 0 -> 9494 bytes .../mipmap-xxxhdpi/ic_launcher_logo_round.png | Bin 0 -> 12219 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16570 bytes app/src/main/res/values-night/themes.xml | 33 + app/src/main/res/values/colors.xml | 29 + .../values/ic_launcher_logo_background.xml | 4 + app/src/main/res/values/spinnerdata.xml | 23 + app/src/main/res/values/strings.xml | 146 +++++ app/src/main/res/values/themes.xml | 33 + .../java/com/mobit/mobit/ExampleUnitTest.kt | 17 + build.gradle | 27 + gradle.properties | 21 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++ gradlew.bat | 84 +++ settings.gradle | 2 + 163 files changed, 8678 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/mobit/mobit/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher_logo-playstore.png create mode 100644 app/src/main/java/com/mobit/mobit/FirstSettingActivity.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentAsset.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentBuy.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentChart.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentCoinInfo.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentCoinList.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentInvestment.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentRecord.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentSell.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentSetting.kt create mode 100644 app/src/main/java/com/mobit/mobit/FragmentTransaction.kt create mode 100644 app/src/main/java/com/mobit/mobit/MainActivity.kt create mode 100644 app/src/main/java/com/mobit/mobit/MyProgressBar.kt create mode 100644 app/src/main/java/com/mobit/mobit/PopupBuySellActivity.kt create mode 100644 app/src/main/java/com/mobit/mobit/PopupResetConfirmActivity.kt create mode 100644 app/src/main/java/com/mobit/mobit/adapter/FragmentAssetAdapter.kt create mode 100644 app/src/main/java/com/mobit/mobit/adapter/FragmentCoinListAdapter.kt create mode 100644 app/src/main/java/com/mobit/mobit/adapter/FragmentRecordAdapter.kt create mode 100644 app/src/main/java/com/mobit/mobit/adapter/FragmentTransactionAdapter.kt create mode 100644 app/src/main/java/com/mobit/mobit/adapter/InvestmentStateAdapter.kt create mode 100644 app/src/main/java/com/mobit/mobit/data/Asset.kt create mode 100644 app/src/main/java/com/mobit/mobit/data/Candle.kt create mode 100644 app/src/main/java/com/mobit/mobit/data/CoinAsset.kt create mode 100644 app/src/main/java/com/mobit/mobit/data/CoinInfo.kt create mode 100644 app/src/main/java/com/mobit/mobit/data/MyViewModel.kt create mode 100644 app/src/main/java/com/mobit/mobit/data/OrderBook.kt create mode 100644 app/src/main/java/com/mobit/mobit/data/Price.kt create mode 100644 app/src/main/java/com/mobit/mobit/data/Transaction.kt create mode 100644 app/src/main/java/com/mobit/mobit/db/MyDBHelper.kt create mode 100644 app/src/main/java/com/mobit/mobit/network/UpbitAPICaller.kt create mode 100644 app/src/main/java/com/mobit/mobit/service/UpbitAPIService.kt create mode 100644 app/src/main/res/color/coin_list_button_selector_color.xml create mode 100644 app/src/main/res/color/navigation_menu_selector_color.xml create mode 100644 app/src/main/res/color/transaction_buy_button_selector_color.xml create mode 100644 app/src/main/res/color/transaction_info_button_selector_color.xml create mode 100644 app/src/main/res/color/transaction_sell_button_selector_color.xml create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/asset_linearlayout_border.xml create mode 100644 app/src/main/res/drawable/asset_linearlayout_border_2.xml create mode 100644 app/src/main/res/drawable/asset_linearlayout_border_3.xml create mode 100644 app/src/main/res/drawable/coin_list_button_border.xml create mode 100644 app/src/main/res/drawable/coin_list_button_border_checked.xml create mode 100644 app/src/main/res/drawable/coin_list_button_border_not_checked.xml create mode 100644 app/src/main/res/drawable/coin_list_linearlayout_border.xml create mode 100644 app/src/main/res/drawable/coin_list_recyclerview_item_background.xml create mode 100644 app/src/main/res/drawable/coin_list_searchview_border.xml create mode 100644 app/src/main/res/drawable/first_setting_button_pressed_background.xml create mode 100644 app/src/main/res/drawable/ic_baseline_add_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_description_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_home_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_insert_chart_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_remove_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_search_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_settings_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_sync_alt_24.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_logo_background.xml create mode 100644 app/src/main/res/drawable/ic_outline_description_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_home_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_insert_chart_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_settings_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_sync_alt_24.xml create mode 100644 app/src/main/res/drawable/ic_round_star_24.xml create mode 100644 app/src/main/res/drawable/ic_round_star_border_24.xml create mode 100644 app/src/main/res/drawable/menu_chart_selector.xml create mode 100644 app/src/main/res/drawable/menu_coinlist_selector.xml create mode 100644 app/src/main/res/drawable/menu_investment_selector.xml create mode 100644 app/src/main/res/drawable/menu_setting_selector.xml create mode 100644 app/src/main/res/drawable/menu_trade_selector.xml create mode 100644 app/src/main/res/drawable/navigation_menu_selector_color.xml create mode 100644 app/src/main/res/drawable/popup_buy_sell_button_border.xml create mode 100644 app/src/main/res/drawable/popup_buy_sell_linearlayout_border.xml create mode 100644 app/src/main/res/drawable/record_linearlayout_border.xml create mode 100644 app/src/main/res/drawable/splash.xml create mode 100644 app/src/main/res/drawable/splash_image.png create mode 100644 app/src/main/res/drawable/transaction_button_background.xml create mode 100644 app/src/main/res/drawable/transaction_button_background_checked.xml create mode 100644 app/src/main/res/drawable/transaction_button_background_not_checked.xml create mode 100644 app/src/main/res/drawable/transaction_linearlayout_border.xml create mode 100644 app/src/main/res/layout/activity_first_setting.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_popup_buy_sell.xml create mode 100644 app/src/main/res/layout/activity_popup_reset_confirm.xml create mode 100644 app/src/main/res/layout/fragment_asset.xml create mode 100644 app/src/main/res/layout/fragment_buy.xml create mode 100644 app/src/main/res/layout/fragment_chart.xml create mode 100644 app/src/main/res/layout/fragment_coin_info.xml create mode 100644 app/src/main/res/layout/fragment_coin_list.xml create mode 100644 app/src/main/res/layout/fragment_investment.xml create mode 100644 app/src/main/res/layout/fragment_record.xml create mode 100644 app/src/main/res/layout/fragment_sell.xml create mode 100644 app/src/main/res/layout/fragment_setting.xml create mode 100644 app/src/main/res/layout/fragment_transaction.xml create mode 100644 app/src/main/res/layout/progress_loading.xml create mode 100644 app/src/main/res/layout/recyclerview_asset_item.xml create mode 100644 app/src/main/res/layout/recyclerview_coinlist_item.xml create mode 100644 app/src/main/res/layout/recyclerview_record_item.xml create mode 100644 app/src/main/res/layout/recyclerview_transaction_item.xml create mode 100644 app/src/main/res/layout/spinner_item.xml create mode 100644 app/src/main/res/menu/menu_bottomnavigationview.xml create mode 100644 app/src/main/res/menu/menu_chart_unit.xml create mode 100644 app/src/main/res/menu/menu_coinlist_item_clicked.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_logo.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_logo_round.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_logo.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_logo_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_logo_round.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_logo.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_logo_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_logo_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_logo.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_logo_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_logo_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_logo.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_logo_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_logo_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_logo.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_logo_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_logo_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/ic_launcher_logo_background.xml create mode 100644 app/src/main/res/values/spinnerdata.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/test/java/com/mobit/mobit/ExampleUnitTest.kt create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..7643783 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..b81a6f6 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..2370474 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..860da66 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..797acea --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..00c1f51 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,55 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + applicationId "com.mobit.mobit" + minSdkVersion 26 + targetSdkVersion 29 + versionCode 1 + versionName "1.0.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + + viewBinding { + enabled = true + } +} + +dependencies { + + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' + implementation 'androidx.fragment:fragment-ktx:1.3.3' + implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/mobit/mobit/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/mobit/mobit/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..b87133b --- /dev/null +++ b/app/src/androidTest/java/com/mobit/mobit/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.mobit.mobit + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.mobit.mobit", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6ba74d1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher_logo-playstore.png b/app/src/main/ic_launcher_logo-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..e78cfca876c727ea134b7c7e3ebf951cbc29b391 GIT binary patch literal 23397 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 + this, + R.layout.spinner_item, + resources.getStringArray(R.array.depositSpinner) + ) + + binding.apply { + depositSpinner.adapter = spinnerAdapter + depositSpinner.setSelection(0, false) + depositSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + depositValue = when (position) { + 0 -> 1000000.0 + 1 -> 3000000.0 + 2 -> 5000000.0 + 3 -> 10000000.0 + 4 -> 30000000.0 + 5 -> 50000000.0 + else -> 1000000.0 + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + + confirmBtn.setOnClickListener { + val intent = Intent() + intent.putExtra("krw", depositValue) + setResult(RESULT_OK, intent) + finish() + } + } + } + + override fun onBackPressed() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentAsset.kt b/app/src/main/java/com/mobit/mobit/FragmentAsset.kt new file mode 100644 index 0000000..f5a7326 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentAsset.kt @@ -0,0 +1,130 @@ +package com.mobit.mobit + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import com.mobit.mobit.adapter.FragmentAssetAdapter +import com.mobit.mobit.data.Asset +import com.mobit.mobit.data.CoinAsset +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.databinding.FragmentAssetBinding +import java.text.DecimalFormat + +/* +보유자산 기능이 구현될 Fragment 입니다. +*/ +class FragmentAsset : Fragment() { + + // UI 변수 시작 + lateinit var binding: FragmentAssetBinding + // UI 변수 끝 + + val myViewModel: MyViewModel by activityViewModels() + val retainedCoin: ArrayList = ArrayList() + + lateinit var adapter: FragmentAssetAdapter + + val formatter = DecimalFormat("###,###") + val changeFormatter = DecimalFormat("###,###.##") + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentAssetBinding.inflate(layoutInflater) + + init() + + return binding.root + } + + fun init() { + myViewModel.asset.observe(viewLifecycleOwner, Observer { asset -> + val krw: Double = asset.krw // 보유 KRW + var total: Double = krw // 총 보유자산 + var buyPrice: Double = 0.0 // 총 매수 + var totalEvaluation: Double = 0.0 // 총 평가 + for (coin in asset.coins) { + total += coin.amount + buyPrice += coin.number * coin.averagePrice + totalEvaluation += coin.amount + } + val gainAndLoss: Double = if (buyPrice > 0.0) totalEvaluation - buyPrice else 0.0 + val yieldValue: Double = if (buyPrice > 0.0) gainAndLoss / buyPrice * 100 else 0.0 + + binding.apply { + krwView.text = formatter.format(krw) + totalView.text = formatter.format(total) + totalBuyView.text = formatter.format(buyPrice) + + if (buyPrice > 0.0) { + recyclerView.visibility = View.VISIBLE + noAssetView.visibility = View.GONE + + gainAndLossView.text = formatter.format(gainAndLoss) + totalEvaluationView.text = formatter.format(totalEvaluation) + yieldView.text = changeFormatter.format(yieldValue) + "%" + + val rgb = if (buyPrice > totalEvaluation) Color.rgb( + 25, + 96, + 186 + ) else if (buyPrice < totalEvaluation) Color.rgb( + 207, + 80, + 71 + ) else Color.rgb(211, 212, 214) + gainAndLossView.setTextColor(rgb) + yieldView.setTextColor(rgb) + } else { + recyclerView.visibility = View.GONE + noAssetView.visibility = View.VISIBLE + + gainAndLossView.text = getString(R.string.asset_no_data) + totalEvaluationView.text = getString(R.string.asset_no_data) + yieldView.text = getString(R.string.asset_no_data) + + val rgb = Color.rgb(211, 212, 214) + gainAndLossView.setTextColor(rgb) + totalEvaluationView.setTextColor(rgb) + yieldView.setTextColor(rgb) + } + } + + retainedCoin.clear() + retainedCoin.addAll(myViewModel.asset.value!!.coins) + adapter.notifyDataSetChanged() + }) + myViewModel.coinInfo.observe(viewLifecycleOwner, Observer { + val asset2 = Asset(myViewModel.asset.value!!.krw, ArrayList()) + for (i in myViewModel.asset.value!!.coins.indices) { + for (coinInfo in it) { + if (myViewModel.asset.value!!.coins[i].code == coinInfo.code) { + myViewModel.asset.value!!.coins[i].amount = + myViewModel.asset.value!!.coins[i].number * coinInfo.price.realTimePrice + asset2.coins.add(myViewModel.asset.value!!.coins[i]) + break + } + } + } + myViewModel.setAsset(asset2) + + retainedCoin.clear() + retainedCoin.addAll(myViewModel.asset.value!!.coins) + adapter.notifyDataSetChanged() + }) + + adapter = FragmentAssetAdapter(retainedCoin) + binding.apply { + recyclerView.layoutManager = + LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + recyclerView.adapter = adapter + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentBuy.kt b/app/src/main/java/com/mobit/mobit/FragmentBuy.kt new file mode 100644 index 0000000..b5071ee --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentBuy.kt @@ -0,0 +1,292 @@ +package com.mobit.mobit + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import com.mobit.mobit.data.CoinInfo +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.data.Transaction +import com.mobit.mobit.databinding.FragmentBuyBinding +import java.text.DecimalFormat +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +/* +코인 매수 기능이 구현될 Fragment 입니다. +*/ +class FragmentBuy : Fragment() { + + lateinit var getContent: ActivityResultLauncher + + // UI 변수 시작 + lateinit var binding: FragmentBuyBinding + // UI 변수 끝 + + val myViewModel: MyViewModel by activityViewModels() + + val formatter = DecimalFormat("###,###") + val formatter2 = DecimalFormat("###,###.####") + val formatter3 = DecimalFormat("###,###.##") + var orderCount: Double = 0.0 + var orderPrice: Double = 0.0 + + var buyIndex: Int = -1 + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentBuyBinding.inflate(layoutInflater) + getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + binding.orderCount.clearFocus() + when (it.resultCode) { + Activity.RESULT_OK -> { + val code = it.data!!.getStringExtra("code") + val name = it.data!!.getStringExtra("name") + val price: Double = orderPrice * orderCount + val fee: Double = price * 0.0005 + val time: String = getNowTime() + val transaction = Transaction( + code, + name, + time, + Transaction.BID, + orderCount, + orderPrice, + price - fee, + fee, + price + ) + + buyIndex = myViewModel.bidCoin( + code, + name, + orderPrice, + orderCount + ) + + myViewModel.addTransaction(transaction) + val thread = object : Thread() { + override fun run() { + myViewModel.myDBHelper!!.setKRW(myViewModel.asset.value!!.krw) + myViewModel.myDBHelper!!.insertTransaction(transaction) + + if (myViewModel.myDBHelper!!.findCoinAsset(code)) { + val ret = + myViewModel.myDBHelper!!.updateCoinAsset(myViewModel.asset.value!!.coins[buyIndex]) + } else { + val ret = + myViewModel.myDBHelper!!.insertCoinAsset(myViewModel.asset.value!!.coins[buyIndex]) + } + } + } + thread.start() + binding.canOrderPrice.text = + "${formatter.format(myViewModel.asset.value!!.krw)}KRW" + resetOrderTextView() + Toast.makeText(context, "매수주문이 정상 처리되었습니다.", Toast.LENGTH_SHORT).show() + } + Activity.RESULT_CANCELED -> { + Log.i("resultCode", "RESULT_CANCELED") + } + } + } + + init() + + return binding.root + } + + override fun onResume() { + super.onResume() + binding.apply { + canOrderPrice.text = "${formatter.format(myViewModel.asset.value!!.krw)}KRW" + orderCount.setText("0") + orderCountSpinner.setSelection(0) + } + } + + fun init() { + myViewModel.coinInfo.observe(viewLifecycleOwner, Observer { + for (coinInfo in myViewModel.coinInfo.value!!) { + if (coinInfo.code == myViewModel.selectedCoin.value!!) { + orderPrice = coinInfo.price.realTimePrice + break + } + } + binding.orderPrice.text = + if (orderPrice > 100.0) + formatter.format(orderPrice) + else + formatter3.format(orderPrice) + binding.orderTotalPrice.text = "${formatter.format(orderPrice * orderCount)}KRW" + }) + + // spinner 아이템을 보여주는 view를 커스텀하기 위해서 adapter를 만들어준다 + val spinnerAdapter = ArrayAdapter( + requireContext(), + R.layout.spinner_item, + resources.getStringArray(R.array.orderCountSpinner) + ) + + binding.apply { + canOrderPrice.text = "${formatter.format(myViewModel.asset.value!!.krw)}KRW" + orderCountSpinner.adapter = spinnerAdapter + orderCountSpinner.setSelection(0, false) + orderCountSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + val krw = myViewModel.asset.value!!.krw + var canOrderCount: Double = 0.0 + when (position) { + 0 -> { + + } + // 최대 + 1 -> { + canOrderCount = krw / (this@FragmentBuy.orderPrice * 1.0005) + } + // 50% + 2 -> { + canOrderCount = (krw / 2) / (this@FragmentBuy.orderPrice * 1.0005) + } + // 25% + 3 -> { + canOrderCount = (krw / 4) / (this@FragmentBuy.orderPrice * 1.0005) + } + // 10% + 4 -> { + canOrderCount = (krw / 10) / (this@FragmentBuy.orderPrice * 1.0005) + } + else -> { + Log.e("FragmentBuy Spinner", "position is $position") + } + } + orderCount.setText(formatter2.format(canOrderCount)) + val totalPrice = canOrderCount * this@FragmentBuy.orderPrice + orderTotalPrice.text = "${formatter.format(totalPrice)}KRW" + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + + orderCount.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int + ) { + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + if (s.isNullOrEmpty()) { + this@FragmentBuy.orderCount = 0.0 + } else { + this@FragmentBuy.orderCount = s.toString().replace(",", "").toDouble() + if (this@FragmentBuy.orderCount < 0) { + this@FragmentBuy.orderCount = 0.0 + orderCount.setText("0") + } + } + val totalPrice = this@FragmentBuy.orderCount * this@FragmentBuy.orderPrice + orderTotalPrice.text = "${formatter.format(totalPrice)}KRW" + } + + override fun afterTextChanged(s: Editable?) {} + }) + orderCount.setOnFocusChangeListener(object : View.OnFocusChangeListener { + override fun onFocusChange(v: View?, hasFocus: Boolean) { + if (v != null) { + if (!hasFocus) { + val text = orderCount.text.toString() + if (text.isNotEmpty()) { + val number = text.replace(",", "").toDouble() + this@FragmentBuy.orderCount = if (number > 0.0) number else 0.0 + } else { + this@FragmentBuy.orderCount = 0.0 + orderCount.setText("0") + } + } + } + } + }) + // 주문 개수와 주문 가격 초기화 + resetBtn.setOnClickListener { + resetOrderTextView() + } + // 코인 매수 + buyBtn.setOnClickListener { + orderCount.clearFocus() + val nowOrderPrice = this@FragmentBuy.orderPrice + if (this@FragmentBuy.orderCount != 0.0 && this@FragmentBuy.orderCount * nowOrderPrice >= 5000.0) { + var coin: CoinInfo? = null + for (coinInfo in myViewModel.coinInfo.value!!) { + if (coinInfo.code == myViewModel.selectedCoin.value!!) { + coin = coinInfo + break + } + } + + val flag = myViewModel.asset.value!!.canBidCoin( + coin!!.code, + coin!!.name, + nowOrderPrice, + this@FragmentBuy.orderCount + ) + if (flag) { + val intent: Intent = Intent(context, PopupBuySellActivity::class.java) + intent.putExtra("type", 1) + intent.putExtra("code", coin!!.code) + intent.putExtra("name", coin!!.name) + intent.putExtra("unitPrice", nowOrderPrice) + intent.putExtra("count", this@FragmentBuy.orderCount) + getContent.launch(intent) + } else { + Toast.makeText(context, "주문가능 금액이 부족합니다.", Toast.LENGTH_SHORT).show() + } + } else { + Toast.makeText(context, "주문 가능한 최소 금액은 5,000KRW입니다.", Toast.LENGTH_SHORT) + .show() + } + } + } + } + + fun resetOrderTextView() { + this@FragmentBuy.orderCount = 0.0 + binding.orderCount.setText(formatter.format(orderCount)) + binding.orderPrice.text = + if (orderPrice > 100.0) + formatter.format(orderPrice) + else + formatter3.format(orderPrice) + binding.orderTotalPrice.text = "0KRW" + } + + fun getNowTime(): String { + val current = LocalDateTime.now() + // yyyy-MM-ddThh:mm:ss 형태로 날짜를 저장한다. + // https://developer.android.com/reference/java/time/format/DateTimeFormatter#ISO_LOCAL_DATE_TIME + val formatted = current.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + return formatted + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentChart.kt b/app/src/main/java/com/mobit/mobit/FragmentChart.kt new file mode 100644 index 0000000..c587301 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentChart.kt @@ -0,0 +1,595 @@ +package com.mobit.mobit + +import android.graphics.Color +import android.graphics.Matrix +import android.graphics.Paint +import android.os.Bundle +import android.os.Handler +import android.os.Message +import android.util.Log +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.RadioGroup +import android.widget.Toast +import androidx.appcompat.widget.PopupMenu +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.charts.CandleStickChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.github.mikephil.charting.data.* +import com.github.mikephil.charting.listener.ChartTouchListener +import com.github.mikephil.charting.listener.OnChartGestureListener +import com.mobit.mobit.data.Candle +import com.mobit.mobit.data.CoinInfo +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.databinding.FragmentChartBinding +import com.mobit.mobit.network.UpbitAPICaller +import java.text.DecimalFormat +import kotlin.math.abs + +/* +코인 차트 기능이 구현될 Fragment 입니다. + +차트 기능 구현할 때 사용할 라이브러리 +-> https://github.com/PhilJay/MPAndroidChart + */ +class FragmentChart : Fragment() { + + companion object { + const val UNIT_MIN_1: Int = 0 + const val UNIT_MIN_3: Int = 1 + const val UNIT_MIN_5: Int = 2 + const val UNIT_MIN_10: Int = 3 + const val UNIT_MIN_30: Int = 4 + const val UNIT_MIN_60: Int = 5 + const val UNIT_MIN_240: Int = 6 + const val UNIT_DAY: Int = 7 + const val UNIT_WEEK: Int = 8 + const val UNIT_MONTH: Int = 9 + } + + // UI 변수 시작 + lateinit var binding: FragmentChartBinding + var minuteBtnFlag: Boolean = true + // UI 변수 끝 + + // 차트 관련 변수 시작 + var unitFlag: Int = UNIT_MIN_1 + var unitMinFlag: Int = UNIT_MIN_1 + // 차트 관련 변수 끝 + + val upbitAPICaller: UpbitAPICaller = UpbitAPICaller() + val upbitCandleHandler: UpbitCandleHandler = UpbitCandleHandler() + lateinit var upbitCandleThread: UpbitCandleThread + + val myViewModel: MyViewModel by activityViewModels() + + var selectedCoin: CoinInfo? = null + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentChartBinding.inflate(layoutInflater) + + upbitCandleThread = UpbitCandleThread() + upbitCandleThread.start() + + initChart() + init() + + return binding.root + } + + override fun onResume() { + super.onResume() + val thread = object : Thread() { + override fun run() { + if (!upbitCandleThread.isAlive) { + upbitCandleThread = UpbitCandleThread() + upbitCandleThread.start() + } + } + } + thread.start() + } + + override fun onStop() { + super.onStop() + upbitCandleThread.threadStop(true) + } + + fun initChart() { + binding.apply { + priceChart.description.isEnabled = false + priceChart.setMaxVisibleValueCount(200) + priceChart.setPinchZoom(false) + priceChart.setDrawGridBackground(false) + priceChart.xAxis.apply { + textColor = Color.TRANSPARENT + position = XAxis.XAxisPosition.BOTTOM + this.setDrawGridLines(true) + } + priceChart.axisLeft.apply { + textColor = Color.WHITE + isEnabled = false + } + priceChart.axisRight.apply { + setLabelCount(7, false) + textColor = Color.WHITE + setDrawGridLines(true) + setDrawAxisLine(false) + } + priceChart.legend.isEnabled = false + + transactionChart.description.isEnabled = false + transactionChart.setMaxVisibleValueCount(200) + transactionChart.setPinchZoom(false) + transactionChart.setDrawGridBackground(false) + transactionChart.xAxis.apply { + textColor = Color.TRANSPARENT + position = XAxis.XAxisPosition.BOTTOM + this.setDrawGridLines(true) + } + transactionChart.axisLeft.apply { + textColor = Color.WHITE + isEnabled = false + } + transactionChart.axisRight.apply { + setLabelCount(7, false) + textColor = Color.WHITE + setDrawGridLines(true) + setDrawAxisLine(false) + } + transactionChart.legend.isEnabled = false + + priceChart.onChartGestureListener = object : OnChartGestureListener { + override fun onChartGestureStart( + me: MotionEvent?, + lastPerformedGesture: ChartTouchListener.ChartGesture? + ) { + } + + override fun onChartGestureEnd( + me: MotionEvent?, + lastPerformedGesture: ChartTouchListener.ChartGesture? + ) { + } + + override fun onChartLongPressed(me: MotionEvent?) {} + + override fun onChartDoubleTapped(me: MotionEvent?) {} + + override fun onChartSingleTapped(me: MotionEvent?) {} + + override fun onChartFling( + me1: MotionEvent?, + me2: MotionEvent?, + velocityX: Float, + velocityY: Float + ) { + } + + override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) { + syncCharts(priceChart, transactionChart) + } + + override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) { + syncCharts(priceChart, transactionChart) + } + } + transactionChart.onChartGestureListener = object : OnChartGestureListener { + override fun onChartGestureStart( + me: MotionEvent?, + lastPerformedGesture: ChartTouchListener.ChartGesture? + ) { + } + + override fun onChartGestureEnd( + me: MotionEvent?, + lastPerformedGesture: ChartTouchListener.ChartGesture? + ) { + } + + override fun onChartLongPressed(me: MotionEvent?) {} + + override fun onChartDoubleTapped(me: MotionEvent?) {} + + override fun onChartSingleTapped(me: MotionEvent?) {} + + override fun onChartFling( + me1: MotionEvent?, + me2: MotionEvent?, + velocityX: Float, + velocityY: Float + ) { + } + + override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) { + syncCharts(transactionChart, priceChart) + } + + override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) { + syncCharts(transactionChart, priceChart) + } + } + } + } + + fun init() { + myViewModel.coinInfo.observe(viewLifecycleOwner, Observer { + for (coinInfo in myViewModel.coinInfo.value!!) { + if (coinInfo.code == myViewModel.selectedCoin.value!!) { + selectedCoin = coinInfo + break + } + } + binding.apply { + if (selectedCoin != null) { + val formatter = DecimalFormat("###,###") + val changeFormatter = DecimalFormat("###,###.##") + coinName.text = "${selectedCoin!!.name}(${selectedCoin!!.code.split('-')[1]})" + coinPrice.text = + if (selectedCoin!!.price.realTimePrice > 100.0) + formatter.format(selectedCoin!!.price.realTimePrice) + else + changeFormatter.format(selectedCoin!!.price.realTimePrice) + coinRate.text = + changeFormatter.format(selectedCoin!!.price.changeRate * 100) + "%" + coinDiff.text = when (selectedCoin!!.price.change) { + "EVEN" -> "" + "RISE" -> "▲ " + "FALL" -> "▼ " + else -> "" + } + changeFormatter.format(abs(selectedCoin!!.price.changePrice)) + + setTextViewColor(selectedCoin!!) + } + } + }) + + binding.apply { + radioGroup.setOnCheckedChangeListener(object : RadioGroup.OnCheckedChangeListener { + override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) { + when (checkedId) { + R.id.minuteBtn -> { + unitFlag = unitMinFlag + } + R.id.dayBtn -> { + minuteBtnFlag = false + unitFlag = UNIT_DAY + } + R.id.weekBtn -> { + minuteBtnFlag = false + unitFlag = UNIT_WEEK + } + R.id.monthBtn -> { + minuteBtnFlag = false + unitFlag = UNIT_MONTH + } + } + } + }) + minuteBtn.setOnClickListener { view -> + if (minuteBtnFlag && unitFlag in UNIT_MIN_1..UNIT_MIN_240) { + setUnitMinBtn(view) + } else if (unitFlag in UNIT_MIN_1..UNIT_MIN_240) { + minuteBtnFlag = true + } + } + + for (coinInfo in myViewModel.coinInfo.value!!) { + if (coinInfo.code == myViewModel.selectedCoin.value!!) { + selectedCoin = coinInfo + break + } + } + // 코인이 관심목록에 등록되어 있는 경우에는 ImageButton을 채워진 별로 변경해야 한다. + if (myViewModel.favoriteCoinInfo.value!!.contains(selectedCoin)) { + favoriteBtn.setImageResource(R.drawable.ic_round_star_24) + } + favoriteBtn.setOnClickListener(object : View.OnClickListener { + override fun onClick(v: View?) { + // 즐겨찾기에 이미 추가되어 있는 경우 + if (myViewModel.favoriteCoinInfo.value!!.contains(selectedCoin)) { + if (myViewModel.removeFavoriteCoinInfo(selectedCoin!!)) { + val thread = object : Thread() { + override fun run() { + val flag = + myViewModel.myDBHelper!!.deleteFavorite(selectedCoin!!.code) + Log.e("favorite delete", flag.toString()) + } + } + thread.start() + favoriteBtn.setImageResource(R.drawable.ic_round_star_border_24) + Toast.makeText(context, "관심코인에서 삭제되었습니다.", Toast.LENGTH_SHORT).show() + } + } + // 즐겨찾기에 추가되어 있지 않은 경우 + else { + if (myViewModel.addFavoriteCoinInfo(selectedCoin!!)) { + val thread = object : Thread() { + override fun run() { + val flag = + myViewModel.myDBHelper!!.insertFavoirte(selectedCoin!!.code) + Log.e("favorite insert", flag.toString()) + } + } + thread.start() + favoriteBtn.setImageResource(R.drawable.ic_round_star_24) + Toast.makeText(context, "관심코인으로 등록되었습니다.", Toast.LENGTH_SHORT).show() + } + } + } + }) + } + } + + fun setUnitMinBtn(view: View) { + val popupMenu: PopupMenu = PopupMenu(requireContext(), view) + popupMenu.menuInflater.inflate( + R.menu.menu_chart_unit, + popupMenu.menu + ) + popupMenu.setOnMenuItemClickListener { item -> + if (item != null) { + when (item.itemId) { + R.id.minute_1 -> { + if (unitFlag != UNIT_MIN_1) { + unitFlag = UNIT_MIN_1 + unitMinFlag = UNIT_MIN_1 + binding.minuteBtn.text = getString(R.string.chart_unit_minute_1) + } + } + R.id.minute_3 -> { + if (unitFlag != UNIT_MIN_3) { + unitFlag = UNIT_MIN_3 + unitMinFlag = UNIT_MIN_3 + binding.minuteBtn.text = getString(R.string.chart_unit_minute_3) + } + } + R.id.minute_5 -> { + if (unitFlag != UNIT_MIN_5) { + unitFlag = UNIT_MIN_5 + unitMinFlag = UNIT_MIN_5 + binding.minuteBtn.text = getString(R.string.chart_unit_minute_5) + } + } + R.id.minute_10 -> { + if (unitFlag != UNIT_MIN_10) { + unitFlag = UNIT_MIN_10 + unitMinFlag = UNIT_MIN_10 + binding.minuteBtn.text = getString(R.string.chart_unit_minute_10) + } + } + R.id.minute_30 -> { + if (unitFlag != UNIT_MIN_30) { + unitFlag = UNIT_MIN_30 + unitMinFlag = UNIT_MIN_30 + binding.minuteBtn.text = getString(R.string.chart_unit_minute_30) + } + } + R.id.minute_60 -> { + if (unitFlag != UNIT_MIN_60) { + unitFlag = UNIT_MIN_60 + unitMinFlag = UNIT_MIN_60 + binding.minuteBtn.text = getString(R.string.chart_unit_minute_60) + } + } + R.id.minute_240 -> { + if (unitFlag != UNIT_MIN_240) { + unitFlag = UNIT_MIN_240 + unitMinFlag = UNIT_MIN_240 + binding.minuteBtn.text = getString(R.string.chart_unit_minute_240) + } + } + } + } + true + } + popupMenu.show() + } + + fun setTextViewColor(coinInfo: CoinInfo) { + binding.apply { + if (coinInfo.price.changeRate > 0) { + coinPrice.setTextColor(Color.parseColor("#bd4e3a")) + coinRate.setTextColor(Color.parseColor("#bd4e3a")) + coinDiff.setTextColor(Color.parseColor("#bd4e3a")) + } else if (coinInfo.price.changeRate < 0) { + coinPrice.setTextColor(Color.parseColor("#135fc1")) + coinRate.setTextColor(Color.parseColor("#135fc1")) + coinDiff.setTextColor(Color.parseColor("#135fc1")) + } else { + coinPrice.setTextColor(Color.parseColor("#FFFFFF")) + coinRate.setTextColor(Color.parseColor("#FFFFFF")) + coinDiff.setTextColor(Color.parseColor("#FFFFFF")) + } + } + } + + fun syncCharts(mainChart: CandleStickChart, otherChart: BarChart) { + val mainMatrix: Matrix + val mainVals = FloatArray(9) + val otherMatrix: Matrix + val otherVals = FloatArray(9) + mainMatrix = mainChart.viewPortHandler.matrixTouch + mainMatrix.getValues(mainVals) + + otherMatrix = otherChart.viewPortHandler.matrixTouch + otherMatrix.getValues(otherVals) + otherVals[Matrix.MSCALE_X] = mainVals[Matrix.MSCALE_X] + otherVals[Matrix.MTRANS_X] = mainVals[Matrix.MTRANS_X] + otherVals[Matrix.MSKEW_X] = mainVals[Matrix.MSKEW_X] + otherMatrix.setValues(otherVals) + otherChart.viewPortHandler.refresh(otherMatrix, otherChart, true) + } + + fun syncCharts(mainChart: BarChart, otherChart: CandleStickChart) { + val mainMatrix: Matrix + val mainVals = FloatArray(9) + val otherMatrix: Matrix + val otherVals = FloatArray(9) + mainMatrix = mainChart.viewPortHandler.matrixTouch + mainMatrix.getValues(mainVals) + + otherMatrix = otherChart.viewPortHandler.matrixTouch + otherMatrix.getValues(otherVals) + otherVals[Matrix.MSCALE_X] = mainVals[Matrix.MSCALE_X] + otherVals[Matrix.MTRANS_X] = mainVals[Matrix.MTRANS_X] + otherVals[Matrix.MSKEW_X] = mainVals[Matrix.MSKEW_X] + otherMatrix.setValues(otherVals) + otherChart.viewPortHandler.refresh(otherMatrix, otherChart, true) + } + + inner class UpbitCandleHandler : Handler() { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + + val bundle: Bundle = msg.data + if (!bundle.isEmpty) { + val flag = bundle.getInt("unitFlag") + val candles = bundle.getSerializable("candles") as ArrayList + val entries1 = ArrayList() + val entries2 = ArrayList() + val barColor = ArrayList() + for (candle in candles) { + entries1.add( + CandleEntry( + candle.createdAt.toFloat(), + candle.shadowHigh, + candle.shadowLow, + candle.open, + candle.close + ) + ) + entries2.add(BarEntry(candle.createdAt.toFloat(), candle.totalTradeVolume)) + if (candle.close >= candle.open) { + barColor.add(Color.rgb(200, 74, 49)) + } else { + barColor.add(Color.rgb(18, 98, 197)) + } + } + + val dataSet1 = CandleDataSet(entries1, "").apply { + axisDependency = YAxis.AxisDependency.LEFT + // 심지 부분 설정 + shadowColor = Color.LTGRAY + shadowWidth = 0.7F + // 음봉 + decreasingColor = Color.rgb(18, 98, 197) + decreasingPaintStyle = Paint.Style.FILL + // 양봉 + increasingColor = Color.rgb(200, 74, 49) + increasingPaintStyle = Paint.Style.FILL + + neutralColor = Color.rgb(6, 18, 34) + setDrawValues(false) + highLightColor = Color.TRANSPARENT + } + val dataSet2 = BarDataSet(entries2, "").apply { + colors = barColor + setDrawValues(false) + highLightColor = Color.TRANSPARENT + } + binding.priceChart.apply { + this.data = CandleData(dataSet1) + invalidate() + } + binding.transactionChart.apply { + this.data = BarData(dataSet2) + invalidate() + } + } + } + } + + inner class UpbitCandleThread : Thread() { + + var stopFlag = false + + override fun run() { + while (!stopFlag) { + val message = upbitCandleHandler.obtainMessage() + val bundle: Bundle = Bundle() + + when (unitFlag) { + UNIT_MIN_1 -> { + val candles = + upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 1) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + UNIT_MIN_3 -> { + val candles = + upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 3) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + UNIT_MIN_5 -> { + val candles = + upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 5) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + UNIT_MIN_10 -> { + val candles = + upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 10) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + UNIT_MIN_30 -> { + val candles = + upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 30) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + UNIT_MIN_60 -> { + val candles = + upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 60) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + UNIT_MIN_240 -> { + val candles = + upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 240) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + UNIT_DAY -> { + val candles = + upbitAPICaller.getCandleDay(myViewModel.selectedCoin.value!!) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + UNIT_WEEK -> { + val candles = + upbitAPICaller.getCandleWeek(myViewModel.selectedCoin.value!!) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + UNIT_MONTH -> { + val candles = + upbitAPICaller.getCandleMonth(myViewModel.selectedCoin.value!!) + bundle.putInt("unitFlag", unitFlag) + bundle.putSerializable("candles", candles) + } + } + + message.data = bundle + upbitCandleHandler.sendMessage(message) + sleep(300) + } + } + + + fun threadStop(flag: Boolean) { + this.stopFlag = flag + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentCoinInfo.kt b/app/src/main/java/com/mobit/mobit/FragmentCoinInfo.kt new file mode 100644 index 0000000..9d29c81 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentCoinInfo.kt @@ -0,0 +1,58 @@ +package com.mobit.mobit + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import com.mobit.mobit.data.CoinInfo +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.databinding.FragmentCoinInfoBinding + +class FragmentCoinInfo : Fragment() { + + // UI 변수 시작 + lateinit var binding: FragmentCoinInfoBinding + // UI 변수 끝 + + val myViewModel: MyViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentCoinInfoBinding.inflate(layoutInflater) + + init() + + return binding.root + } + + fun init() { + binding.coinInfo.text = when (myViewModel.selectedCoin.value!!) { + CoinInfo.BTC_CODE -> getString(R.string.BTC) + CoinInfo.ETH_CODE -> getString(R.string.ETH) + CoinInfo.ADA_CODE -> getString(R.string.ADA) + CoinInfo.DOGE_CODE -> getString(R.string.DOGE) + CoinInfo.XRP_CODE -> getString(R.string.XRP) + CoinInfo.DOT_CODE -> getString(R.string.DOT) + CoinInfo.BCH_CODE -> getString(R.string.BCH) + CoinInfo.LTC_CODE -> getString(R.string.LTC) + CoinInfo.LINK_CODE -> getString(R.string.LINK) + CoinInfo.ETC_CODE -> getString(R.string.ETC) + CoinInfo.THETA_CODE -> getString(R.string.THETA) + CoinInfo.XLM_CODE -> getString(R.string.XLM) + CoinInfo.VET_CODE -> getString(R.string.VET) + CoinInfo.EOS_CODE -> getString(R.string.EOS) + CoinInfo.TRX_CODE -> getString(R.string.TRX) + CoinInfo.NEO_CODE -> getString(R.string.NEO) + CoinInfo.IOTA_CODE -> getString(R.string.IOTA) + CoinInfo.ATOM_CODE -> getString(R.string.ATOM) + CoinInfo.BSV_CODE -> getString(R.string.BSV) + CoinInfo.BTT_CODE -> getString(R.string.BTT) + else -> "" + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentCoinList.kt b/app/src/main/java/com/mobit/mobit/FragmentCoinList.kt new file mode 100644 index 0000000..d12246d --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentCoinList.kt @@ -0,0 +1,143 @@ +package com.mobit.mobit + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.RadioGroup +import androidx.appcompat.widget.SearchView +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import com.mobit.mobit.adapter.FragmentCoinListAdapter +import com.mobit.mobit.data.CoinInfo +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.databinding.FragmentCoinListBinding + +/* +가상화폐 목록과 정보 확인 기능이 구현될 Fragment 입니다. +*/ +class FragmentCoinList : Fragment() { + + // UI 변수 시작 + lateinit var binding: FragmentCoinListBinding + // UI 변수 끝 + + val myViewModel: MyViewModel by activityViewModels() + + // true -> 전체 코인 리스트를 보여주고 있는 상황 + // false -> 관심 코인 리스트를 보여주고 있는 상황 + var adapterState: Boolean = true + + val coinInfo: ArrayList = ArrayList() // 전체 코인 리스트 + lateinit var adapter: FragmentCoinListAdapter // 전체 코인 리스트 adapter + + val favoriteCoinInfo: ArrayList = ArrayList() // 관심 코인 리스트 + lateinit var favoriteAdapter: FragmentCoinListAdapter // 관심 코인 리스트 adapter + + var listener: OnFragmentInteraction? = null // MainActivity와 통신할 때 사용되는 interface + + interface OnFragmentInteraction { + fun showTransaction() + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentCoinListBinding.inflate(layoutInflater) + + init() + + return binding.root + } + + fun init() { + myViewModel.coinInfo.observe(viewLifecycleOwner, androidx.lifecycle.Observer { +// coinInfo.clear() +// coinInfo.addAll(myViewModel.coinInfo.value!!) + for (index in it.indices) { + if (coinInfo.size > index) + coinInfo[index] = it[index] + else + coinInfo.add(it[index]) + } + adapter.notifyDataSetChanged() + }) + + myViewModel.favoriteCoinInfo.observe(viewLifecycleOwner, Observer { + favoriteCoinInfo.clear() + favoriteCoinInfo.addAll(myViewModel.favoriteCoinInfo.value!!) + favoriteAdapter.notifyDataSetChanged() + }) + + // 일반 코인 목록을 보여주는 adapter + adapter = FragmentCoinListAdapter(coinInfo, coinInfo) + adapter.listener = object : FragmentCoinListAdapter.OnItemClickListener { + override fun onItemClicked(view: View, coinInfo: CoinInfo) { + myViewModel.setSelectedCoin(coinInfo.code) + listener?.showTransaction() + Log.i("Clicked Coin", coinInfo.code) + } + } + // 관심 목록을 보여주는 adapter + favoriteAdapter = FragmentCoinListAdapter(favoriteCoinInfo, favoriteCoinInfo) + favoriteAdapter.listener = object : FragmentCoinListAdapter.OnItemClickListener { + override fun onItemClicked(view: View, coinInfo: CoinInfo) { + myViewModel.setSelectedCoin(coinInfo.code) + listener?.showTransaction() + Log.i("Clicked Coin", coinInfo.code) + } + } + + binding.apply { + recyclerView.layoutManager = + LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + recyclerView.adapter = adapter + + radioGroup.setOnCheckedChangeListener(object : RadioGroup.OnCheckedChangeListener { + override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) { + when (checkedId) { + R.id.krwBtn -> { + adapterState = true + recyclerView.adapter = adapter + adapter.notifyDataSetChanged() + } + R.id.favoriteBtn -> { + adapterState = false + recyclerView.adapter = favoriteAdapter + favoriteAdapter.notifyDataSetChanged() + } + else -> { + Log.e("FragmentCoinList", "Radio Group Error") + } + } + } + }) + + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + if (adapterState) { + adapter.filter.filter(query) + } else { + favoriteAdapter.filter.filter(query) + } + return true + } + + override fun onQueryTextChange(newText: String?): Boolean { + if (adapterState) { + adapter.filter.filter(newText) + } else { + favoriteAdapter.filter.filter(newText) + } + return true + } + + }) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentInvestment.kt b/app/src/main/java/com/mobit/mobit/FragmentInvestment.kt new file mode 100644 index 0000000..a0966c5 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentInvestment.kt @@ -0,0 +1,49 @@ +package com.mobit.mobit + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import com.google.android.material.tabs.TabLayoutMediator +import com.mobit.mobit.adapter.InvestmentStateAdapter +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.databinding.FragmentInvestmentBinding + +/* +투자내역 기능이 구현될 Fragment 입니다. +*/ +class FragmentInvestment : Fragment() { + + // UI 변수 시작 + lateinit var binding: FragmentInvestmentBinding + // UI 변수 끝 + + val myViewModel: MyViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentInvestmentBinding.inflate(layoutInflater) + + init() + + return binding.root + } + + fun init() { + binding.apply { + viewPager.adapter = InvestmentStateAdapter(requireActivity()) + TabLayoutMediator(tabLayout, viewPager) { tab, position -> + tab.text = when (position) { + 0 -> getString(R.string.investment_tab1) + 1 -> getString(R.string.investment_tab2) + else -> "" + } + }.attach() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentRecord.kt b/app/src/main/java/com/mobit/mobit/FragmentRecord.kt new file mode 100644 index 0000000..358ed21 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentRecord.kt @@ -0,0 +1,79 @@ +package com.mobit.mobit + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.SearchView +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import com.mobit.mobit.adapter.FragmentRecordAdapter +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.data.Transaction +import com.mobit.mobit.databinding.FragmentRecordBinding + +/* +거래내역 기능이 구현될 Fragment 입니다. +*/ +class FragmentRecord : Fragment() { + + // UI 변수 시작 + lateinit var binding: FragmentRecordBinding + // UI 변수 끝 + + val myViewModel: MyViewModel by activityViewModels() + + lateinit var adapter: FragmentRecordAdapter + val transactions: ArrayList = ArrayList() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentRecordBinding.inflate(layoutInflater) + + init() + + return binding.root + } + + fun init() { + myViewModel.transaction.observe(viewLifecycleOwner, Observer { transaction -> + transactions.clear() + transactions.addAll(transaction) + adapter.notifyDataSetChanged() + + binding.apply { + if (transactions.isEmpty()) { + recyclerView.visibility = View.GONE + noRecordView.visibility = View.VISIBLE + } else { + recyclerView.visibility = View.VISIBLE + noRecordView.visibility = View.GONE + } + } + }) + + adapter = FragmentRecordAdapter(transactions, transactions) + binding.apply { + recyclerView.layoutManager = + LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + recyclerView.adapter = adapter + + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + adapter.filter.filter(query) + return true + } + + override fun onQueryTextChange(newText: String?): Boolean { + adapter.filter.filter(newText) + return true + } + }) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentSell.kt b/app/src/main/java/com/mobit/mobit/FragmentSell.kt new file mode 100644 index 0000000..4816b15 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentSell.kt @@ -0,0 +1,323 @@ +package com.mobit.mobit + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import com.mobit.mobit.data.CoinAsset +import com.mobit.mobit.data.CoinInfo +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.data.Transaction +import com.mobit.mobit.databinding.FragmentSellBinding +import java.text.DecimalFormat +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +/* +코인 매도 기능이 구현될 Fragment 입니다. +*/ +class FragmentSell : Fragment() { + + lateinit var getContent: ActivityResultLauncher + + // UI 변수 시작 + lateinit var binding: FragmentSellBinding + // UI 변수 끝 + + val myViewModel: MyViewModel by activityViewModels() + + var selectedCoin: CoinAsset? = null + val formatter = DecimalFormat("###,###") + val formatter2 = DecimalFormat("###,###.####") + val formatter3 = DecimalFormat("###,###.##") + var orderCount: Double = 0.0 + var orderPrice: Double = 0.0 + + var coinAsset: CoinAsset? = null + + var listener: OnPopupActivityControl? = null + + interface OnPopupActivityControl { + fun onPopupActivityShow() + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentSellBinding.inflate(layoutInflater) + getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + binding.orderCount.clearFocus() + when (it.resultCode) { + Activity.RESULT_OK -> { + val code = it.data!!.getStringExtra("code") + val name = it.data!!.getStringExtra("name") + val price: Double = orderPrice * orderCount + val fee: Double = price * 0.0005 + val time: String = getNowTime() + val transaction = Transaction( + code, + name, + time, + Transaction.ASK, + orderCount, + orderPrice, + price, + fee, + price - fee + ) + coinAsset = myViewModel.askCoin( + code, + orderPrice, + orderCount + ) + myViewModel.addTransaction(transaction) + val thread = object : Thread() { + override fun run() { + myViewModel.myDBHelper!!.setKRW(myViewModel.asset.value!!.krw) + myViewModel.myDBHelper!!.insertTransaction(transaction) + + if (myViewModel.asset.value!!.coins.contains(coinAsset!!)) { + myViewModel.myDBHelper!!.updateCoinAsset(coinAsset!!) + } else { + myViewModel.myDBHelper!!.deleteCoinAsset(coinAsset!!) + } + } + } + thread.start() + binding.canOrderCoin.text = "${formatter.format(coinAsset!!.number)} ${ + myViewModel.selectedCoin.value!!.split('-')[1] + }" + resetOrderTextView() + Toast.makeText(context, "매도주문이 정상 처리되었습니다.", Toast.LENGTH_SHORT).show() + } + Activity.RESULT_CANCELED -> { + Log.i("resultCode", "RESULT_CANCELED") + } + } + } + + init() + + return binding.root + } + + override fun onResume() { + super.onResume() + binding.apply { + orderCount.setText("0") + orderCountSpinner.setSelection(0) + } + } + + fun init() { + myViewModel.coinInfo.observe(viewLifecycleOwner, Observer { + for (coinInfo in myViewModel.coinInfo.value!!) { + if (coinInfo.code == myViewModel.selectedCoin.value!!) { + orderPrice = coinInfo.price.realTimePrice + break + } + } + binding.orderPrice.text = + if (orderPrice > 100.0) + formatter.format(orderPrice) + else + formatter3.format(orderPrice) + binding.orderTotalPrice.text = "${formatter.format(orderPrice * orderCount)}KRW" + }) + myViewModel.selectedCoin.observe(viewLifecycleOwner, Observer { + var check = false + for (coinAsset in myViewModel.asset.value!!.coins) { + if (coinAsset.code == myViewModel.selectedCoin.value!!) { + check = true + selectedCoin = coinAsset + binding.canOrderCoin.text = + "${formatter2.format(coinAsset.number)} ${coinAsset.code.split('-')[1]}" + break + } + } + if (!check) { + binding.canOrderCoin.text = "0 ${myViewModel.selectedCoin.value!!.split('-')[1]}" + } + }) + + // spinner 아이템을 보여주는 view를 커스텀하기 위해서 adapter를 만들어준다 + val spinnerAdapter = ArrayAdapter( + requireContext(), + R.layout.spinner_item, + resources.getStringArray(R.array.orderCountSpinner) + ) + + binding.apply { + for (coinAsset in myViewModel.asset.value!!.coins) { + if (coinAsset.code == myViewModel.selectedCoin.value!!) { + selectedCoin = coinAsset + canOrderCoin.text = + "${formatter.format(coinAsset.number)} ${coinAsset.code.split('-')[1]}" + break + } + } + if (selectedCoin == null) { + canOrderCoin.text = "0 ${myViewModel.selectedCoin.value!!.split('-')[1]}" + } + orderCountSpinner.adapter = spinnerAdapter + orderCountSpinner.setSelection(0, false) + orderCountSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + var canOrderCount: Double = 0.0 + // 선택된 코인이 매수한 적 없는 코인인 경우, + // selectedCoin은 null이 되므로 조건문을 추가해야 한다. + if (selectedCoin != null) { + when (position) { + 0 -> { + + } + // 최대 + 1 -> { + canOrderCount = selectedCoin!!.number + } + // 50% + 2 -> { + canOrderCount = selectedCoin!!.number / 2 + } + // 25% + 3 -> { + canOrderCount = selectedCoin!!.number / 4 + } + // 10% + 4 -> { + canOrderCount = selectedCoin!!.number / 10 + } + else -> { + Log.e("FragmentSell Spinner", "position is $position") + } + } + } + orderCount.setText(formatter2.format(canOrderCount)) + val totalPrice = canOrderCount * this@FragmentSell.orderPrice + orderTotalPrice.text = "${formatter.format(totalPrice)}KRW" + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + + orderCount.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int + ) { + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + if (s.isNullOrEmpty()) { + this@FragmentSell.orderCount = 0.0 + } else { + this@FragmentSell.orderCount = s.toString().replace(",", "").toDouble() + if (this@FragmentSell.orderCount < 0) { + this@FragmentSell.orderCount = 0.0 + orderCount.setText("0") + } + } + val totalPrice = this@FragmentSell.orderCount * this@FragmentSell.orderPrice + orderTotalPrice.text = "${formatter.format(totalPrice)}KRW" + } + + override fun afterTextChanged(s: Editable?) {} + }) + orderCount.setOnFocusChangeListener(object : View.OnFocusChangeListener { + override fun onFocusChange(v: View?, hasFocus: Boolean) { + if (v != null) { + if (!hasFocus) { + val text = orderCount.text.toString() + if (text.isNotEmpty()) { + val number = text.replace(",", "").toDouble() + this@FragmentSell.orderCount = if (number > 0.0) number else 0.0 + } else { + this@FragmentSell.orderCount = 0.0 + orderCount.setText("0") + } + } + } + } + }) + // 주문 개수와 주문 가격 초기화 + resetBtn.setOnClickListener { + resetOrderTextView() + } + // 코인 매도 + sellBtn.setOnClickListener { + orderCount.clearFocus() + val nowOrderPrice = this@FragmentSell.orderPrice + if (this@FragmentSell.orderCount != 0.0 && this@FragmentSell.orderCount * nowOrderPrice >= 5000.0) { + var coin: CoinInfo? = null + for (coinInfo in myViewModel.coinInfo.value!!) { + if (coinInfo.code == myViewModel.selectedCoin.value!!) { + coin = coinInfo + break + } + } + + val flag = myViewModel.asset.value!!.canAskCoin( + coin!!.code, + nowOrderPrice, + this@FragmentSell.orderCount + ) + if (flag) { + listener?.onPopupActivityShow() + val intent: Intent = Intent(context, PopupBuySellActivity::class.java) + intent.putExtra("type", 2) + intent.putExtra("code", coin!!.code) + intent.putExtra("name", coin!!.name) + intent.putExtra("unitPrice", nowOrderPrice) + intent.putExtra("count", this@FragmentSell.orderCount) + getContent.launch(intent) + } else { + Toast.makeText(context, "주문가능 코인이 부족합니다.", Toast.LENGTH_SHORT).show() + } + } else { + Toast.makeText(context, "주문 가능한 최소 금액은 5,000KRW입니다.", Toast.LENGTH_SHORT) + .show() + } + } + } + } + + fun resetOrderTextView() { + this@FragmentSell.orderCount = 0.0 + binding.orderCount.setText(formatter.format(orderCount)) + binding.orderPrice.text = + if (orderPrice > 100.0) + formatter.format(orderPrice) + else + formatter3.format(orderPrice) + binding.orderTotalPrice.text = "0KRW" + } + + fun getNowTime(): String { + val current = LocalDateTime.now() + // yyyy-MM-ddThh:mm:ss 형태로 날짜를 저장한다. + // https://developer.android.com/reference/java/time/format/DateTimeFormatter#ISO_LOCAL_DATE_TIME + val formatted = current.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + return formatted + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentSetting.kt b/app/src/main/java/com/mobit/mobit/FragmentSetting.kt new file mode 100644 index 0000000..b5b3826 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentSetting.kt @@ -0,0 +1,94 @@ +package com.mobit.mobit + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.databinding.FragmentSettingBinding + +/* +설정 기능이 구현될 Fragment 입니다. + */ +class FragmentSetting : Fragment() { + + lateinit var binding: FragmentSettingBinding + + val myViewModel: MyViewModel by activityViewModels() + val myProgressBar: MyProgressBar = MyProgressBar() + + lateinit var getContent: ActivityResultLauncher + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentSettingBinding.inflate(layoutInflater) + getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + myProgressBar.progressON(activity, "초기화 하는중..") + val thread = object : Thread() { + override fun run() { + myViewModel.myDBHelper!!.clearDB() + } + } + thread.start() + try { + if (thread.isAlive) { + thread.join() + } + } catch (e: InterruptedException) { + Log.e("Setting Reset Error", e.toString()) + } + myProgressBar.progressOFF() + val intent = Intent(context, MainActivity::class.java) + startActivity(intent) + requireActivity().finish() + } + } + + init() + + return binding.root + } + + fun init() { + binding.apply { + guideTitle.setOnClickListener { + when (guideLayout.visibility) { + View.VISIBLE -> { + guideLayout.visibility = View.GONE + guideTitle.setCompoundDrawablesWithIntrinsicBounds( + 0, + 0, + R.drawable.ic_baseline_keyboard_arrow_down_24, + 0 + ) + } + View.GONE -> { + guideLayout.visibility = View.VISIBLE + guideTitle.setCompoundDrawablesWithIntrinsicBounds( + 0, + 0, + R.drawable.ic_baseline_keyboard_arrow_up_24, + 0 + ) + } + } + } + + resetView.setOnClickListener { + val intent = Intent(context, PopupResetConfirmActivity::class.java) + getContent.launch(intent) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/FragmentTransaction.kt b/app/src/main/java/com/mobit/mobit/FragmentTransaction.kt new file mode 100644 index 0000000..fbddf77 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/FragmentTransaction.kt @@ -0,0 +1,236 @@ +package com.mobit.mobit + +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.RadioGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import com.mobit.mobit.adapter.FragmentTransactionAdapter +import com.mobit.mobit.data.CoinInfo +import com.mobit.mobit.data.MyViewModel +import com.mobit.mobit.data.OrderBook +import com.mobit.mobit.databinding.FragmentTransactionBinding +import java.text.DecimalFormat +import kotlin.math.abs + +/* +코인의 매수/매도 기능이 구현될 Fragment 입니다. + */ +class FragmentTransaction : Fragment() { + + // Fragment 변수 시작 + val fragmentBuy: Fragment = FragmentBuy() + val fragmentSell: Fragment = FragmentSell() + val fragmentCoinInfo: Fragment = FragmentCoinInfo() + // Fragment 변수 끝 + + // UI 변수 시작 + lateinit var binding: FragmentTransactionBinding + // UI 변수 끝 + + val myViewModel: MyViewModel by activityViewModels() + + var doScrollVertically = true + lateinit var adapter: FragmentTransactionAdapter // 호가 정보 리스트 adapter + var selectedCoin: CoinInfo? = null // CoinList에서 사용자가 선택한 코인의 정보 + val orderBook: ArrayList = + ArrayList() // selectedCoin의 호가 정보를 갖는다. (내림차순으로 정렬되어 있음) + // [0, 14] -> 매도 호가 + // [15, 29] -> 매수 호가 + + var resumeFlag: Boolean = true + + var listener: OnFragmentInteraction? = null // MainActivity와 통신할 때 사용되는 interface + + interface OnFragmentInteraction { + fun orderBookThreadStop() + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentTransactionBinding.inflate(layoutInflater) + + init() + + return binding.root + } + + override fun onResume() { + super.onResume() + if (resumeFlag) { + binding.buyAndSellGroup.check(R.id.coinBuyBtn) + replaceFragment(fragmentBuy) + } else { + resumeFlag = true + } + } + + override fun onDestroy() { + super.onDestroy() + listener?.orderBookThreadStop() + myViewModel.orderBook.value!!.clear() + doScrollVertically = true + } + + fun init() { + (fragmentSell as FragmentSell).listener = object : FragmentSell.OnPopupActivityControl { + override fun onPopupActivityShow() { + resumeFlag = false + } + } + + myViewModel.coinInfo.observe(viewLifecycleOwner, Observer { + for (coinInfo in myViewModel.coinInfo.value!!) { + if (coinInfo.code == myViewModel.selectedCoin.value!!) { + selectedCoin = coinInfo + break + } + } + binding.apply { + if (selectedCoin != null) { + val formatter = DecimalFormat("###,###") + val changeFormatter = DecimalFormat("###,###.##") + coinName.text = "${selectedCoin!!.name}(${selectedCoin!!.code.split('-')[1]})" + coinPrice.text = + if (selectedCoin!!.price.realTimePrice > 100.0) + formatter.format(selectedCoin!!.price.realTimePrice) + else + changeFormatter.format(selectedCoin!!.price.realTimePrice) + coinRate.text = + changeFormatter.format(selectedCoin!!.price.changeRate * 100) + "%" + coinDiff.text = when (selectedCoin!!.price.change) { + "EVEN" -> "" + "RISE" -> "▲ " + "FALL" -> "▼ " + else -> "" + } + changeFormatter.format(abs(selectedCoin!!.price.changePrice)) + + setTextViewColor(selectedCoin!!) + } + } + }) + myViewModel.orderBook.observe(viewLifecycleOwner, Observer { + orderBook.clear() + orderBook.addAll(myViewModel.orderBook.value!!) + adapter.notifyDataSetChanged() + + if (doScrollVertically && orderBook.isNotEmpty()) { + Log.i("FragmentTransaction", orderBook.size.toString()) + binding.apply { + // recyclerview에 있는 item들 중에서 가운데에 위치한 아이템이 화면의 중앙에 위치하도록 하고 싶은데 방법을 모르겠다. + recyclerView.scrollToPosition(0) + recyclerView.scrollToPosition(6) + } + doScrollVertically = false + } + }) + + for (coinInfo in myViewModel.coinInfo.value!!) { + if (coinInfo.code == myViewModel.selectedCoin.value!!) { + selectedCoin = coinInfo + break + } + } + adapter = FragmentTransactionAdapter(orderBook, selectedCoin!!.price.openPrice) + + binding.apply { + recyclerView.layoutManager = + LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + recyclerView.adapter = adapter + + buyAndSellGroup.setOnCheckedChangeListener(object : RadioGroup.OnCheckedChangeListener { + override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) { + when (checkedId) { + R.id.coinBuyBtn -> { + replaceFragment(fragmentBuy) + } + R.id.coinSellBtn -> { + replaceFragment(fragmentSell) + } + R.id.coinInfoBtn -> { + replaceFragment(fragmentCoinInfo) + } + else -> { + Log.e("FragmentTransaction", "Radio Group Error") + } + } + } + }) + + // 코인이 관심목록에 등록되어 있는 경우에는 ImageButton을 채워진 별로 변경해야 한다. + if (myViewModel.favoriteCoinInfo.value!!.contains(selectedCoin)) { + favoriteBtn.setImageResource(R.drawable.ic_round_star_24) + } + + favoriteBtn.setOnClickListener(object : View.OnClickListener { + override fun onClick(v: View?) { + // 즐겨찾기에 이미 추가되어 있는 경우 + if (myViewModel.favoriteCoinInfo.value!!.contains(selectedCoin)) { + if (myViewModel.removeFavoriteCoinInfo(selectedCoin!!)) { + val thread = object : Thread() { + override fun run() { + val flag = + myViewModel.myDBHelper!!.deleteFavorite(selectedCoin!!.code) + Log.e("favorite delete", flag.toString()) + } + } + thread.start() + favoriteBtn.setImageResource(R.drawable.ic_round_star_border_24) + Toast.makeText(context, "관심코인에서 삭제되었습니다.", Toast.LENGTH_SHORT).show() + } + } + // 즐겨찾기에 추가되어 있지 않은 경우 + else { + if (myViewModel.addFavoriteCoinInfo(selectedCoin!!)) { + val thread = object : Thread() { + override fun run() { + val flag = + myViewModel.myDBHelper!!.insertFavoirte(selectedCoin!!.code) + Log.e("favorite insert", flag.toString()) + } + } + thread.start() + favoriteBtn.setImageResource(R.drawable.ic_round_star_24) + Toast.makeText(context, "관심코인으로 등록되었습니다.", Toast.LENGTH_SHORT).show() + } + } + } + }) + } + } + + fun replaceFragment(fragment: Fragment) { + val fragmentTransaction: androidx.fragment.app.FragmentTransaction = + childFragmentManager.beginTransaction() + fragmentTransaction.replace(R.id.frameLayout, fragment) + fragmentTransaction.commit() + } + + fun setTextViewColor(coinInfo: CoinInfo) { + binding.apply { + if (coinInfo.price.changeRate > 0) { + coinPrice.setTextColor(Color.parseColor("#bd4e3a")) + coinRate.setTextColor(Color.parseColor("#bd4e3a")) + coinDiff.setTextColor(Color.parseColor("#bd4e3a")) + } else if (coinInfo.price.changeRate < 0) { + coinPrice.setTextColor(Color.parseColor("#135fc1")) + coinRate.setTextColor(Color.parseColor("#135fc1")) + coinDiff.setTextColor(Color.parseColor("#135fc1")) + } else { + coinPrice.setTextColor(Color.parseColor("#FFFFFF")) + coinRate.setTextColor(Color.parseColor("#FFFFFF")) + coinDiff.setTextColor(Color.parseColor("#FFFFFF")) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/MainActivity.kt b/app/src/main/java/com/mobit/mobit/MainActivity.kt new file mode 100644 index 0000000..b27f3be --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/MainActivity.kt @@ -0,0 +1,364 @@ +package com.mobit.mobit + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Bundle +import android.os.Handler +import android.os.Message +import android.util.Log +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import com.mobit.mobit.data.* +import com.mobit.mobit.databinding.ActivityMainBinding +import com.mobit.mobit.db.MyDBHelper +import com.mobit.mobit.service.UpbitAPIService +import java.util.* +import kotlin.collections.ArrayList + +class MainActivity : AppCompatActivity() { + + // Fragment 변수 시작 + val fragmentCoinList: Fragment = FragmentCoinList() + val fragmentChart: Fragment = FragmentChart() + val fragmentTransaction: Fragment = FragmentTransaction() + val fragmentInvestment: Fragment = FragmentInvestment() + val fragmentSetting: Fragment = FragmentSetting() + // Fragment 변수 끝 + + // UI 변수 시작 + lateinit var binding: ActivityMainBinding + // UI 변수 끝 + + val myViewModel: MyViewModel by viewModels() + + val dbHandler: DBHandler = DBHandler() + lateinit var dbThread: DBThread + + // 뒤로가기 두번 누르면 앱 종료 관련 변수 시작 + val FINISH_INTERVAL_TIME: Long = 2000 + var backPressedTime: Long = 0 + // 뒤로가기 두번 누르면 앱 종료 관련 변수 끝 + + val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.data != null) { + val krw: Double = it.data!!.getDoubleExtra("krw", 10000000.0) + myViewModel.asset.value!!.krw = krw + val thread = object : Thread() { + override fun run() { + myViewModel.myDBHelper!!.setFlag(true) + myViewModel.myDBHelper!!.setKRW(krw) + } + } + thread.start() + } + } + + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent != null) { + val mode = intent.getStringExtra("mode") + when (mode) { + "CoinInfo" -> { + val isSuccess = intent.getBooleanExtra("isSuccess", false) + if (isSuccess) { + val coinInfo = + intent.getSerializableExtra("coinInfo") as ArrayList + val favoriteCoinInfo = + intent.getSerializableExtra("favoriteCoinInfo") as ArrayList + myViewModel.setCoinInfo(coinInfo) + myViewModel.setFavoriteCoinInfo(favoriteCoinInfo) + } + } + "orderBook" -> { + val isSuccess = intent.getBooleanExtra("isSuccess", false) + if (isSuccess) { + val orderBook = + intent.getSerializableExtra("orderBook") as ArrayList + myViewModel.setOrderBook(orderBook) + } + } + } + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + initDB() + initData() + initService() + init() + } + + override fun onBackPressed() { + val tempTime: Long = System.currentTimeMillis() + val intervalTime = tempTime - backPressedTime + + if (0 <= intervalTime && intervalTime <= FINISH_INTERVAL_TIME) { + super.onBackPressed() + } else { + backPressedTime = tempTime + val msg: String = "뒤로 가기를 한 번 더 누르면 종료됩니다." + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() + } + } + + override fun onRestart() { + super.onRestart() + + val serviceBRIntent = Intent(this, UpbitAPIService::class.java) + serviceBRIntent.putExtra("mode", "START") + sendBroadcast(serviceBRIntent) + } + + override fun onStop() { + super.onStop() + + val serviceBRIntent = Intent(this, UpbitAPIService::class.java) + serviceBRIntent.putExtra("mode", "STOP") + sendBroadcast(serviceBRIntent) + } + + override fun onDestroy() { + super.onDestroy() + unregisterReceiver(receiver) + } + + fun initDB() { + myViewModel.myDBHelper = MyDBHelper(this) + dbThread = DBThread() + dbThread.start() + } + + fun initData() { + myViewModel.setSelectedCoin(CoinInfo.BTC_CODE) + myViewModel.setFavoriteCoinInfo(ArrayList()) + myViewModel.setOrderBook(ArrayList()) + myViewModel.setAsset(Asset(0.0, ArrayList())) + } + + fun initService() { + registerReceiver(receiver, IntentFilter("com.mobit.APIRECEIVE")) + val intent = Intent(this, UpbitAPIService::class.java) + startService(intent) + } + + fun init() { + myViewModel.selectedCoin.observe(this, androidx.lifecycle.Observer { + val serviceBRIntent = Intent("com.mobit.APICALL") + serviceBRIntent.putExtra("mode", "SELECTED_COIN_SETTING") + serviceBRIntent.putExtra("selectedCoin", myViewModel.selectedCoin.value!!) + sendBroadcast(serviceBRIntent) + }) + myViewModel.favoriteCoinInfo.observe(this, androidx.lifecycle.Observer { + val serviceBRIntent = Intent("com.mobit.APICALL") + serviceBRIntent.putExtra("mode", "FAVORITE_COININFO_SETTING") + serviceBRIntent.putExtra("favoriteCoinInfo", myViewModel.favoriteCoinInfo.value!!) + sendBroadcast(serviceBRIntent) + }) + + replaceFragment(fragmentCoinList) + binding.apply { + bottomNavBar.setOnNavigationItemSelectedListener { + when (it.itemId) { + R.id.menu_coinlist -> { + replaceFragment(fragmentCoinList) + return@setOnNavigationItemSelectedListener true + } + R.id.menu_chart -> { + replaceFragment(fragmentChart) + return@setOnNavigationItemSelectedListener true + } + R.id.menu_transaction -> { + val serviceBTIntent = Intent("com.mobit.APICALL") + serviceBTIntent.putExtra("mode", "START_THREAD2") + sendBroadcast(serviceBTIntent) + + replaceFragment(fragmentTransaction) + return@setOnNavigationItemSelectedListener true + } + R.id.menu_investment -> { + replaceFragment(fragmentInvestment) + return@setOnNavigationItemSelectedListener true + } + R.id.menu_setting -> { + replaceFragment(fragmentSetting) + return@setOnNavigationItemSelectedListener true + } + else -> { + return@setOnNavigationItemSelectedListener false + } + } + } + } + + (fragmentCoinList as FragmentCoinList).listener = + object : FragmentCoinList.OnFragmentInteraction { + override fun showTransaction() { + binding.bottomNavBar.selectedItemId = R.id.menu_transaction + } + } + (fragmentTransaction as FragmentTransaction).listener = + object : FragmentTransaction.OnFragmentInteraction { + override fun orderBookThreadStop() { + val serviceBTIntent = Intent("com.mobit.APICALL") + serviceBTIntent.putExtra("mode", "STOP_THREAD2") + sendBroadcast(serviceBTIntent) + } + } + } + + fun replaceFragment(fragment: Fragment) { + val fragmentTransaction: androidx.fragment.app.FragmentTransaction = + supportFragmentManager.beginTransaction() + fragmentTransaction.replace(R.id.frameLayout, fragment) + fragmentTransaction.commit() + } + + inner class DBHandler : Handler() { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + + val bundle: Bundle = msg.data + if (!bundle.isEmpty) { + val isFavorites = bundle.getBoolean("isFavorites") + val isKrw = bundle.getBoolean("isKrw") + val isCoinAssets = bundle.getBoolean("isCoinAssets") + val isTransaction = bundle.getBoolean("isTransactions") + + if (isFavorites) { + val favorites = bundle.getSerializable("favorites") as ArrayList + val list = ArrayList() + for (code in favorites) { + list.add( + CoinInfo( + code, + code, + Price( + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + "EVEN", + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + "", + 0.0, + "" + ) + ) + ) + } + myViewModel.setFavoriteCoinInfo(list) + } + if (isKrw) { + val krw = bundle.getDouble("krw") + if (isCoinAssets) { + val coinAssets = + bundle.getSerializable("coinAssets") as ArrayList + val asset = Asset(krw, coinAssets) + myViewModel.setAsset(asset) + } else { + val asset = Asset(krw, ArrayList()) + myViewModel.setAsset(asset) + } + Log.i("isKrw True", krw.toString()) + } else { + val asset = Asset(10000000.0, ArrayList()) + myViewModel.setAsset(asset) + Log.i("isKrw False", asset.krw.toString()) + } + if (isTransaction) { + val transactions = + bundle.getSerializable("transactions") as ArrayList + myViewModel.setTransaction(transactions) + } else { + val transactions = ArrayList() + myViewModel.setTransaction(transactions) + } + + val flag = bundle.getBoolean("flag") + setTheme(R.style.Theme_Mobit) + if (!flag) { + val intent = Intent(this@MainActivity, FirstSettingActivity::class.java) + getContent.launch(intent) + } + + val serviceBRIntent = Intent("com.mobit.APICALL") + serviceBRIntent.putExtra("mode", "INITIAL_SETTING") + serviceBRIntent.putExtra("selectedCoin", myViewModel.selectedCoin.value!!) + serviceBRIntent.putExtra("favoriteCoinInfo", myViewModel.favoriteCoinInfo.value!!) + serviceBRIntent.putExtra("asset", myViewModel.asset.value!!) + sendBroadcast(serviceBRIntent) + + val serviceBRIntent2 = Intent("com.mobit.APICALL") + serviceBRIntent2.putExtra("mode", "START_THREAD1") + sendBroadcast(serviceBRIntent2) + } + } + } + + inner class DBThread : Thread() { + override fun run() { + val message: Message = dbHandler.obtainMessage() + val bundle: Bundle = Bundle() + + // DB로부터 favorite 데이터 가져오기 + val favorites = myViewModel.myDBHelper!!.getFavorites() + if (favorites.isNotEmpty()) { + bundle.putBoolean("isFavorites", true) + bundle.putSerializable("favorites", favorites) + } else { + bundle.putBoolean("isFavorites", false) + } + + // DB로부터 KRW 데이터 가져오기 + val krw = myViewModel.myDBHelper!!.getKRW() + if (krw != null) { + bundle.putBoolean("isKrw", true) + bundle.putDouble("krw", krw!!) + } else { + bundle.putBoolean("isKrw", false) + } + + // DB로부터 CoinAsset 데이터 가져오기 + val coinAssets = myViewModel.myDBHelper!!.getCoinAssets() + if (coinAssets.isNotEmpty()) { + bundle.putBoolean("isCoinAssets", true) + bundle.putSerializable("coinAssets", coinAssets) + } else { + bundle.putBoolean("isCoinAssets", false) + } + + // DB로부터 Transaction 데이터 가져오기 + val transactions = myViewModel.myDBHelper!!.getTransactions() + if (transactions.isNotEmpty()) { + bundle.putBoolean("isTransactions", true) + bundle.putSerializable("transactions", transactions) + } else { + bundle.putBoolean("isTransactions", false) + } + + val flag = myViewModel.myDBHelper!!.getFlag() + bundle.putBoolean("flag", flag) + + message.data = bundle + dbHandler.sendMessage(message) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/MyProgressBar.kt b/app/src/main/java/com/mobit/mobit/MyProgressBar.kt new file mode 100644 index 0000000..84836b6 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/MyProgressBar.kt @@ -0,0 +1,51 @@ +package com.mobit.mobit + +import android.app.Activity +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.text.TextUtils +import android.view.View +import android.widget.TextView +import androidx.appcompat.app.AppCompatDialog + +class MyProgressBar { + private var progressDialog: AppCompatDialog? = null + + fun progressON(activity: Activity?, message: String?) { + if (activity == null || activity.isFinishing) { + return + } + if (progressDialog != null && progressDialog!!.isShowing) { + progressSET(message) + } else { + progressDialog = AppCompatDialog(activity) + progressDialog!!.setCancelable(false) + progressDialog!!.window + ?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + progressDialog!!.setContentView(R.layout.progress_loading) + progressDialog!!.show() + } + val tv_progress_message = + progressDialog!!.findViewById(R.id.tv_progress_message) as TextView? + if (!TextUtils.isEmpty(message)) { + tv_progress_message!!.text = message + } + } + + fun progressSET(message: String?) { + if (progressDialog == null || !progressDialog!!.isShowing) { + return + } + val tv_progress_message = + progressDialog!!.findViewById(R.id.tv_progress_message) as TextView? + if (!TextUtils.isEmpty(message)) { + tv_progress_message!!.text = message + } + } + + fun progressOFF() { + if (progressDialog != null && progressDialog!!.isShowing) { + progressDialog!!.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/PopupBuySellActivity.kt b/app/src/main/java/com/mobit/mobit/PopupBuySellActivity.kt new file mode 100644 index 0000000..26bf432 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/PopupBuySellActivity.kt @@ -0,0 +1,71 @@ +package com.mobit.mobit + +import android.app.Activity +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.util.Log +import android.view.Window +import com.mobit.mobit.databinding.ActivityPopupBuySellBinding +import java.text.DecimalFormat + +class PopupBuySellActivity : Activity() { + + lateinit var binding: ActivityPopupBuySellBinding + val formatter = DecimalFormat("###,###") + val formatter2 = DecimalFormat("###,###.####") + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityPopupBuySellBinding.inflate(layoutInflater) + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(binding.root) + + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + + // 1-> 매수 + // 2-> 매도 + val type: Int = intent.getIntExtra("type", 1) + val code: String = intent.getStringExtra("code") + val name: String = intent.getStringExtra("name") + val unitPrice: Double = intent.getDoubleExtra("unitPrice", 0.0) + val count: Double = intent.getDoubleExtra("count", 0.0) + val totalPrice: Double = unitPrice * count + + binding.apply { + when (type) { + 1 -> { + title.setTextColor(Color.parseColor("#c34c34")) + confirmBtn.setTextColor(Color.parseColor("#c34c34")) + title.text = "매수주문 확인" + confirmBtn.text = "매수확인" + } + 2 -> { + title.setTextColor(Color.parseColor("#1262c5")) + confirmBtn.setTextColor(Color.parseColor("#1262c5")) + title.text = "매도주문 확인" + confirmBtn.text = "매도확인" + } + } + coin.text = "$name(${code.split('-')[1]}/KRW)" + orderPrice.text = formatter.format(unitPrice) + orderCount.text = formatter2.format(count) + orderTotalPrice.text = formatter.format(totalPrice) + + confirmBtn.setOnClickListener { + val intent: Intent = Intent() + intent.putExtra("code", code) + intent.putExtra("name", name) + setResult(RESULT_OK, intent) + Log.i("PopupBuySellActivity", "RESULT_OK") + finish() + } + cancelBtn.setOnClickListener { + setResult(RESULT_CANCELED) + Log.i("PopupBuySellActivity", "RESULT_CANCELED") + finish() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/PopupResetConfirmActivity.kt b/app/src/main/java/com/mobit/mobit/PopupResetConfirmActivity.kt new file mode 100644 index 0000000..9eae887 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/PopupResetConfirmActivity.kt @@ -0,0 +1,38 @@ +package com.mobit.mobit + +import android.app.Activity +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.Window +import com.mobit.mobit.databinding.ActivityPopupResetConfirmBinding + +class PopupResetConfirmActivity : Activity() { + + lateinit var binding: ActivityPopupResetConfirmBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityPopupResetConfirmBinding.inflate(layoutInflater) + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(binding.root) + + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + + binding.apply { + cancelBtn.setOnClickListener { + setResult(RESULT_CANCELED) + finish() + } + + confirmBtn.setOnClickListener { + setResult(RESULT_OK) + finish() + } + } + } + + override fun onBackPressed() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/adapter/FragmentAssetAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/FragmentAssetAdapter.kt new file mode 100644 index 0000000..d763f0f --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/adapter/FragmentAssetAdapter.kt @@ -0,0 +1,87 @@ +package com.mobit.mobit.adapter + +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.mobit.mobit.R +import com.mobit.mobit.data.CoinAsset +import java.text.DecimalFormat + +/* +FragmentAsset에서 보유 자산을 보여줄 때 사용하는 adapter 입니다. +*/ +class FragmentAssetAdapter(var items: ArrayList) : + RecyclerView.Adapter() { + + val formatter = DecimalFormat("###,###") + val formatter2 = DecimalFormat("###,###.##") + val formatter3 = DecimalFormat("###,###.####") + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val nameView: TextView + val codeView: TextView + val gainAndLossView: TextView + val yieldView: TextView + val retainedNumView: TextView + val averagePriceView: TextView + val evaluationView: TextView + val buyPriceView: TextView + + init { + nameView = itemView.findViewById(R.id.nameView) + codeView = itemView.findViewById(R.id.codeView) + gainAndLossView = itemView.findViewById(R.id.gainAndLossView) + yieldView = itemView.findViewById(R.id.yieldView) + retainedNumView = itemView.findViewById(R.id.retainedNumView) + averagePriceView = itemView.findViewById(R.id.averagePriceView) + evaluationView = itemView.findViewById(R.id.evaluationView) + buyPriceView = itemView.findViewById(R.id.buyPriceView) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val v = LayoutInflater.from(parent.context) + .inflate(R.layout.recyclerview_asset_item, parent, false) + return ViewHolder(v) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.nameView.text = items[position].name + val code = items[position].code.split("-")[1] + holder.codeView.text = "($code)" + holder.retainedNumView.text = + "${formatter3.format(items[position].number)} $code" + holder.averagePriceView.text = + if (items[position].averagePrice > 100.0) + "${formatter.format(items[position].averagePrice)} KRW" + else "${formatter2.format(items[position].averagePrice)} KRW" + holder.evaluationView.text = "${formatter.format(items[position].amount)} KRW" + + val buyPrice = items[position].averagePrice * items[position].number + holder.buyPriceView.text = "${formatter.format(buyPrice)} KRW" + + val gainAndLoss = items[position].amount - buyPrice + val yieldValue = gainAndLoss / buyPrice * 100 + holder.gainAndLossView.text = formatter.format(gainAndLoss) + holder.yieldView.text = formatter2.format(yieldValue) + "%" + + val rgb = if (gainAndLoss > 0) Color.rgb( + 207, + 80, + 71 + ) else if (gainAndLoss < 0) Color.rgb( + 25, + 96, + 186 + ) else Color.rgb(211, 212, 214) + holder.gainAndLossView.setTextColor(rgb) + holder.yieldView.setTextColor(rgb) + } + + override fun getItemCount(): Int { + return items.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/adapter/FragmentCoinListAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/FragmentCoinListAdapter.kt new file mode 100644 index 0000000..c6ffe16 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/adapter/FragmentCoinListAdapter.kt @@ -0,0 +1,130 @@ +package com.mobit.mobit.adapter + +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Filter +import android.widget.Filterable +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.mobit.mobit.R +import com.mobit.mobit.data.CoinInfo +import java.text.DecimalFormat + +class FragmentCoinListAdapter( + var items: ArrayList, + var filteredItems: ArrayList +) : + RecyclerView.Adapter(), Filterable { + + var listener: OnItemClickListener? = null + + interface OnItemClickListener { + fun onItemClicked(view: View, coinInfo: CoinInfo) + } + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val linearLayout: LinearLayout + val korCoinName: TextView + val engCoinName: TextView + val realTimePrice: TextView + val changeRate: TextView + val totalTradePrice: TextView + + init { + linearLayout = itemView.findViewById(R.id.linearLayout) + korCoinName = itemView.findViewById(R.id.korCoinName) + engCoinName = itemView.findViewById(R.id.engCoinName) + realTimePrice = itemView.findViewById(R.id.realTimePrice) + changeRate = itemView.findViewById(R.id.changeRate) + totalTradePrice = itemView.findViewById(R.id.totalTradePrice) + + val clickListener: View.OnClickListener = object : View.OnClickListener { + override fun onClick(v: View?) { + if (v != null) { + listener?.onItemClicked(v, filteredItems[adapterPosition]) + } + } + } + linearLayout.setOnClickListener(clickListener) + korCoinName.setOnClickListener(clickListener) + engCoinName.setOnClickListener(clickListener) + realTimePrice.setOnClickListener(clickListener) + changeRate.setOnClickListener(clickListener) + totalTradePrice.setOnClickListener(clickListener) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val v = LayoutInflater.from(parent.context) + .inflate(R.layout.recyclerview_coinlist_item, parent, false) + return ViewHolder(v) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val formatter = DecimalFormat("###,###") + val changeFormatter = DecimalFormat("###,###.##") + + holder.korCoinName.text = filteredItems[position].name + holder.engCoinName.text = filteredItems[position].code.split('-')[1] + "/KRW" + holder.realTimePrice.text = + if (filteredItems[position].price.realTimePrice > 100.0) + formatter.format(filteredItems[position].price.realTimePrice) + else + changeFormatter.format(filteredItems[position].price.realTimePrice) + holder.changeRate.text = + changeFormatter.format(filteredItems[position].price.changeRate * 100) + "%" + var temp = (filteredItems[position].price.totalTradePrice24 / 1000000).toInt() + holder.totalTradePrice.text = formatter.format(temp) + "백만" + + if (filteredItems[position].price.changeRate > 0) { + holder.realTimePrice.setTextColor(Color.parseColor("#bd4e3a")) + holder.changeRate.setTextColor(Color.parseColor("#bd4e3a")) + } else if (filteredItems[position].price.changeRate < 0) { + holder.realTimePrice.setTextColor(Color.parseColor("#135fc1")) + holder.changeRate.setTextColor(Color.parseColor("#135fc1")) + } else { + holder.realTimePrice.setTextColor(Color.parseColor("#FFFFFF")) + holder.changeRate.setTextColor(Color.parseColor("#FFFFFF")) + } + } + + override fun getItemCount(): Int { + return filteredItems.size + } + + override fun getFilter(): Filter { + return object : Filter() { + override fun performFiltering(constraint: CharSequence?): FilterResults { + val str: String = constraint.toString() + + if (str.isNullOrBlank()) { + filteredItems = items + } else { + val filteringList: ArrayList = ArrayList() + for (coinInfo in items) { + val coinName = coinInfo.name + if (coinName.contains(str)) { + filteringList.add(coinInfo) + } + } + filteredItems = filteringList + } + val result: FilterResults = FilterResults() + result.values = filteredItems + + return result + } + + override fun publishResults(constraint: CharSequence?, results: FilterResults?) { + if (results != null) { + filteredItems = results.values as ArrayList + notifyDataSetChanged() + } + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/adapter/FragmentRecordAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/FragmentRecordAdapter.kt new file mode 100644 index 0000000..abe5d47 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/adapter/FragmentRecordAdapter.kt @@ -0,0 +1,125 @@ +package com.mobit.mobit.adapter + +import android.graphics.Color +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Filter +import android.widget.Filterable +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.mobit.mobit.R +import com.mobit.mobit.data.Transaction +import java.text.DecimalFormat + +// blue -> #1561bf +// red -> #b35241 +class FragmentRecordAdapter( + var items: ArrayList, + var filteredList: ArrayList +) : + RecyclerView.Adapter(), Filterable { + + val formatter = DecimalFormat("###,###") + val formatter2 = DecimalFormat("###,###.##") + val formatter3 = DecimalFormat("###,###.####") + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val nameView: TextView + val typeView: TextView + val timeView: TextView + val tradePriceView: TextView + val tradeNumView: TextView + val unitPriceView: TextView + val feeView: TextView + val totalPriceView: TextView + + init { + nameView = itemView.findViewById(R.id.nameView) + typeView = itemView.findViewById(R.id.typeView) + timeView = itemView.findViewById(R.id.timeView) + tradePriceView = itemView.findViewById(R.id.tradePriceView) + tradeNumView = itemView.findViewById(R.id.tradeNumView) + unitPriceView = itemView.findViewById(R.id.unitPriceView) + feeView = itemView.findViewById(R.id.feeView) + totalPriceView = itemView.findViewById(R.id.totalPriceView) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val v = LayoutInflater.from(parent.context) + .inflate(R.layout.recyclerview_record_item, parent, false) + return ViewHolder(v) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + Log.i("FragmentRecordAdapter", filteredList[position].code) + val code = filteredList[position].code.split("-")[1] +// val code = filteredList[position].code + holder.nameView.text = "${filteredList[position].name}($code)" + + val tradePrice = filteredList[position].tradePrice + val fee = filteredList[position].fee + when (filteredList[position].type) { + // 매도 + Transaction.ASK -> { + holder.typeView.text = "매도" + holder.typeView.setTextColor(Color.rgb(26, 96, 184)) + holder.totalPriceView.text = "${formatter.format(tradePrice - fee)} KRW" + } + // 매수 + Transaction.BID -> { + holder.typeView.text = "매수" + holder.typeView.setTextColor(Color.rgb(188, 79, 59)) + holder.totalPriceView.text = "${formatter.format(tradePrice + fee)} KRW" + } + } + + val times = filteredList[position].time.split("T") + holder.timeView.text = "${times[0]} ${times[1].substring(0, 5)}" + holder.tradePriceView.text = "${formatter.format(tradePrice)} KRW" + holder.tradeNumView.text = "${formatter3.format(filteredList[position].quantity)} $code" + holder.unitPriceView.text = + if (filteredList[position].unitPrice > 100.0) + "${formatter.format(filteredList[position].unitPrice)} KRW" + else "${formatter2.format(filteredList[position].unitPrice)} KRW" + holder.feeView.text = "${formatter2.format(fee)} KRW" + } + + override fun getItemCount(): Int { + return filteredList.size + } + + override fun getFilter(): Filter { + return object : Filter() { + override fun performFiltering(constraint: CharSequence?): FilterResults { + val str: String = constraint.toString() + + if (str.isBlank()) { + filteredList = items + } else { + val filteringList: ArrayList = ArrayList() + for (transaction in items) { + if (transaction.name.contains(str)) { + filteringList.add(transaction) + } + } + filteredList = filteringList + } + + val filterResults: FilterResults = FilterResults() + filterResults.values = filteredList + + return filterResults + } + + override fun publishResults(constraint: CharSequence?, results: FilterResults?) { + if (results != null) { + filteredList = results.values as ArrayList + notifyDataSetChanged() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/adapter/FragmentTransactionAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/FragmentTransactionAdapter.kt new file mode 100644 index 0000000..a34d01c --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/adapter/FragmentTransactionAdapter.kt @@ -0,0 +1,91 @@ +package com.mobit.mobit.adapter + +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.mobit.mobit.R +import com.mobit.mobit.data.OrderBook +import java.text.DecimalFormat + +class FragmentTransactionAdapter(var items: ArrayList, val openPrice: Double) : + RecyclerView.Adapter() { + + var listener: OnItemClickListener? = null + + interface OnItemClickListener { + fun onItemClicked(view: View, price: Double) + } + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val linearLayout: LinearLayout + val price: TextView + val priceRate: TextView + val orderSize: TextView + + init { + linearLayout = itemView.findViewById(R.id.linearLayout) + price = itemView.findViewById(R.id.price) + priceRate = itemView.findViewById(R.id.priceRate) + orderSize = itemView.findViewById(R.id.orderSize) + + val clickListener: View.OnClickListener = object : View.OnClickListener { + override fun onClick(v: View?) { + if (v != null) { + + } + } + } + price.setOnClickListener(clickListener) + priceRate.setOnClickListener(clickListener) + orderSize.setOnClickListener(clickListener) + } + + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val v = LayoutInflater.from(parent.context) + .inflate(R.layout.recyclerview_transaction_item, parent, false) + return ViewHolder(v) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val formatter = DecimalFormat("###,###") + val changeFormatter = DecimalFormat("###,###.###") + + val price = items[position].price + val priceRate = (price - openPrice) / openPrice * 100 + holder.price.text = + if (price > 100.0) formatter.format(price) else changeFormatter.format(price) + holder.priceRate.text = "${changeFormatter.format(priceRate)}%" + holder.orderSize.text = changeFormatter.format(items[position].size) + + if (openPrice > price) { + holder.price.setTextColor(Color.parseColor("#135fc1")) + holder.priceRate.setTextColor(Color.parseColor("#135fc1")) + } else if (openPrice < price) { + holder.price.setTextColor(Color.parseColor("#bd4e3a")) + holder.priceRate.setTextColor(Color.parseColor("#bd4e3a")) + } else { + holder.price.setTextColor(Color.parseColor("#FFFFFF")) + holder.priceRate.setTextColor(Color.parseColor("#FFFFFF")) + } + + if (position < 15) { + holder.price.setBackgroundColor(Color.parseColor("#081d3a")) + holder.priceRate.setBackgroundColor(Color.parseColor("#081d3a")) + holder.orderSize.setBackgroundColor(Color.parseColor("#081d3a")) + } else { + holder.price.setBackgroundColor(Color.parseColor("#241a23")) + holder.priceRate.setBackgroundColor(Color.parseColor("#241a23")) + holder.orderSize.setBackgroundColor(Color.parseColor("#241a23")) + } + } + + override fun getItemCount(): Int { + return items.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/adapter/InvestmentStateAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/InvestmentStateAdapter.kt new file mode 100644 index 0000000..8894c34 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/adapter/InvestmentStateAdapter.kt @@ -0,0 +1,26 @@ +package com.mobit.mobit.adapter + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.mobit.mobit.FragmentAsset +import com.mobit.mobit.FragmentRecord + +class InvestmentStateAdapter(fragmentActivity: FragmentActivity) : + FragmentStateAdapter(fragmentActivity) { + + val fragmentAsset: Fragment = FragmentAsset() + val fragmentRecord: Fragment = FragmentRecord() + + override fun getItemCount(): Int { + return 2 + } + + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> fragmentAsset + 1 -> fragmentRecord + else -> fragmentAsset + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/data/Asset.kt b/app/src/main/java/com/mobit/mobit/data/Asset.kt new file mode 100644 index 0000000..c72cdfa --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/data/Asset.kt @@ -0,0 +1,109 @@ +package com.mobit.mobit.data + +import android.util.Log +import java.io.Serializable + +class Asset : Serializable { + + constructor(krw: Double, coins: ArrayList) { + this.krw = krw + this.coins.addAll(coins) + } + + var krw: Double = 0.0 // 보유 KRW 금액 + val coins: ArrayList = ArrayList() // 보유 코인 자산 + + fun canBidCoin(code: String, name: String, price: Double, number: Double): Boolean { + val orderPrice = price * number + + if (krw < orderPrice) + return false + + return true + } + + // code에 해당하는 코인을 price 가격으로 number개 만큼 매수한다. + // 매수한 코인의 인덱스를 리턴한다. + fun bidCoin(code: String, name: String, price: Double, number: Double): Int { + val orderPrice = price * number + if (krw < orderPrice) + return -1 + krw -= orderPrice + + var index: Int = -1 + for (i in coins.indices) { + if (coins[i].code == code) { + index = i + break + } + } + + var ret: Int = 0 + if (index == -1) { + val newCoin = CoinAsset(code, name, number, number * price, price) + coins.add(newCoin) + ret = coins.indexOf(newCoin) + } else { + coins[index].averagePrice = + (coins[index].number * coins[index].averagePrice + orderPrice) / (coins[index].number + number) + coins[index].number += number + coins[index].amount += price * number + ret = index + } + Log.i("bidCoin in Asset", coins[ret].number.toString()) + return ret + } + + fun canAskCoin(code: String, price: Double, number: Double): Boolean { + var index: Int = -1 + for (i in coins.indices) { + if (coins[i].code == code) { + index = i + break + } + } + if (index == -1) + return false + + val coin = coins[index] + if (coin!!.number < number) + return false + + return true + } + + // code에 해당하는 코인을 price 가격으로 number개 만큼 매도한다. + // 매도한 코인의 coinAsset을 리턴한다. + fun askCoin(code: String, price: Double, number: Double): CoinAsset? { + var index: Int = -1 + for (i in coins.indices) { + if (coins[i].code == code) { + index = i + break + } + } + if (index == -1) + return null + + val coin = coins[index] + if (coin!!.number < number) + return null + + val orderPrice = price * number + val fee = orderPrice * 0.0005 + krw += (orderPrice - fee) + + var ret: CoinAsset? = null + if (coin.number == number) { + coins.remove(coin) + coin.number = 0.0 + ret = coin + } else { + coins[index].number -= number + coins[index].amount -= (price * number) + ret = coins[index] + } + + return ret + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/data/Candle.kt b/app/src/main/java/com/mobit/mobit/data/Candle.kt new file mode 100644 index 0000000..1673f3f --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/data/Candle.kt @@ -0,0 +1,12 @@ +package com.mobit.mobit.data + +import java.io.Serializable + +data class Candle( + val createdAt: Long, + val open: Float, + val close: Float, + val shadowHigh: Float, + val shadowLow: Float, + val totalTradeVolume: Float +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/data/CoinAsset.kt b/app/src/main/java/com/mobit/mobit/data/CoinAsset.kt new file mode 100644 index 0000000..0f370a5 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/data/CoinAsset.kt @@ -0,0 +1,11 @@ +package com.mobit.mobit.data + +import java.io.Serializable + +data class CoinAsset( + val code: String, // 코인 코드 + val name: String, // 코인 이름 + var number: Double, // 코인 보유 개수 + var amount: Double, // 코인 보유 금액 + var averagePrice: Double, // 평균 단가 +) : Serializable diff --git a/app/src/main/java/com/mobit/mobit/data/CoinInfo.kt b/app/src/main/java/com/mobit/mobit/data/CoinInfo.kt new file mode 100644 index 0000000..ba85a39 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/data/CoinInfo.kt @@ -0,0 +1,52 @@ +package com.mobit.mobit.data + +import java.io.Serializable + +data class CoinInfo( + val code: String, // 코인 코드 + val name: String, // 코인 이름 + val price: Price // 코인 현재가 정보 +) : Serializable { + companion object { + val BTC_CODE = "KRW-BTC" // 비트코인 + val BTC_NAME = "비트코인" + val ETH_CODE = "KRW-ETH" // 이더리움 + val ETH_NAME = "이더리움" + val ADA_CODE = "KRW-ADA" // 에이다 + val ADA_NAME = "에이다" + val DOGE_CODE = "KRW-DOGE" // 도지코인 + val DOGE_NAME = "도지코인" + val XRP_CODE = "KRW-XRP" // 리플 + val XRP_NAME = "리플" + val DOT_CODE = "KRW-DOT" // 폴카닷 + val DOT_NAME = "폴카닷" + val BCH_CODE = "KRW-BCH" // 비트코인캐시 + val BCH_NAME = "비트코인캐시" + val LTC_CODE = "KRW-LTC" // 라이트코인 + val LTC_NAME = "라이트코인" + val LINK_CODE = "KRW-LINK" // 체인링크 + val LINK_NAME = "체인링크" + val ETC_CODE = "KRW-ETC" // 이더리움클래식 + val ETC_NAME = "이더리움클래식" + val THETA_CODE = "KRW-THETA" // 쎄타토큰 + val THETA_NAME = "쎄타토큰" + val XLM_CODE = "KRW-XLM" // 스텔라루멘 + val XLM_NAME = "스텔라루멘" + val VET_CODE = "KRW-VET" // 비체인 + val VET_NAME = "비체인" + val EOS_CODE = "KRW-EOS" // 이오스 + val EOS_NAME = "이오스" + val TRX_CODE = "KRW-TRX" // 트론 + val TRX_NAME = "트론" + val NEO_CODE = "KRW-NEO" // 네오 + val NEO_NAME = "네오" + val IOTA_CODE = "KRW-IOTA" // 아이오타 + val IOTA_NAME = "아이오타" + val ATOM_CODE = "KRW-ATOM" // 코스모스 + val ATOM_NAME = "코스모스" + val BSV_CODE = "KRW-BSV" // 비트코인에스브이 + val BSV_NAME = "비트코인에스브이" + val BTT_CODE = "KRW-BTT" // 비트토렌트 + val BTT_NAME = "비트토렌트" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/data/MyViewModel.kt b/app/src/main/java/com/mobit/mobit/data/MyViewModel.kt new file mode 100644 index 0000000..a104ef4 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/data/MyViewModel.kt @@ -0,0 +1,107 @@ +package com.mobit.mobit.data + +import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.mobit.mobit.db.MyDBHelper + +class MyViewModel : ViewModel() { + + // FragmentCoinList에서 선택한 코인을 다른 Fragment에서 참고할 때 사용하는 변수 + val selectedCoin: MutableLiveData = MutableLiveData() + + // 실시간으로 얻어온 코인 정보를 저장할 변수 + val coinInfo: MutableLiveData> = MutableLiveData() + + // 사용자가 즐겨찾기에 추가한 코인 정보를 저장할 변수 + val favoriteCoinInfo: MutableLiveData> = MutableLiveData() + + // 실시간으로 얻어온 호가 정보를 저장할 변수 + val orderBook: MutableLiveData> = MutableLiveData() + + // 사용자가 보유중인 자산 정보를 저장할 변수 + val asset: MutableLiveData = MutableLiveData() + + // 사용자가 매수 또는 매도를 진행할 때마다, 거래 내역을 저장할 변수 + val transaction: MutableLiveData> = MutableLiveData() + + // DB에 데이터를 저장하기 위한 변수 + var myDBHelper: MyDBHelper? = null + + fun setSelectedCoin(selectedCoin: String) { + this.selectedCoin.value = selectedCoin + } + + fun setCoinInfo(coinInfo: ArrayList) { + this.coinInfo.value = coinInfo + } + + fun setFavoriteCoinInfo(favoriteCoinInfo: ArrayList) { + this.favoriteCoinInfo.value = favoriteCoinInfo + } + + fun setOrderBook(orderBook: ArrayList) { + this.orderBook.value = orderBook + } + + fun setAsset(asset: Asset) { + this.asset.value = asset + } + + fun setTransaction(transaction: ArrayList) { + this.transaction.value = transaction + } + + fun addTransaction(transaction: Transaction) { + val temp = ArrayList() + temp.add(transaction) + temp.addAll(this.transaction.value!!) + this.transaction.value = temp + } + + fun addFavoriteCoinInfo(coinInfo: CoinInfo): Boolean { + if (favoriteCoinInfo.value == null) + return false + + if (favoriteCoinInfo.value!!.contains(coinInfo)) + return false + + val temp = ArrayList() + temp.addAll(favoriteCoinInfo.value!!) + temp.add(coinInfo) + this.favoriteCoinInfo.value = temp + + return true + } + + fun removeFavoriteCoinInfo(coinInfo: CoinInfo): Boolean { + if (favoriteCoinInfo.value == null) + return false + + if (!favoriteCoinInfo.value!!.contains(coinInfo)) + return false + + val temp = ArrayList() + temp.addAll(favoriteCoinInfo.value!!) + temp.remove(coinInfo) + favoriteCoinInfo.value = temp + + return true + } + + fun bidCoin(code: String, name: String, price: Double, number: Double): Int { + val temp = Asset(this.asset.value!!.krw, this.asset.value!!.coins) + val ret = temp.bidCoin(code, name, price, number) + Log.i("FragmentBuy before setValue", temp.coins[ret].number.toString()) + this.asset.value = temp + Log.i("FragmentBuy after setValue", this.asset.value!!.coins[ret].number.toString()) + return ret + } + + fun askCoin(code: String, price: Double, number: Double): CoinAsset? { + val temp = Asset(this.asset.value!!.krw, this.asset.value!!.coins) + val ret = temp.askCoin(code, price, number) + this.asset.value = temp + return ret + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/data/OrderBook.kt b/app/src/main/java/com/mobit/mobit/data/OrderBook.kt new file mode 100644 index 0000000..31a9ce6 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/data/OrderBook.kt @@ -0,0 +1,12 @@ +package com.mobit.mobit.data + +import java.io.Serializable + +data class OrderBook(val price: Double, val size: Double) : Serializable, Comparable { + override fun compareTo(other: OrderBook): Int { + return when (this.price > other.price) { + true -> 1 + false -> -1 + } + } +} diff --git a/app/src/main/java/com/mobit/mobit/data/Price.kt b/app/src/main/java/com/mobit/mobit/data/Price.kt new file mode 100644 index 0000000..4d9fc0d --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/data/Price.kt @@ -0,0 +1,39 @@ +package com.mobit.mobit.data + +import java.io.Serializable + +/* +openPrice : 시가 +highPrice : 고가 +lowPrice : 저가 +endPrice : 종가 +prevEndPrice : 전일 종가 +change : { ("EVEN", 보합), ("RISE", 상승), ("FALL", 하락) } +changePrice : 부호가 있는 변화액 +changeRate : 부호가 있는 변화율 +totalTradeVolume : 누적 거래량(UTC 0시 기준) +totalTradePrice : 누적 거래대금(UTC 0시 기준) +totalTradePrice24: 24시간 누적 거래대금 +highestWeekPrice : 52주 신고가 +highestWeekDate: 52주 신고가 달성일 "yyyy-MM-dd" +lowestWeekPrice : 52주 신저가 +lowestWeekDate: 52주 신저가 달성일 "yyyy-MM-dd" + */ +data class Price( + var realTimePrice: Double, + var openPrice: Double, + var highPrice: Double, + var lowPrice: Double, + var endPrice: Double, + var prevEndPrice: Double, + var change: String, + var changePrice: Double, + var changeRate: Double, + var totalTradeVolume: Double, + var totalTradePrice: Double, + var totalTradePrice24: Double, + var highestWeekPrice: Double, + var highestWeekDate: String, + var lowestWeekPrice: Double, + var lowestWeekDate: String +) : Serializable diff --git a/app/src/main/java/com/mobit/mobit/data/Transaction.kt b/app/src/main/java/com/mobit/mobit/data/Transaction.kt new file mode 100644 index 0000000..5dba47e --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/data/Transaction.kt @@ -0,0 +1,18 @@ +package com.mobit.mobit.data + +data class Transaction( + val code: String, // 코인 코드 + val name: String, // 코인 이름 + val time: String, // 체결시간 "yyyy-MM-ddThh:mm:ss" 형태로 저장함 (https://developer.android.com/reference/java/time/format/DateTimeFormatter#ISO_LOCAL_DATE_TIME) + val type: Int, // 매수 매도 여부 + val quantity: Double, // 거래 수량 + val unitPrice: Double, // 거래 단가 + val tradePrice: Double, // 거래 금액 + val fee: Double, // 수수료 + val totalPrice: Double // 정산 금액 +) { + companion object { + const val BID = 100 // 매수 + const val ASK = 200 // 매도 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/db/MyDBHelper.kt b/app/src/main/java/com/mobit/mobit/db/MyDBHelper.kt new file mode 100644 index 0000000..3c3debf --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/db/MyDBHelper.kt @@ -0,0 +1,344 @@ +package com.mobit.mobit.db + +import android.content.ContentValues +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.util.Log +import com.mobit.mobit.data.CoinAsset +import com.mobit.mobit.data.Transaction +import org.json.JSONObject + +class MyDBHelper(val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { + companion object { + val DB_NAME = "mobit.db" + val DB_VERSION = 1 + val TABLE_NAME = arrayOf("favorite", "krw", "coinAsset", "trade", "firstFlag") + + // about favorite + val CODE = "code" + + // about krw + val KRW = "krw" + + // about coinAsset + val NAME = "name" + val NUMBER = "number" + val AMOUNT = "amount" + val AVERAGE_PRICE = "averagePrice" + + // about transaction + val TRANSACTION = "trade" + + // about flag + val FIRST_SETTING = "firstSetting" + } + + // 관심 코인을 DB에 저장 + fun insertFavoirte(code: String): Boolean { + val values = ContentValues() + values.put(CODE, code) + + val db = writableDatabase + val ret = db.insert(TABLE_NAME[0], null, values) > 0 + db.close() + return ret + } + + // 관심 코인을 DB로부터 제거 + fun deleteFavorite(code: String): Boolean { + val strsql = "select * from ${TABLE_NAME[0]} where $CODE='$code';" + val db = readableDatabase + val cursor = db.rawQuery(strsql, null) + val flag = cursor.count != 0 + if (flag) { + cursor.moveToFirst() + db.delete(TABLE_NAME[0], "$CODE=?", arrayOf(code)) + } + cursor.close() + db.close() + return flag + } + + // DB에 저장되어 있는 관심 코인들의 code를 반환 + fun getFavorites(): ArrayList { + val ret = ArrayList() + + val strsql = "select * from ${TABLE_NAME[0]};" + val db = readableDatabase + val cursor = db.rawQuery(strsql, null) + cursor.moveToFirst() + if (cursor.count != 0) { + do { + val code = cursor.getString(0) + ret.add(code) + } while (cursor.moveToNext()) + } + cursor.close() + db.close() + + return ret + } + + // KRW 테이블에 저장되어 있는 데이터를 krw로 변경 + fun setKRW(krw: Double): Boolean { + clearKRW() + return insertKRW(krw) + } + + // KRW 테이블에 krw 데이터 추가 + fun insertKRW(krw: Double): Boolean { + val values = ContentValues() + values.put(KRW, krw) + + val db = writableDatabase + val ret = db.insert(TABLE_NAME[1], null, values) > 0 + db.close() + return ret + } + + // KRW 테이블의 데이터를 모두 제거 + fun clearKRW() { + val strsql = "delete from ${TABLE_NAME[1]}" + val db = writableDatabase + db.execSQL(strsql) + } + + // DB에 저장되어 있는 KRW 값을 반환 + fun getKRW(): Double? { + var ret: Double? = null + + val strsql = "select * from ${TABLE_NAME[1]};" + val db = readableDatabase + val cursor = db.rawQuery(strsql, null) + cursor.moveToFirst() + if (cursor.count != 0) { + ret = cursor.getDouble(0) + } + cursor.close() + db.close() + return ret + } + + // DB에 coinAsset을 저장 + fun insertCoinAsset(coinAsset: CoinAsset): Boolean { + val values = ContentValues() + values.put(CODE, coinAsset.code) + values.put(NAME, coinAsset.name) + values.put(NUMBER, coinAsset.number) + values.put(AMOUNT, coinAsset.amount) + values.put(AVERAGE_PRICE, coinAsset.averagePrice) + + val db = writableDatabase + val ret = db.insert(TABLE_NAME[2], null, values) > 0 + db.close() + return ret + } + + // DB에 있는 코인 자산 데이터 중에서 coinAsset에 해당하는 데이터를 삭제 + fun deleteCoinAsset(coinAsset: CoinAsset): Boolean { + val strsql = "select * from ${TABLE_NAME[2]} where $CODE='${coinAsset.code}';" + val db = readableDatabase + val cursor = db.rawQuery(strsql, null) + val flag = cursor.count != 0 + if (flag) { + cursor.moveToFirst() + db.delete(TABLE_NAME[2], "$CODE=?", arrayOf(coinAsset.code)) + } + cursor.close() + db.close() + return flag + } + + // DB에 있는 코인 자산 데이터를 업데이트 + fun updateCoinAsset(coinAsset: CoinAsset): Boolean { + val strsql = "select * from ${TABLE_NAME[2]} where $CODE='${coinAsset.code}';" + val db = writableDatabase + val cursor = db.rawQuery(strsql, null) + val flag = cursor.count != 0 + if (flag) { + cursor.moveToFirst() + val values = ContentValues() + values.put(NUMBER, coinAsset.number) + values.put(AMOUNT, coinAsset.amount) + values.put(AVERAGE_PRICE, coinAsset.averagePrice) + db.update(TABLE_NAME[2], values, "$CODE=?", arrayOf(coinAsset.code)) + } + cursor.close() + db.close() + return flag + } + + // DB에 저장되어 있는 코인 자산 정보를 반환 + fun getCoinAssets(): ArrayList { + val ret = ArrayList() + + val strsql = "select * from ${TABLE_NAME[2]};" + val db = readableDatabase + val cursor = db.rawQuery(strsql, null) + cursor.moveToFirst() + if (cursor.count != 0) { + do { + val code = cursor.getString(0) + val name = cursor.getString(1) + val number = cursor.getDouble(2) + val amount = cursor.getDouble(3) + val averagePrice = cursor.getDouble(4) + ret.add(CoinAsset(code, name, number, amount, averagePrice)) + } while (cursor.moveToNext()) + } + cursor.close() + db.close() + + return ret + } + + fun findCoinAsset(code: String): Boolean { + val strsql = "select * from ${TABLE_NAME[2]} where $CODE='$code';" + val db = readableDatabase + val cursor = db.rawQuery(strsql, null) + val ret = cursor.count != 0 + cursor.close() + db.close() + return ret + } + + // DB에 trasaction을 JSONObject 형태의 string으로 저장 + fun insertTransaction(transaction: Transaction): Boolean { + val values = ContentValues() + val json: String = createJSONObject(transaction) + values.put(TRANSACTION, json) + + val db = writableDatabase + val ret = db.insert(TABLE_NAME[3], null, values) > 0 + db.close() + return ret + } + + // DB에 저장되어 있는 거래내역 정보를 반환 + fun getTransactions(): ArrayList { + val ret = ArrayList() + + val strsql = "select * from ${TABLE_NAME[3]};" + val db = readableDatabase + val cursor = db.rawQuery(strsql, null) + cursor.moveToFirst() + if (cursor.count != 0) { + do { + val jsonObject = cursor.getString(0) + val transaction = createTransactionFromJSONObject(jsonObject) + ret.add(transaction) + } while (cursor.moveToNext()) + } + cursor.close() + db.close() + + ret.reverse() + return ret + } + + // Transaction 객체 하나를 JSONObject 형태로 바꾸고, 이를 String 타입으로 리턴한다. + fun createJSONObject(transaction: Transaction): String { + val jsonObject: JSONObject = JSONObject() + jsonObject.put("code", transaction.code) + jsonObject.put("name", transaction.name) + jsonObject.put("time", transaction.time) + jsonObject.put("type", transaction.type) + jsonObject.put("quantity", transaction.quantity) + jsonObject.put("unitPrice", transaction.unitPrice) + jsonObject.put("tradePrice", transaction.tradePrice) + jsonObject.put("fee", transaction.fee) + jsonObject.put("totalPrice", transaction.totalPrice) + return jsonObject.toString() + } + + fun createTransactionFromJSONObject(transaction: String): Transaction { + val jsonObject: JSONObject = JSONObject(transaction) + val code = jsonObject.getString("code") + val name = jsonObject.getString("name") + val time = jsonObject.getString("time") + val type = jsonObject.getInt("type") + val quantity = jsonObject.getDouble("quantity") + val unitPrice = jsonObject.getDouble("unitPrice") + val tradePrice = jsonObject.getDouble("tradePrice") + val fee = jsonObject.getDouble("fee") + val totalPrice = jsonObject.getDouble("totalPrice") + return Transaction(code, name, time, type, quantity, unitPrice, tradePrice, fee, totalPrice) + } + + fun setFlag(flag: Boolean): Boolean { + val values = ContentValues() + val num = if (flag) 1 else 0 + values.put(FIRST_SETTING, num) + + val db = writableDatabase + val ret = db.insert(TABLE_NAME[4], null, values) > 0 + db.close() + return ret + } + + fun getFlag(): Boolean { + var ret = false + + val strsql = "select * from ${TABLE_NAME[4]};" + val db = readableDatabase + val cursor = db.rawQuery(strsql, null) + cursor.moveToFirst() + if (cursor.count != 0) { + val num = cursor.getInt(0) + Log.i("getFlag num", num.toString()) + ret = if (num == 1) true else false + } + cursor.close() + db.close() + + return ret + } + + fun clearDB(): Boolean { + val strsql1 = "delete from ${TABLE_NAME[0]}" + val strsql2 = "delete from ${TABLE_NAME[1]}" + val strsql3 = "delete from ${TABLE_NAME[2]}" + val strsql4 = "delete from ${TABLE_NAME[3]}" + val strsql5 = "delete from ${TABLE_NAME[4]}" + + val db = writableDatabase + db.execSQL(strsql1) + db.execSQL(strsql2) + db.execSQL(strsql3) + db.execSQL(strsql4) + db.execSQL(strsql5) + + return true + } + + override fun onCreate(db: SQLiteDatabase?) { + val createTable1 = "create table if not exists ${TABLE_NAME[0]}($CODE text primary key)" + val createTable2 = + "create table if not exists ${TABLE_NAME[1]}($KRW real)" + val createTable3 = + "create table if not exists ${TABLE_NAME[2]}($CODE text primary key, $NAME text, $NUMBER real, $AMOUNT real, $AVERAGE_PRICE real)" + val createTable4 = "create table if not exists ${TABLE_NAME[3]}($TRANSACTION text)" + val createTable5 = "create table if not exists ${TABLE_NAME[4]}($FIRST_SETTING INTEGER)" + db?.execSQL(createTable1) + db?.execSQL(createTable2) + db?.execSQL(createTable3) + db?.execSQL(createTable4) + db?.execSQL(createTable5) + } + + override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { + val dropTable1 = "drop table if exists ${TABLE_NAME[0]}" + val dropTable2 = "drop table if exists ${TABLE_NAME[1]}" + val dropTable3 = "drop table if exists ${TABLE_NAME[2]}" + val dropTable4 = "drop table if exists ${TABLE_NAME[3]}" + val dropTable5 = "drop table if exists ${TABLE_NAME[4]}" + db?.execSQL(dropTable1) + db?.execSQL(dropTable2) + db?.execSQL(dropTable3) + db?.execSQL(dropTable4) + db?.execSQL(dropTable5) + onCreate(db) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/network/UpbitAPICaller.kt b/app/src/main/java/com/mobit/mobit/network/UpbitAPICaller.kt new file mode 100644 index 0000000..bd650ad --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/network/UpbitAPICaller.kt @@ -0,0 +1,249 @@ +package com.mobit.mobit.network + +import android.util.Log +import com.mobit.mobit.data.Candle +import com.mobit.mobit.data.OrderBook +import com.mobit.mobit.data.Price +import org.json.JSONArray +import java.io.BufferedReader +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL + + +/* +Upbit open API를 사용해서 데이터를 얻어오는 작업을 구현할 클래스입니다. +*/ +class UpbitAPICaller { + + companion object { + val TICK_URL = "https://api.upbit.com/v1/trades/ticks" + val TICKER_URL = "https://api.upbit.com/v1/ticker" + val ORDERBOOK_URL = "https://api.upbit.com/v1/orderbook" + val CANDLE_MINUTE_URL = "https://api.upbit.com/v1/candles/minutes" + val CANDLE_DAY_URL = "https://api.upbit.com/v1/candles/days" + val CANDLE_WEEK_URL = "https://api.upbit.com/v1/candles/weeks" + val CANDLE_MONTH_URL = "https://api.upbit.com/v1/candles/months" + } + + fun connect(connUrl: String): String { + var ret = "" + try { + val url = URL(connUrl) + val con = url.openConnection() as HttpURLConnection + con.requestMethod = "GET" + con.setRequestProperty("Accept", "application/json") + + val responseCode = con.responseCode + val br: BufferedReader + if (responseCode == 200) { + br = BufferedReader(InputStreamReader(con.inputStream)) + } else { + br = BufferedReader(InputStreamReader(con.errorStream)) + } + var inputLine: String? + val response = StringBuffer() + while (br.readLine().also { inputLine = it } != null) { + response.append(inputLine) + } + br.close() + ret += response.toString() + } catch (e: Exception) { + Log.e("connect Error", e.toString()) + return ret + } + + return ret + } + + // code에 해당하는 코인 현재가 정보를 가져오는 함수 + fun getTicker(codes: ArrayList): ArrayList { + val ret = ArrayList() + var markets = "?markets=" + for (i in codes.indices) { + markets += when (i) { + codes.size - 1 -> codes[i] + else -> "${codes[i]}, " + } + } + if (markets == "?markets=") + return ret + + val url = TICKER_URL + markets + val text = connect(url) + if (text.isBlank()) + return ret + + val jsonArray: JSONArray = JSONArray(text) + for (i in 0..jsonArray.length() - 1) { + val jsonObject = jsonArray.getJSONObject(i) + val openPrice = jsonObject.getDouble("opening_price") + val highPrice = jsonObject.getDouble("high_price") + val lowPrice = jsonObject.getDouble("low_price") + val endPrice = jsonObject.getDouble("trade_price") + val prevEndPrice = jsonObject.getDouble("prev_closing_price") + val change = jsonObject.getString("change") + val changePrice = jsonObject.getDouble("signed_change_price") + val changeRate = jsonObject.getDouble("signed_change_rate") + val totalTradeVolume = jsonObject.getDouble("acc_trade_volume") + val totalTradePrice = jsonObject.getDouble("acc_trade_price") + val totalTradePrice24 = jsonObject.getDouble("acc_trade_price_24h") + val highestWeekPrice = jsonObject.getDouble("highest_52_week_price") + val highestWeekDate = jsonObject.getString("highest_52_week_date") + val lowestWeekPrice = jsonObject.getDouble("lowest_52_week_price") + val lowestWeekDate = jsonObject.getString("lowest_52_week_date") + val price = Price( + endPrice, + openPrice, + highPrice, + lowPrice, + endPrice, + prevEndPrice, + change, + changePrice, + changeRate, + totalTradeVolume, + totalTradePrice, + totalTradePrice24, + highestWeekPrice, + highestWeekDate, + lowestWeekPrice, + lowestWeekDate + ) + ret.add(price) + } + + return ret + } + + // code에 해당하는 코인의 호가 정보를 가져오는 함수 + fun getOrderbook(code: String): ArrayList { + val ret = ArrayList() + + val markets = "?markets=$code" + val url = ORDERBOOK_URL + markets + val text = connect(url) + if (text.isBlank()) + return ret + + val orderbooks = JSONArray(text) + val jsonObject = orderbooks!!.getJSONObject(0) + val market = jsonObject.getString("market") + val orderbook = jsonObject.getJSONArray("orderbook_units") + val ask = ArrayList() + val bid = ArrayList() + // ask(매도)는 가격이 오름차순으로 정렬되어 있고, + // bid(매수)는 가격이 내림차순으로 정렬되어 있다. + for (j in 0..orderbook.length() - 1) { + val temp = orderbook.getJSONObject(j) + ask.add(OrderBook(temp.getDouble("ask_price"), temp.getDouble("ask_size"))) + bid.add(OrderBook(temp.getDouble("bid_price"), temp.getDouble("bid_size"))) + } + ask.reverse() + ret.addAll(ask) + ret.addAll(bid) + + return ret + } + + // code에 해당하는 코인의 unit분 단위의 캔들 차트 정보를 가져오는 함수 + fun getCandleMinute(code: String, unit: Int): ArrayList { + val ret = ArrayList() + + val query = "/$unit?market=$code&count=200" + val url = CANDLE_MINUTE_URL + query + val text = connect(url) + if (text.isBlank()) + return ret + + val candles = JSONArray(text) + for (i in 1..candles.length()) { + val candle = candles.getJSONObject(candles.length() - i) + val createdAt = i.toLong() + val open = candle.getDouble("opening_price").toFloat() + val close = candle.getDouble("trade_price").toFloat() + val shadowHigh = candle.getDouble("high_price").toFloat() + val shadowLow = candle.getDouble("low_price").toFloat() + val totalTradeVolume = candle.getDouble("candle_acc_trade_volume").toFloat() + ret.add(Candle(createdAt, open, close, shadowHigh, shadowLow, totalTradeVolume)) + } + + return ret + } + + // code에 해당하는 코인의 일 단위의 캔들 차트 정보를 가져오는 함수 + fun getCandleDay(code: String): ArrayList { + val ret = ArrayList() + + val query = "?market=$code&count=200" + val url = CANDLE_DAY_URL + query + val text = connect(url) + if (text.isBlank()) + return ret + + val candles = JSONArray(text) + for (i in 1..candles.length()) { + val candle = candles.getJSONObject(candles.length() - i) + val createdAt = i.toLong() + val open = candle.getDouble("opening_price").toFloat() + val close = candle.getDouble("trade_price").toFloat() + val shadowHigh = candle.getDouble("high_price").toFloat() + val shadowLow = candle.getDouble("low_price").toFloat() + val totalTradeVolume = candle.getDouble("candle_acc_trade_volume").toFloat() + ret.add(Candle(createdAt, open, close, shadowHigh, shadowLow, totalTradeVolume)) + } + + return ret + } + + // code에 해당하는 코인의 주 단위의 캔들 차트 정보를 가져오는 함수 + fun getCandleWeek(code: String): ArrayList { + val ret = ArrayList() + + val query = "?market=$code&count=200" + val url = CANDLE_WEEK_URL + query + val text = connect(url) + if (text.isBlank()) + return ret + + val candles = JSONArray(text) + for (i in 1..candles.length()) { + val candle = candles.getJSONObject(candles.length() - i) + val createdAt = i.toLong() + val open = candle.getDouble("opening_price").toFloat() + val close = candle.getDouble("trade_price").toFloat() + val shadowHigh = candle.getDouble("high_price").toFloat() + val shadowLow = candle.getDouble("low_price").toFloat() + val totalTradeVolume = candle.getDouble("candle_acc_trade_volume").toFloat() + ret.add(Candle(createdAt, open, close, shadowHigh, shadowLow, totalTradeVolume)) + } + + return ret + } + + // code에 해당하는 코인의 월 단위의 캔들 차트 정보를 가져오는 함수 + fun getCandleMonth(code: String): ArrayList { + val ret = ArrayList() + + val query = "?market=$code&count=200" + val url = CANDLE_MONTH_URL + query + val text = connect(url) + if (text.isBlank()) + return ret + + val candles = JSONArray(text) + for (i in 1..candles.length()) { + val candle = candles.getJSONObject(candles.length() - i) + val createdAt = i.toLong() + val open = candle.getDouble("opening_price").toFloat() + val close = candle.getDouble("trade_price").toFloat() + val shadowHigh = candle.getDouble("high_price").toFloat() + val shadowLow = candle.getDouble("low_price").toFloat() + val totalTradeVolume = candle.getDouble("candle_acc_trade_volume").toFloat() + ret.add(Candle(createdAt, open, close, shadowHigh, shadowLow, totalTradeVolume)) + } + + return ret + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mobit/mobit/service/UpbitAPIService.kt b/app/src/main/java/com/mobit/mobit/service/UpbitAPIService.kt new file mode 100644 index 0000000..7b849e9 --- /dev/null +++ b/app/src/main/java/com/mobit/mobit/service/UpbitAPIService.kt @@ -0,0 +1,233 @@ +package com.mobit.mobit.service + +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.IBinder +import android.util.Log +import com.mobit.mobit.data.CoinInfo +import com.mobit.mobit.network.UpbitAPICaller + +class UpbitAPIService : Service() { + + lateinit var selectedCoin: String + var favoriteCoinInfo: ArrayList = ArrayList() + + val upbitAPICaller: UpbitAPICaller = UpbitAPICaller() + + // 코인 정보 가져오는 쓰레드 + lateinit var upbitAPIThread: UpbitAPIThread + + // 코인 호가 정보 가져오는 쓰레드 + lateinit var upbitAPIThread2: UpbitAPIThread + + val codes: ArrayList = arrayListOf( + CoinInfo.BTC_CODE, + CoinInfo.ETH_CODE, + CoinInfo.ADA_CODE, + CoinInfo.DOGE_CODE, + CoinInfo.XRP_CODE, + CoinInfo.DOT_CODE, + CoinInfo.BCH_CODE, + CoinInfo.LTC_CODE, + CoinInfo.LINK_CODE, + CoinInfo.ETC_CODE, + CoinInfo.THETA_CODE, + CoinInfo.XLM_CODE, + CoinInfo.VET_CODE, + CoinInfo.EOS_CODE, + CoinInfo.TRX_CODE, + CoinInfo.NEO_CODE, + CoinInfo.IOTA_CODE, + CoinInfo.ATOM_CODE, + CoinInfo.BSV_CODE, + CoinInfo.BTT_CODE + ) + + var receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + APIControl(intent) + } + } + + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun onCreate() { + super.onCreate() + registerReceiver(receiver, IntentFilter("com.mobit.APICALL")) + + upbitAPIThread = UpbitAPIThread(100, codes) + upbitAPIThread2 = UpbitAPIThread(200, codes) + } + + override fun onDestroy() { + super.onDestroy() + unregisterReceiver(receiver) + } + + fun APIControl(intent: Intent?) { + if (intent != null) { + val mode = intent.getStringExtra("mode") + when (mode) { + "INITIAL_SETTING" -> { + selectedCoin = intent.getStringExtra("selectedCoin") + favoriteCoinInfo.clear() + favoriteCoinInfo.addAll(intent.getSerializableExtra("favoriteCoinInfo") as ArrayList) + } + "SELECTED_COIN_SETTING" -> { + selectedCoin = intent.getStringExtra("selectedCoin") + } + "FAVORITE_COININFO_SETTING" -> { + favoriteCoinInfo.clear() + favoriteCoinInfo.addAll(intent.getSerializableExtra("favoriteCoinInfo") as ArrayList) + } + "START" -> { + val thread: Thread = object : Thread() { + override fun run() { + if (upbitAPIThread.isAlive) { + try { + upbitAPIThread.join() + upbitAPIThread2.join() + } catch (e: InterruptedException) { + Log.e("OnRestart Error", e.toString()) + } + } + upbitAPIThread = UpbitAPIThread(100, codes) + upbitAPIThread.start() + upbitAPIThread2 = UpbitAPIThread(200, codes) + upbitAPIThread2.start() + } + } + thread.start() + } + "START_THREAD1" -> { + val thread: Thread = object : Thread() { + override fun run() { + if (upbitAPIThread.isAlive) { + try { + upbitAPIThread.join() + } catch (e: InterruptedException) { + Log.e("OnRestart Error", e.toString()) + } + } + upbitAPIThread = UpbitAPIThread(100, codes) + upbitAPIThread.start() + } + } + thread.start() + } + "START_THREAD2" -> { + val thread: Thread = object : Thread() { + override fun run() { + if (upbitAPIThread2.isAlive) { + try { + upbitAPIThread2.join() + } catch (e: InterruptedException) { + Log.e("OnRestart Error", e.toString()) + } + } + upbitAPIThread2 = UpbitAPIThread(200, codes) + upbitAPIThread2.start() + } + } + thread.start() + } + "STOP" -> { + upbitAPIThread.threadStop(true) + upbitAPIThread2.threadStop(true) + } + "STOP_THREAD1" -> { + upbitAPIThread.threadStop(true) + } + "STOP_THREAD2" -> { + upbitAPIThread2.threadStop(true) + } + } + } + } + + inner class UpbitAPIThread(var type: Int, val codes: ArrayList) : Thread() { + + var stopFlag: Boolean = false + + override fun run() { + while (!stopFlag) { + val intent = Intent("com.mobit.APIRECEIVE") + // 코인 정보 받아오기 + if (type == 100) { + intent.putExtra("mode", "CoinInfo") + val prices = upbitAPICaller.getTicker(codes) + if (prices.isNotEmpty()) { + val coinInfo = ArrayList() + for (i in prices.indices) { + coinInfo.add(CoinInfo(codes[i], getCoinName(codes[i]), prices[i])) + } + intent.putExtra("coinInfo", coinInfo) + intent.putExtra("isSuccess", true) + + val favoriteCoinInfo2 = ArrayList() + for (favorite in favoriteCoinInfo) { + for (coin in coinInfo) { + if (favorite.code == coin.code) { + favoriteCoinInfo2.add(coin) + break + } + } + } + intent.putExtra("favoriteCoinInfo", favoriteCoinInfo2) + } else { + intent.putExtra("isSuccess", false) + } + } + // 호가 정보 받아오기 + else if (type == 200) { + intent.putExtra("mode", "orderBook") + val orderBook = upbitAPICaller.getOrderbook(selectedCoin) + if (orderBook.isNotEmpty()) { + intent.putExtra("orderBook", orderBook) + intent.putExtra("isSuccess", true) + } else { + intent.putExtra("isSuccess", false) + } + } + + sendBroadcast(intent) + sleep(300) + } + } + + fun getCoinName(code: String): String { + return when (code) { + CoinInfo.BTC_CODE -> CoinInfo.BTC_NAME + CoinInfo.ETH_CODE -> CoinInfo.ETH_NAME + CoinInfo.ADA_CODE -> CoinInfo.ADA_NAME + CoinInfo.DOGE_CODE -> CoinInfo.DOGE_NAME + CoinInfo.XRP_CODE -> CoinInfo.XRP_NAME + CoinInfo.DOT_CODE -> CoinInfo.DOT_NAME + CoinInfo.BCH_CODE -> CoinInfo.BCH_NAME + CoinInfo.LTC_CODE -> CoinInfo.LTC_NAME + CoinInfo.LINK_CODE -> CoinInfo.LINK_NAME + CoinInfo.ETC_CODE -> CoinInfo.ETC_NAME + CoinInfo.THETA_CODE -> CoinInfo.THETA_NAME + CoinInfo.XLM_CODE -> CoinInfo.XLM_NAME + CoinInfo.VET_CODE -> CoinInfo.VET_NAME + CoinInfo.EOS_CODE -> CoinInfo.EOS_NAME + CoinInfo.TRX_CODE -> CoinInfo.TRX_NAME + CoinInfo.NEO_CODE -> CoinInfo.NEO_NAME + CoinInfo.IOTA_CODE -> CoinInfo.IOTA_NAME + CoinInfo.ATOM_CODE -> CoinInfo.ATOM_NAME + CoinInfo.BSV_CODE -> CoinInfo.BSV_NAME + CoinInfo.BTT_CODE -> CoinInfo.BTT_NAME + else -> CoinInfo.BTC_NAME + } + } + + fun threadStop(flag: Boolean) { + this.stopFlag = flag + } + } +} \ No newline at end of file diff --git a/app/src/main/res/color/coin_list_button_selector_color.xml b/app/src/main/res/color/coin_list_button_selector_color.xml new file mode 100644 index 0000000..1218601 --- /dev/null +++ b/app/src/main/res/color/coin_list_button_selector_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/navigation_menu_selector_color.xml b/app/src/main/res/color/navigation_menu_selector_color.xml new file mode 100644 index 0000000..ff8d0d6 --- /dev/null +++ b/app/src/main/res/color/navigation_menu_selector_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/transaction_buy_button_selector_color.xml b/app/src/main/res/color/transaction_buy_button_selector_color.xml new file mode 100644 index 0000000..438a8db --- /dev/null +++ b/app/src/main/res/color/transaction_buy_button_selector_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/transaction_info_button_selector_color.xml b/app/src/main/res/color/transaction_info_button_selector_color.xml new file mode 100644 index 0000000..abf4a70 --- /dev/null +++ b/app/src/main/res/color/transaction_info_button_selector_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/transaction_sell_button_selector_color.xml b/app/src/main/res/color/transaction_sell_button_selector_color.xml new file mode 100644 index 0000000..1218601 --- /dev/null +++ b/app/src/main/res/color/transaction_sell_button_selector_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asset_linearlayout_border.xml b/app/src/main/res/drawable/asset_linearlayout_border.xml new file mode 100644 index 0000000..ec2c3e1 --- /dev/null +++ b/app/src/main/res/drawable/asset_linearlayout_border.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asset_linearlayout_border_2.xml b/app/src/main/res/drawable/asset_linearlayout_border_2.xml new file mode 100644 index 0000000..1266c7e --- /dev/null +++ b/app/src/main/res/drawable/asset_linearlayout_border_2.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/asset_linearlayout_border_3.xml b/app/src/main/res/drawable/asset_linearlayout_border_3.xml new file mode 100644 index 0000000..fb9199e --- /dev/null +++ b/app/src/main/res/drawable/asset_linearlayout_border_3.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/coin_list_button_border.xml b/app/src/main/res/drawable/coin_list_button_border.xml new file mode 100644 index 0000000..5e5213e --- /dev/null +++ b/app/src/main/res/drawable/coin_list_button_border.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/coin_list_button_border_checked.xml b/app/src/main/res/drawable/coin_list_button_border_checked.xml new file mode 100644 index 0000000..14ffc0a --- /dev/null +++ b/app/src/main/res/drawable/coin_list_button_border_checked.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/coin_list_button_border_not_checked.xml b/app/src/main/res/drawable/coin_list_button_border_not_checked.xml new file mode 100644 index 0000000..caa87c3 --- /dev/null +++ b/app/src/main/res/drawable/coin_list_button_border_not_checked.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/coin_list_linearlayout_border.xml b/app/src/main/res/drawable/coin_list_linearlayout_border.xml new file mode 100644 index 0000000..ab47af6 --- /dev/null +++ b/app/src/main/res/drawable/coin_list_linearlayout_border.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/coin_list_recyclerview_item_background.xml b/app/src/main/res/drawable/coin_list_recyclerview_item_background.xml new file mode 100644 index 0000000..03bfa00 --- /dev/null +++ b/app/src/main/res/drawable/coin_list_recyclerview_item_background.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/coin_list_searchview_border.xml b/app/src/main/res/drawable/coin_list_searchview_border.xml new file mode 100644 index 0000000..9d00229 --- /dev/null +++ b/app/src/main/res/drawable/coin_list_searchview_border.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/first_setting_button_pressed_background.xml b/app/src/main/res/drawable/first_setting_button_pressed_background.xml new file mode 100644 index 0000000..b651a92 --- /dev/null +++ b/app/src/main/res/drawable/first_setting_button_pressed_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml new file mode 100644 index 0000000..3c43aeb --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_add_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_description_24.xml b/app/src/main/res/drawable/ic_baseline_description_24.xml new file mode 100644 index 0000000..e7ef3d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_description_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_home_24.xml b/app/src/main/res/drawable/ic_baseline_home_24.xml new file mode 100644 index 0000000..4c5e854 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_home_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_insert_chart_24.xml b/app/src/main/res/drawable/ic_baseline_insert_chart_24.xml new file mode 100644 index 0000000..21c76e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_insert_chart_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml new file mode 100644 index 0000000..4e8cf25 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml new file mode 100644 index 0000000..7b184de --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_remove_24.xml b/app/src/main/res/drawable/ic_baseline_remove_24.xml new file mode 100644 index 0000000..1e51d8b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_remove_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_search_24.xml b/app/src/main/res/drawable/ic_baseline_search_24.xml new file mode 100644 index 0000000..182087d --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_search_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_settings_24.xml b/app/src/main/res/drawable/ic_baseline_settings_24.xml new file mode 100644 index 0000000..b240b83 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_settings_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_sync_alt_24.xml b/app/src/main/res/drawable/ic_baseline_sync_alt_24.xml new file mode 100644 index 0000000..d4c1bf8 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_sync_alt_24.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_logo_background.xml b/app/src/main/res/drawable/ic_launcher_logo_background.xml new file mode 100644 index 0000000..ca3826a --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_logo_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_outline_description_24.xml b/app/src/main/res/drawable/ic_outline_description_24.xml new file mode 100644 index 0000000..7c671fc --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_description_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_home_24.xml b/app/src/main/res/drawable/ic_outline_home_24.xml new file mode 100644 index 0000000..b6db651 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_home_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_insert_chart_24.xml b/app/src/main/res/drawable/ic_outline_insert_chart_24.xml new file mode 100644 index 0000000..59a8638 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_insert_chart_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_settings_24.xml b/app/src/main/res/drawable/ic_outline_settings_24.xml new file mode 100644 index 0000000..c8123a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_settings_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_sync_alt_24.xml b/app/src/main/res/drawable/ic_outline_sync_alt_24.xml new file mode 100644 index 0000000..d4c1bf8 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_sync_alt_24.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/drawable/ic_round_star_24.xml b/app/src/main/res/drawable/ic_round_star_24.xml new file mode 100644 index 0000000..19d3bf2 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_star_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_star_border_24.xml b/app/src/main/res/drawable/ic_round_star_border_24.xml new file mode 100644 index 0000000..a84f4ec --- /dev/null +++ b/app/src/main/res/drawable/ic_round_star_border_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/menu_chart_selector.xml b/app/src/main/res/drawable/menu_chart_selector.xml new file mode 100644 index 0000000..bfe29ee --- /dev/null +++ b/app/src/main/res/drawable/menu_chart_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/menu_coinlist_selector.xml b/app/src/main/res/drawable/menu_coinlist_selector.xml new file mode 100644 index 0000000..8ac05a2 --- /dev/null +++ b/app/src/main/res/drawable/menu_coinlist_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/menu_investment_selector.xml b/app/src/main/res/drawable/menu_investment_selector.xml new file mode 100644 index 0000000..c795a4b --- /dev/null +++ b/app/src/main/res/drawable/menu_investment_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/menu_setting_selector.xml b/app/src/main/res/drawable/menu_setting_selector.xml new file mode 100644 index 0000000..88939ee --- /dev/null +++ b/app/src/main/res/drawable/menu_setting_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/menu_trade_selector.xml b/app/src/main/res/drawable/menu_trade_selector.xml new file mode 100644 index 0000000..e23f984 --- /dev/null +++ b/app/src/main/res/drawable/menu_trade_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/navigation_menu_selector_color.xml b/app/src/main/res/drawable/navigation_menu_selector_color.xml new file mode 100644 index 0000000..ff8d0d6 --- /dev/null +++ b/app/src/main/res/drawable/navigation_menu_selector_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/popup_buy_sell_button_border.xml b/app/src/main/res/drawable/popup_buy_sell_button_border.xml new file mode 100644 index 0000000..c643e96 --- /dev/null +++ b/app/src/main/res/drawable/popup_buy_sell_button_border.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/popup_buy_sell_linearlayout_border.xml b/app/src/main/res/drawable/popup_buy_sell_linearlayout_border.xml new file mode 100644 index 0000000..5f1c8a8 --- /dev/null +++ b/app/src/main/res/drawable/popup_buy_sell_linearlayout_border.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/record_linearlayout_border.xml b/app/src/main/res/drawable/record_linearlayout_border.xml new file mode 100644 index 0000000..6cad8ff --- /dev/null +++ b/app/src/main/res/drawable/record_linearlayout_border.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/splash.xml b/app/src/main/res/drawable/splash.xml new file mode 100644 index 0000000..cfd1d2f --- /dev/null +++ b/app/src/main/res/drawable/splash.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/splash_image.png b/app/src/main/res/drawable/splash_image.png new file mode 100644 index 0000000000000000000000000000000000000000..818042653e9dd04a7e6b4ec80e59b878e8be5754 GIT binary patch literal 4083 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 RZXbUFB{?)X{{R8qxN`si literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/transaction_button_background.xml b/app/src/main/res/drawable/transaction_button_background.xml new file mode 100644 index 0000000..71c09a7 --- /dev/null +++ b/app/src/main/res/drawable/transaction_button_background.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/transaction_button_background_checked.xml b/app/src/main/res/drawable/transaction_button_background_checked.xml new file mode 100644 index 0000000..24465a0 --- /dev/null +++ b/app/src/main/res/drawable/transaction_button_background_checked.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/transaction_button_background_not_checked.xml b/app/src/main/res/drawable/transaction_button_background_not_checked.xml new file mode 100644 index 0000000..17295f8 --- /dev/null +++ b/app/src/main/res/drawable/transaction_button_background_not_checked.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/transaction_linearlayout_border.xml b/app/src/main/res/drawable/transaction_linearlayout_border.xml new file mode 100644 index 0000000..ed7307a --- /dev/null +++ b/app/src/main/res/drawable/transaction_linearlayout_border.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_first_setting.xml b/app/src/main/res/layout/activity_first_setting.xml new file mode 100644 index 0000000..1679d0f --- /dev/null +++ b/app/src/main/res/layout/activity_first_setting.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +