├── app ├── .gitignore ├── src │ ├── main │ │ ├── gfd_logo-web.png │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── gfd_logo.png │ │ │ │ ├── icon_bio.png │ │ │ │ ├── gfd_app_logo.png │ │ │ │ ├── gfd_launcher.png │ │ │ │ ├── icon_location.png │ │ │ │ ├── btn_bottom_help.png │ │ │ │ ├── btn_bottom_home.png │ │ │ │ ├── btn_bottom_rank.png │ │ │ │ ├── gfd_logo_foreground.png │ │ │ │ ├── rounding_trans_background.xml │ │ │ │ ├── rect_background.xml │ │ │ │ ├── github_login_background.xml │ │ │ │ ├── rounding_background.xml │ │ │ │ ├── guest_login_background.xml │ │ │ │ ├── profile_gradation.xml │ │ │ │ ├── ic_close_gray.xml │ │ │ │ ├── github_login_users_background.xml │ │ │ │ ├── shadow.xml │ │ │ │ ├── gfd_logo_background.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── gfd_logo.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── gfd_logo_round.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ ├── gfd_logo_background.png │ │ │ │ └── gfd_logo_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── gfd_logo.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── gfd_logo_round.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ ├── gfd_logo_background.png │ │ │ │ └── gfd_logo_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── gfd_logo.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── gfd_logo_round.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ ├── gfd_logo_background.png │ │ │ │ └── gfd_logo_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── gfd_logo.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── gfd_logo_round.png │ │ │ │ ├── gfd_logo_background.png │ │ │ │ ├── gfd_logo_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── gfd_logo.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── gfd_logo_round.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ ├── gfd_logo_background.png │ │ │ │ └── gfd_logo_foreground.png │ │ │ ├── values │ │ │ │ ├── ic_launcher_background_gfd.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── colors.xml │ │ │ │ └── strings.xml │ │ │ ├── xml │ │ │ │ ├── backup_descriptor.xml │ │ │ │ └── network_security_config.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── gfd_logo.xml │ │ │ │ ├── gfd_logo_round.xml │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── font │ │ │ │ └── font.xml │ │ │ ├── anim │ │ │ │ ├── fade_in.xml │ │ │ │ ├── fade_out.xml │ │ │ │ ├── bottom_to_top.xml │ │ │ │ ├── left_to_right.xml │ │ │ │ ├── right_to_left.xml │ │ │ │ └── top_to_bottom.xml │ │ │ ├── menu │ │ │ │ └── bottom_navigation_menu.xml │ │ │ ├── layout │ │ │ │ ├── progressbar_dialog.xml │ │ │ │ ├── rowlayout.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── activity_launcher.xml │ │ │ │ ├── contributions_layout.xml │ │ │ │ ├── fragment_main_sub.xml │ │ │ │ ├── activity_main2.xml │ │ │ │ ├── item_search_github_id.xml │ │ │ │ ├── rv_rank_item.xml │ │ │ │ ├── activity_search.xml │ │ │ │ ├── fragment_main.xml │ │ │ │ └── fragment_rank.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── seok │ │ │ │ └── gfd │ │ │ │ ├── retrofit │ │ │ │ ├── domain │ │ │ │ │ ├── request │ │ │ │ │ │ ├── GUserRequestDto.kt │ │ │ │ │ │ └── CommitRequestDto.kt │ │ │ │ │ ├── MultiResponseDto.kt │ │ │ │ │ ├── TRCommitItem.kt │ │ │ │ │ ├── SingleResponseDto.kt │ │ │ │ │ ├── CommonResponseDto.kt │ │ │ │ │ ├── resopnse │ │ │ │ │ │ ├── CommitResponseDto.kt │ │ │ │ │ │ ├── UserCount.kt │ │ │ │ │ │ ├── CommitResponse.kt │ │ │ │ │ │ ├── CommitsResponseDto.kt │ │ │ │ │ │ └── NestedContributionsResponseDto.kt │ │ │ │ │ ├── GfdUser.kt │ │ │ │ │ ├── Token.kt │ │ │ │ │ ├── YearContributionDto.kt │ │ │ │ │ └── User.kt │ │ │ │ ├── repository │ │ │ │ │ ├── CommitsRepository.kt │ │ │ │ │ └── UserRepository.kt │ │ │ │ ├── service │ │ │ │ │ ├── GithubContributionService.kt │ │ │ │ │ ├── GithubApiService.kt │ │ │ │ │ ├── UserService.kt │ │ │ │ │ └── CommitService.kt │ │ │ │ └── RetrofitClient.kt │ │ │ │ ├── views │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── LauncherActivity.kt │ │ │ │ └── SearchActivity.kt │ │ │ │ ├── utils │ │ │ │ ├── ValidationCheck.kt │ │ │ │ ├── Contribution.kt │ │ │ │ ├── ProgressBarDialog.kt │ │ │ │ ├── GithubCrawler.kt │ │ │ │ └── SharedPreference.kt │ │ │ │ ├── room │ │ │ │ ├── converter │ │ │ │ │ └── DateConverter.kt │ │ │ │ ├── entity │ │ │ │ │ └── GithubId.kt │ │ │ │ ├── dao │ │ │ │ │ └── SearchGithubIdDao.kt │ │ │ │ └── AppDatabase.kt │ │ │ │ ├── database │ │ │ │ ├── Commits.kt │ │ │ │ ├── CommitsDatabaseDao.kt │ │ │ │ └── CommitsDatabase.kt │ │ │ │ ├── viewmodel │ │ │ │ ├── GithubIdViewModel.kt │ │ │ │ ├── GithubContributionViewModel.kt │ │ │ │ ├── RankFragmentViewModel.kt │ │ │ │ ├── GithubCommitDataViewModel.kt │ │ │ │ └── UserViewModel.kt │ │ │ │ ├── adapter │ │ │ │ ├── CustomAdapter.kt │ │ │ │ ├── GithubIdAdapter.kt │ │ │ │ ├── ContributionsAdapter.kt │ │ │ │ └── CommitsAdapter.kt │ │ │ │ └── v1 │ │ │ │ └── views │ │ │ │ ├── Main2Activity.kt │ │ │ │ ├── LoginActivity.kt │ │ │ │ ├── RankFragment.kt │ │ │ │ ├── MainFragment.kt │ │ │ │ ├── MainSub.kt │ │ │ │ ├── OptionFragment.kt │ │ │ │ └── GuestMain.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── seok │ │ │ └── gfd │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── seok │ │ └── gfd │ │ ├── ExampleInstrumentedTest.kt │ │ └── room │ │ └── dao │ │ └── GithubIdDaoTest.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── img ├── preview01.png ├── preview02.png ├── preview03.png ├── preview04.png ├── preview05.png ├── preview06.png ├── preview07.png ├── preview1_01.png ├── preview1_02.png ├── preview1_03.png ├── preview1_1_01.png ├── preview1_1_02.png └── preview1_1_03.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── encodings.xml ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── compiler.xml ├── vcs.xml ├── render.experimental.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml ├── gradle.xml ├── jarRepositories.xml └── navEditor.xml ├── Makefile ├── gradle.properties ├── README.md ├── .gitignore ├── .travis.yml ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /img/preview01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview01.png -------------------------------------------------------------------------------- /img/preview02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview02.png -------------------------------------------------------------------------------- /img/preview03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview03.png -------------------------------------------------------------------------------- /img/preview04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview04.png -------------------------------------------------------------------------------- /img/preview05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview05.png -------------------------------------------------------------------------------- /img/preview06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview06.png -------------------------------------------------------------------------------- /img/preview07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview07.png -------------------------------------------------------------------------------- /img/preview1_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview1_01.png -------------------------------------------------------------------------------- /img/preview1_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview1_02.png -------------------------------------------------------------------------------- /img/preview1_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview1_03.png -------------------------------------------------------------------------------- /img/preview1_1_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview1_1_01.png -------------------------------------------------------------------------------- /img/preview1_1_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview1_1_02.png -------------------------------------------------------------------------------- /img/preview1_1_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/img/preview1_1_03.png -------------------------------------------------------------------------------- /app/src/main/gfd_logo-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/gfd_logo-web.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/gfd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/drawable/gfd_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_bio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/drawable/icon_bio.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/gfd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-hdpi/gfd_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/gfd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-mdpi/gfd_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/gfd_app_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/drawable/gfd_app_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/gfd_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/drawable/gfd_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/drawable/icon_location.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/gfd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xhdpi/gfd_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/gfd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxhdpi/gfd_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/gfd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxxhdpi/gfd_logo.png -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/request/GUserRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain.request 2 | 3 | class GUserRequestDto { 4 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_bottom_help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/drawable/btn_bottom_help.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_bottom_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/drawable/btn_bottom_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_bottom_rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/drawable/btn_bottom_rank.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/gfd_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/drawable/gfd_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/gfd_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-hdpi/gfd_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/gfd_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-mdpi/gfd_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/gfd_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xhdpi/gfd_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/gfd_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxhdpi/gfd_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/gfd_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxxhdpi/gfd_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/gfd_logo_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-hdpi/gfd_logo_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/gfd_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-hdpi/gfd_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/gfd_logo_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-mdpi/gfd_logo_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/gfd_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-mdpi/gfd_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/gfd_logo_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xhdpi/gfd_logo_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/gfd_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xhdpi/gfd_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/gfd_logo_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxhdpi/gfd_logo_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/gfd_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxhdpi/gfd_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/gfd_logo_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxxhdpi/gfd_logo_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/gfd_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msnodeve/Github-for-Developer/HEAD/app/src/main/res/mipmap-xxxhdpi/gfd_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background_gfd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/MultiResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain 2 | 3 | class MultiResponseDto: CommonResponseDto() { 4 | var list: List? = null 5 | } -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/render.experimental.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/TRCommitItem.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain 2 | 3 | class TRCommitItem( 4 | val userProfile: String, 5 | val userId : String, 6 | val dataCount : Int 7 | ) -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_descriptor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/SingleResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain 2 | 3 | import lombok.Getter 4 | import lombok.Setter 5 | 6 | @Getter 7 | @Setter 8 | class SingleResponseDto : CommonResponseDto() { 9 | var data: T? = null 10 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 01 01:23:26 KST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounding_trans_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/gfd_logo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/font/font.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/gfd_logo_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rect_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/CommonResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain 2 | 3 | import lombok.Getter 4 | import lombok.Setter 5 | 6 | @Setter 7 | @Getter 8 | open class CommonResponseDto { 9 | var success: Boolean? = false 10 | 11 | var code : Int? = 0 12 | 13 | var msg : String? = "" 14 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/github_login_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounding_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/guest_login_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10.0.2.2 5 | 52.78.188.192 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/profile_gradation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/test/java/com/seok/gfd/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * Example local unit test, which will execute on the development machine (host). 7 | * 8 | * See [testing documentation](http://d.android.com/tools/testing). 9 | */ 10 | class ExampleUnitTest { 11 | @Test 12 | fun envTest(){ 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/resopnse/CommitResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain.resopnse 2 | 3 | import lombok.AllArgsConstructor 4 | import lombok.Getter 5 | import lombok.NoArgsConstructor 6 | import lombok.Setter 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | class CommitResponseDto { 13 | var data: Object? = null 14 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/views/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.views 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.seok.gfd.R 6 | 7 | class MainActivity : AppCompatActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_main) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_gray.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/utils/ValidationCheck.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.utils 2 | 3 | class ValidationCheck { 4 | companion object { 5 | fun validIsEmptyString(str: String): Boolean { 6 | return str.isEmpty() || str == "" 7 | } 8 | 9 | fun isExistSite(str: String): Boolean{ 10 | val url = "https://github.com/$str" 11 | 12 | 13 | 14 | return true 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/room/converter/DateConverter.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.room.converter 2 | 3 | import androidx.room.TypeConverter 4 | import java.util.* 5 | 6 | class DateConverter { 7 | @TypeConverter 8 | fun fromTimestamp(value: Long?): Date? { 9 | return value?.let { Date(it) } 10 | } 11 | 12 | @TypeConverter 13 | fun dateToTimestamp(date: Date?): Long? { 14 | return date?.time?.toLong() 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/resopnse/UserCount.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain.resopnse 2 | 3 | import lombok.AllArgsConstructor 4 | import lombok.Getter 5 | import lombok.NoArgsConstructor 6 | import lombok.Setter 7 | 8 | @Setter 9 | @Getter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | class UserCount { 13 | var success: String = "" 14 | var code: Int = 0 15 | var msg: String = "" 16 | var data :Long = 0 17 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/github_login_users_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/database/Commits.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.database 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "commits") 8 | data class Commits( 9 | @PrimaryKey 10 | @ColumnInfo(name = "data_date") 11 | val dataDate: String, 12 | 13 | @ColumnInfo(name = "data_count") 14 | val dataCount: Int, 15 | 16 | @ColumnInfo(name = "fill") 17 | val fill: String 18 | ) -------------------------------------------------------------------------------- /app/src/main/res/anim/bottom_to_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/anim/left_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/anim/right_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/anim/top_to_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/resopnse/CommitResponse.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain.resopnse 2 | 3 | import lombok.AllArgsConstructor 4 | import lombok.Getter 5 | import lombok.NoArgsConstructor 6 | import lombok.Setter 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | class CommitResponse { 13 | var user_url: String = "" 14 | 15 | var data_date: String = "" 16 | 17 | var data_count: Int = 0 18 | 19 | var user_image: String = "" 20 | 21 | var user_id: String = "" 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/request/CommitRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain.request 2 | 3 | import lombok.AllArgsConstructor 4 | import lombok.Getter 5 | import lombok.NoArgsConstructor 6 | import lombok.Setter 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | class CommitRequestDto { 13 | var userId: String = "" 14 | var dataCount: Int = 0 15 | 16 | constructor(userId: String, dataCount: Int) { 17 | this.userId = userId 18 | this.dataCount = dataCount 19 | } 20 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | set_env: 2 | echo "GITHUB_CLIENT_ID=\"client_id\"" >> app/gradle.properties 3 | echo "GITHUB_CLIENT_SECRET=\"client_secret\"" >> app/gradle.properties 4 | echo "REDIRECT_CALLBACK_URL=\"gfd://github.for.developer\"" >> app/gradle.properties 5 | echo "PREFERENCES_FILE=\"file_name\"" >> app/gradle.properties 6 | echo "PREFERENCES_TOKEN_KEY=\"token_key\"" >> app/gradle.properties 7 | echo "BASIC_AUTH_KEY=\"basic_auth_key\"" >> app/gradle.properties 8 | echo "GFD_API_URL=\"gfd_api_url\"" >> app/gradle.properties 9 | echo "GITHUB_OAUTH_URL=\"gfd_oauth_url\"" >> app/gradle.properties -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/room/entity/GithubId.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.room.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import java.util.* 7 | 8 | private const val TABLE_NAME = "search_github_id" 9 | 10 | @Entity(tableName = TABLE_NAME) 11 | data class GithubId( 12 | @ColumnInfo(name = "github_id") val githubId: String? 13 | ) { 14 | @PrimaryKey(autoGenerate = true) 15 | var id: Int = 0 16 | @ColumnInfo(name = "created", defaultValue = "CURRENT_TIMESTAMP") 17 | var created: Date? = Date() 18 | } -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/GfdUser.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain 2 | 3 | import lombok.AllArgsConstructor 4 | import lombok.Getter 5 | import lombok.NoArgsConstructor 6 | import lombok.Setter 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | class GfdUser { 13 | var userId: String = "" 14 | 15 | var userUrl: String = "" 16 | 17 | var userImage: String = "" 18 | 19 | constructor(userId : String, userUrl : String, userImage: String){ 20 | this.userId = userId 21 | this.userUrl = userUrl 22 | this.userImage = userImage 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/repository/CommitsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.repository 2 | 3 | import android.app.Application 4 | import com.seok.gfd.database.Commits 5 | import com.seok.gfd.database.CommitsDatabase 6 | import com.seok.gfd.database.CommitsDatabaseDao 7 | 8 | class CommitsRepository(application: Application) { 9 | private val commitsDatabaseDao : CommitsDatabaseDao 10 | 11 | init{ 12 | val commitsDatabase = CommitsDatabase.getInstance(application) 13 | commitsDatabaseDao = commitsDatabase.commitsDatabaseDao() 14 | } 15 | 16 | fun insert(commits: Commits){ 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/utils/Contribution.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.utils 2 | 3 | import lombok.AllArgsConstructor 4 | import lombok.Getter 5 | import lombok.NoArgsConstructor 6 | import lombok.Setter 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | class Contribution { 13 | var year : String? = null 14 | var total : Int? = 0 15 | var list : ArrayList? = ArrayList() 16 | 17 | class ContributionInfo(date : String, count: Int, color : String, intensity: Int){ 18 | var date : String = date 19 | var count : Int = count 20 | var color : String = color 21 | var intensity : Int = intensity 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/seok/gfd/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | 5 | import org.junit.Test 6 | import org.junit.runner.RunWith 7 | 8 | /** 9 | * Instrumented test, which will execute on an Android device. 10 | * 11 | * See [testing documentation](http://d.android.com/tools/testing). 12 | */ 13 | @RunWith(AndroidJUnit4::class) 14 | class ExampleInstrumentedTest { 15 | @Test 16 | fun useAppContext() { 17 | // Context of the app under test. 18 | // val appContext = InstrumentationRegistry.getTargetContext() 19 | // assertEquals("com.seok.gitfordeveloper", appContext.packageName) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/Token.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain 2 | 3 | import lombok.AllArgsConstructor 4 | import lombok.Getter 5 | import lombok.Setter 6 | 7 | @Getter 8 | @Setter 9 | @AllArgsConstructor 10 | class Token { 11 | var access_token : String = "" 12 | var scope : String = "" 13 | var token_type : String = "" 14 | var code : Int = 0 15 | constructor(accessToken : String, scope: String, tokenType : String, code : Int){ 16 | this.access_token = accessToken 17 | this.scope = scope 18 | this.token_type = tokenType 19 | this.code = code 20 | } 21 | constructor(code : Int){ 22 | this.code = code 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/service/GithubContributionService.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.service 2 | 3 | import com.seok.gfd.retrofit.domain.resopnse.CommitsResponseDto 4 | import com.seok.gfd.retrofit.domain.resopnse.NestedContributionsResponseDto 5 | import retrofit2.Call 6 | import retrofit2.http.GET 7 | import retrofit2.http.Path 8 | import retrofit2.http.Query 9 | 10 | interface GithubContributionService { 11 | @GET("{userId}") 12 | fun getContributions(@Path("userId") userId: String): Call 13 | 14 | @GET("{userId}") 15 | fun getNestedContributions( 16 | @Path("userId") userId: String, 17 | @Query("format") format: String 18 | ): Call 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/utils/ProgressBarDialog.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.utils 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.graphics.Color 6 | import android.graphics.drawable.ColorDrawable 7 | import com.seok.gfd.R 8 | 9 | class ProgressbarDialog(context: Context){ 10 | private val dialog = Dialog(context) 11 | 12 | init{ 13 | dialog.setContentView(R.layout.progressbar_dialog) 14 | dialog.window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 15 | dialog.setCanceledOnTouchOutside(false) 16 | } 17 | fun show(){ 18 | dialog.show() 19 | } 20 | fun hide(){ 21 | dialog.hide() 22 | dialog.dismiss() 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/database/CommitsDatabaseDao.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.database 2 | 3 | import androidx.room.* 4 | 5 | @Dao 6 | interface CommitsDatabaseDao{ 7 | @Insert(onConflict = OnConflictStrategy.REPLACE) 8 | fun insert(commits : Commits) 9 | 10 | @Update 11 | fun update(commits: Commits) 12 | 13 | @Query("SELECT * FROM commits ORDER BY data_count DESC LIMIT 1") 14 | fun maxCommit() : Commits 15 | 16 | @Query("SELECT * FROM commits") 17 | fun getAllCommits() : List 18 | 19 | @Query("SELECT * FROM commits WHERE data_date = :dataDate") 20 | fun getCommits(dataDate: String) : Commits 21 | 22 | @Query("SELECT * FROM commits ORDER BY data_date DESC") 23 | fun getYearCommits() : List 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/views/LauncherActivity.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.views 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.os.Handler 6 | import androidx.appcompat.app.AppCompatActivity 7 | import com.seok.gfd.R 8 | 9 | class LauncherActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_launcher) 14 | 15 | startLoading() 16 | } 17 | 18 | private fun startLoading(){ 19 | Handler().postDelayed({ 20 | val intent = Intent(this, SearchActivity::class.java) 21 | startActivity(intent) 22 | finish() 23 | }, 50) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/YearContributionDto.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain 2 | 3 | import com.seok.gfd.retrofit.domain.resopnse.CommitsResponseDto 4 | import lombok.AllArgsConstructor 5 | import lombok.Getter 6 | import lombok.NoArgsConstructor 7 | import lombok.Setter 8 | 9 | @Getter 10 | @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | class YearContributionDto { 14 | var year: String? = null 15 | 16 | var contributions: ArrayList? = null 17 | 18 | constructor() { 19 | this.contributions = ArrayList() 20 | } 21 | 22 | class Contribution{ 23 | var date : String = "" 24 | var count : Int = 0 25 | var color : String = "" 26 | var intensity : Int = 0 27 | } 28 | 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/bottom_navigation_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/service/GithubApiService.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.service 2 | 3 | import com.seok.gfd.retrofit.domain.SingleResponseDto 4 | import com.seok.gfd.retrofit.domain.Token 5 | import com.seok.gfd.retrofit.domain.User 6 | import retrofit2.Call 7 | import retrofit2.http.* 8 | 9 | interface GithubApiService { 10 | @GET("user") 11 | fun getUserInfoFromGithubApi(@Header("Authorization") token: String): Call 12 | 13 | @Headers("Accept: application/json") 14 | @POST("access_token") 15 | fun getAccessTokenFromGithubApi( 16 | @Query("client_id") clientId : String, 17 | @Query("client_secret") clientSecret : String, 18 | @Query("code") code : String) : Call 19 | 20 | @GET("users/{userId}") 21 | fun getUserInfo( 22 | @Path("userId") userId: String 23 | ):Call 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/service/UserService.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.service 2 | 3 | import com.seok.gfd.retrofit.domain.GfdUser 4 | import com.seok.gfd.retrofit.domain.SingleResponseDto 5 | import retrofit2.Call 6 | import retrofit2.http.Body 7 | import retrofit2.http.GET 8 | import retrofit2.http.Header 9 | import retrofit2.http.POST 10 | 11 | interface UserService { 12 | @GET("users") 13 | fun getUserList( 14 | @Header("Authorization") authKey: String 15 | ): Call> 16 | 17 | @POST("users") 18 | fun signUpUser( 19 | @Header("Authorization") authKey: String, 20 | @Body gfdUser: GfdUser 21 | ): Call> 22 | 23 | @GET("users/count") 24 | fun getUsersCount( 25 | @Header("Authorization") authKey: String 26 | ): Call> 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/progressbar_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | android.useAndroidX=true 17 | android.enableJetifier=true -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/room/dao/SearchGithubIdDao.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.room.dao 2 | 3 | import androidx.room.* 4 | import com.seok.gfd.room.entity.GithubId 5 | 6 | private const val TABLE_NAME = "search_github_id" 7 | 8 | @Dao 9 | interface GithubIdDao { 10 | @Insert(onConflict = OnConflictStrategy.REPLACE) 11 | suspend fun insert(githubId: GithubId): Long 12 | 13 | @Delete 14 | suspend fun delete(githubId: GithubId) 15 | 16 | @Query("SELECT * FROM $TABLE_NAME WHERE github_id LIKE '%' || :githubId || '%' ORDER BY created DESC") 17 | suspend fun selectAll(githubId: String): List 18 | 19 | @Query("SELECT * FROM $TABLE_NAME ORDER BY created DESC") 20 | suspend fun selectAll(): List 21 | 22 | @Query("SELECT COUNT(*) FROM $TABLE_NAME WHERE github_id = :githubId") 23 | suspend fun selectById(githubId: String): Int 24 | 25 | @Query("delete from $TABLE_NAME") 26 | suspend fun deleteAll() 27 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/rowlayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/resopnse/CommitsResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain.resopnse 2 | 3 | import lombok.AllArgsConstructor 4 | import lombok.Getter 5 | import lombok.NoArgsConstructor 6 | import lombok.Setter 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | class CommitsResponseDto { 13 | 14 | var year : String? = null 15 | 16 | var years: List? = null 17 | 18 | var contributions: List? = null 19 | 20 | constructor(year: String?) { 21 | this.year = year 22 | this.contributions = ArrayList() 23 | } 24 | 25 | 26 | class Year{ 27 | var year: String = "" 28 | var total: Int = 0 29 | var range : Range? = null 30 | 31 | class Range{ 32 | var start: String = "" 33 | var end: String = "" 34 | } 35 | } 36 | 37 | class Contribution{ 38 | var date : String = "" 39 | var count : Int = 0 40 | var color : String = "" 41 | var intensity : Int = 0 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/utils/GithubCrawler.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.utils 2 | 3 | import com.seok.gfd.database.Commits 4 | import org.jsoup.Jsoup 5 | import org.jsoup.nodes.Element 6 | 7 | class GithubCrawler { 8 | 9 | fun getCommitCrawler(url:String) : ArrayList { 10 | val doc = Jsoup.connect(url).get() 11 | val regex = """fill=\"#[a-zA-Z0-9]{6}\" data-count=\"\d+\" data-date=\"([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))\"""".toRegex() 12 | val element = doc.select("g rect") 13 | val commits = ArrayList() 14 | for(e : Element in element) { 15 | val matchResult = regex.find(e.toString())?.value?.replace("\"", "") 16 | val split: List = matchResult?.split(" ")!! 17 | commits.add( 18 | Commits( 19 | split[2].split("=")[1], 20 | split[1].split("=")[1].toInt(), 21 | split[0].split("=")[1] 22 | ) 23 | ) 24 | } 25 | return commits 26 | } 27 | 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/database/CommitsDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.database 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | 8 | @Database(entities = [Commits::class], version = 1, exportSchema = false) 9 | abstract class CommitsDatabase : RoomDatabase(){ 10 | abstract fun commitsDatabaseDao(): CommitsDatabaseDao 11 | companion object{ 12 | @Volatile 13 | private var INSTANCE: CommitsDatabase? = null 14 | 15 | fun getInstance(context: Context): CommitsDatabase{ 16 | synchronized(this){ 17 | var instance = INSTANCE 18 | if (instance == null){ 19 | instance = Room.databaseBuilder( 20 | context.applicationContext, 21 | CommitsDatabase::class.java, 22 | "commits_db") 23 | .fallbackToDestructiveMigration() 24 | .build() 25 | INSTANCE = instance 26 | } 27 | return instance 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Github For Developer 2 | ![Build Status](https://travis-ci.org/msnodeve/Github-for-Developer.svg?branch=master) 3 | 4 | 다운로드하기 Google Play 5 | 6 | *** 7 |

8 | 9 | # Release 10 | 11 | v3.4.3 릴리즈 [Google Paly Store](https://play.google.com/store/apps/details?id=com.seok.gfd) 12 | 13 |

14 | 15 | # Preview 16 | 17 | 18 | 19 | 20 | 21 |

22 | 23 | # Notice 24 | 25 | v1.6.2 릴리즈 작업 완료 26 | 27 | - Backend 작업 28 | - UX/UI 개선 작업 29 | - 자동 로그인 구현 30 | 31 | v2.4.1 릴리즈 작업 완료 32 | 33 | - 금일 커밋 랭킹 서비스 구축 34 | - 프로필, 랭킹 프래그먼트로 분리 35 | 36 | v2.10.4 릴리즈 작업 완료 37 | 38 | - 대대적인 UI/UX 개선 39 | - 도움을 주신 분 탭 추가 40 | - 앱 성능 최적화 41 | 42 | v2.11.1 43 | 44 | - Github Contribution 그래프 UI 변경 45 | - 랭킹 서비스 오류로 인해 수정 진행 중 46 | 47 | v3.4.3 48 | 49 | - Guest 사용자 서비스 50 | - UI 추가 -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #239a3b 5 | #D81B60 6 | 7 | 8 | #ebedf0 9 | #c6e48b 10 | #7bc96f 11 | #239a3b 12 | #196127 13 | 14 | 15 | #282828 16 | #444444 17 | #e5e1e1 18 | #e4f9f5 19 | 20 | 21 | #00b1e5 22 | #08e68d 23 | 24 | 25 | #0F0F0F 26 | #898989 27 | #646464 28 | #C8C8C8 29 | #3842FF 30 | #38EEFF 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/service/CommitService.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.service 2 | 3 | import com.seok.gfd.retrofit.domain.MultiResponseDto 4 | import com.seok.gfd.retrofit.domain.SingleResponseDto 5 | import com.seok.gfd.retrofit.domain.resopnse.CommitResponse 6 | import com.seok.gfd.retrofit.domain.request.CommitRequestDto 7 | import com.seok.gfd.retrofit.domain.resopnse.CommitResponseDto 8 | import retrofit2.Call 9 | import retrofit2.http.* 10 | 11 | interface CommitService { 12 | @GET("commits") 13 | fun getCommitsRank( 14 | @Header("Authorization") authKey: String, 15 | @Query("page") page: Int, 16 | @Query("size") size: Int 17 | ): Call 18 | 19 | @GET("commits/{dataDate}") 20 | fun getCommitList( 21 | @Header("Authorization") authKey: String, 22 | @Path("dataDate") dataDate: String 23 | ): Call> 24 | @POST("commits") 25 | fun enrollCommit( 26 | @Header("Authorization") authKey: String, 27 | @Body commitRequestDto: CommitRequestDto 28 | ): Call> 29 | 30 | @GET("trc/{date}") 31 | fun getTRCommitList( 32 | @Header("Authorization") authKey: String, 33 | @Path("date") date: String 34 | ): Call> 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/room/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.room 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import androidx.room.TypeConverters 8 | import com.seok.gfd.room.converter.DateConverter 9 | import com.seok.gfd.room.dao.GithubIdDao 10 | import com.seok.gfd.room.entity.GithubId 11 | 12 | @Database(entities = [GithubId::class], version = 2) 13 | @TypeConverters(DateConverter::class) 14 | abstract class AppDatabase : RoomDatabase() { 15 | abstract fun githubIdDao(): GithubIdDao 16 | 17 | companion object { 18 | private var INSTANCE: AppDatabase? = null 19 | 20 | fun getInstance(context: Context): AppDatabase { 21 | if (INSTANCE == null) { 22 | synchronized(AppDatabase::class) { 23 | INSTANCE = Room.databaseBuilder( 24 | context.applicationContext, 25 | AppDatabase::class.java, "cata.db" 26 | ) 27 | .fallbackToDestructiveMigration() 28 | .build() 29 | } 30 | } 31 | return INSTANCE!! 32 | } 33 | 34 | fun destroyInstance(){ 35 | INSTANCE = null 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/resopnse/NestedContributionsResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain.resopnse 2 | 3 | import lombok.AllArgsConstructor 4 | import lombok.Getter 5 | import lombok.NoArgsConstructor 6 | import lombok.Setter 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | class NestedContributionsResponseDto { 13 | 14 | var years: HashMap? = null 15 | 16 | var contributions : Contributions? = null 17 | 18 | inner class Years{ 19 | var year: String? = null 20 | var total : Int? = 0 21 | var range: Range? = null 22 | 23 | inner class Range{ 24 | var start: String = "" 25 | var end: String = "" 26 | } 27 | } 28 | 29 | inner class Contributions{ 30 | var year: String? = null 31 | var total : Int? = 0 32 | var range: Range? = null 33 | var contributions : HashMap>>? = null 34 | 35 | inner class Range{ 36 | var start: String = "" 37 | var end: String = "" 38 | } 39 | 40 | inner class Contribution{ 41 | var date : String = "" 42 | var count : Int = 0 43 | var color : String = "" 44 | var intensity : Int = 0 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | app/gradle.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # IntelliJ 38 | *.iml 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/gradle.xml 42 | .idea/assetWizardSettings.xml 43 | .idea/dictionaries 44 | .idea/libraries 45 | .idea/caches 46 | 47 | # Keystore files 48 | # Uncomment the following line if you do not want to check your keystore files in. 49 | #*.jks 50 | 51 | # External native build folder generated in Android Studio 2.2 and later 52 | .externalNativeBuild 53 | 54 | # Google Services (e.g. APIs or Firebase) 55 | google-services.json 56 | 57 | # Freeline 58 | freeline.py 59 | freeline/ 60 | freeline_project_description.json 61 | 62 | # fastlane 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | fastlane/readme.md 68 | 69 | # mac 70 | .DS_Store 71 | .idea/runConfigurations.xml 72 | 73 | .settings 74 | release 75 | .project 76 | .classpath 77 | 78 | network_security_config.xml -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/utils/SharedPreference.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.utils 2 | 3 | import android.app.Application 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.google.gson.GsonBuilder 6 | import com.seok.gfd.BuildConfig 7 | import com.seok.gfd.R 8 | import com.seok.gfd.retrofit.domain.User 9 | 10 | class SharedPreference(private val application: Application) { 11 | private val pref = application.getSharedPreferences( 12 | "", 13 | AppCompatActivity.MODE_PRIVATE 14 | ) 15 | 16 | private val editor = pref.edit() 17 | 18 | fun setValue(key: String, value: String) { 19 | editor.putString(key, value) 20 | editor.commit() 21 | } 22 | 23 | fun getValue(key: String): String { 24 | return if(pref.contains(key)){ 25 | @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") 26 | pref.getString(key, application.getString(R.string.no_token)) 27 | }else{ 28 | application.getString(R.string.no_token) 29 | } 30 | } 31 | 32 | fun setValueObject(key: String, user:User){ 33 | val gson = GsonBuilder().create() 34 | // 객체 -> json 저장 35 | val userInfoJson = gson.toJson(user, User::class.java) 36 | editor.putString(key, userInfoJson) 37 | editor.commit() 38 | } 39 | 40 | fun getValueObject(key:String):User{ 41 | val gson = GsonBuilder().create() 42 | val user = pref.getString(key, null) 43 | // json -> 객체 변환 44 | return gson.fromJson(user, User::class.java) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/viewmodel/GithubIdViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.viewmodel 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import com.seok.gfd.room.AppDatabase 8 | import com.seok.gfd.room.entity.GithubId 9 | import kotlinx.coroutines.runBlocking 10 | 11 | class GithubIdViewModel(val context: Application) : AndroidViewModel(context) { 12 | private val TAG = this.javaClass.toString() 13 | private val database = AppDatabase.getInstance(context) 14 | 15 | private val _githubIds = MutableLiveData>() 16 | 17 | val githubIds: LiveData> 18 | get() = _githubIds 19 | 20 | fun insertGithubId(githubId: GithubId) { 21 | runBlocking { 22 | if (database.githubIdDao().selectById(githubId.githubId.toString()) == 0) { 23 | database.githubIdDao().insert(githubId) 24 | } 25 | } 26 | } 27 | 28 | fun getGithubId(name: String) { 29 | runBlocking { 30 | if (name == "" || name.isEmpty()) { 31 | _githubIds.value = database.githubIdDao().selectAll() 32 | } else { 33 | _githubIds.value = database.githubIdDao().selectAll(name) 34 | } 35 | } 36 | } 37 | 38 | fun deleteGithubId(githubId: GithubId) { 39 | runBlocking { 40 | database.githubIdDao().delete(githubId) 41 | } 42 | } 43 | 44 | fun closeDatabase() { 45 | database.close() 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/adapter/CustomAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.ImageView 8 | import android.widget.TextView 9 | import android.widget.Toast 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.seok.gfd.R 12 | 13 | class CustomAdapter(personNames : ArrayList, personImages : ArrayList, context: Context) : RecyclerView.Adapter() { 14 | var personNames =personNames 15 | var personImages : ArrayList = personImages 16 | var context: Context = context 17 | 18 | override fun getItemCount() = personNames.size 19 | 20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { 21 | val v = LayoutInflater.from(parent.context).inflate(R.layout.rowlayout, parent, false) 22 | return MyViewHolder(v) 23 | } 24 | 25 | 26 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) { 27 | holder.name.text = personNames[position] 28 | holder.image.setImageResource(personImages[position]) 29 | holder.itemView.setOnClickListener { 30 | Toast.makeText(context, personNames[position], Toast.LENGTH_SHORT).show() 31 | } 32 | } 33 | 34 | inner class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){ 35 | var name : TextView = itemView.findViewById(R.id.name) 36 | var image : ImageView = itemView.findViewById(R.id.image) 37 | } 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/contributions_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 20 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/seok/gfd/room/dao/GithubIdDaoTest.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.room.dao 2 | 3 | import androidx.room.Room 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | import androidx.test.platform.app.InstrumentationRegistry 6 | import com.seok.gfd.room.AppDatabase 7 | import com.seok.gfd.room.entity.GithubId 8 | import kotlinx.coroutines.runBlocking 9 | import org.junit.After 10 | import org.junit.Before 11 | 12 | import org.junit.Assert.* 13 | import org.junit.Test 14 | import org.junit.runner.RunWith 15 | 16 | @RunWith(AndroidJUnit4::class) 17 | class GithubIdDaoTest { 18 | private lateinit var db : AppDatabase 19 | 20 | @Before 21 | fun createDb() { 22 | val context = InstrumentationRegistry.getInstrumentation().targetContext 23 | assertEquals("com.seok.gfd", context.packageName) 24 | 25 | db = Room.inMemoryDatabaseBuilder( 26 | context, 27 | AppDatabase::class.java 28 | ).build() 29 | } 30 | 31 | @After 32 | fun closeDb() = runBlocking { 33 | db.githubIdDao().deleteAll() 34 | db.close() 35 | } 36 | 37 | @Test 38 | fun iWantToKnowTheDatabaseIsFind() = runBlocking{ 39 | val searchGithubId = GithubId(githubId = "github") 40 | 41 | db.githubIdDao().insert(searchGithubId) 42 | var entity = db.githubIdDao().selectAll("g")[0] 43 | assertHabitEquals(searchGithubId, entity) 44 | 45 | db.githubIdDao().delete(entity) 46 | assertEquals(0, db.githubIdDao().selectAll("g").size) 47 | } 48 | 49 | private fun assertHabitEquals(expected: GithubId, actual: GithubId) { 50 | // id 는 자동생성되므로, 검증을 위해서 id의 동일성은 무시 51 | assertEquals(expected.copy(), actual.copy()) 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - openjdk8 5 | 6 | before_cache: 7 | # Do not cache a few Gradle files/directories (see https://docs.travis-ci.com/user/languages/java/#Caching) 8 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 9 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 10 | 11 | cache: 12 | directories: 13 | # Android SDK 14 | - $HOME/android-sdk-dl 15 | - $HOME/android-sdk 16 | 17 | # Gradle dependencies 18 | - $HOME/.gradle/caches/ 19 | - $HOME/.gradle/wrapper/ 20 | 21 | # Android build cache (see http://tools.android.com/tech-docs/build-cache) 22 | - $HOME/.android/build-cache 23 | 24 | install: 25 | # Download and unzip the Android SDK tools (if not already there thanks to the cache mechanism) 26 | # Latest version available here: https://developer.android.com/studio/#command-tools 27 | - if test ! -e $HOME/android-sdk-dl/sdk-tools.zip ; then curl https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip > $HOME/android-sdk-dl/sdk-tools.zip ; fi 28 | - unzip -qq -n $HOME/android-sdk-dl/sdk-tools.zip -d $HOME/android-sdk 29 | 30 | # Install or update Android SDK components (will not do anything if already up to date thanks to the cache mechanism) 31 | - echo y | $HOME/android-sdk/tools/bin/sdkmanager 'platform-tools' > /dev/null 32 | - echo y | $HOME/android-sdk/tools/bin/sdkmanager 'build-tools;28.0.3' > /dev/null 33 | - echo y | $HOME/android-sdk/tools/bin/sdkmanager 'platforms;android-28' > /dev/null 34 | 35 | # https://github.com/kt3k/coveralls-gradle-plugin 36 | env: 37 | - ANDROID_HOME=$HOME/android-sdk 38 | 39 | before_script: 40 | - echo $CI_NAME 41 | - echo $CI_BUILD_NUMBER 42 | - echo $CI_BUILD_URL 43 | - echo $CI_BRANCH 44 | - make set_env 45 | 46 | script: 47 | - ./gradlew --no-daemon --parallel lintDebug testDebug 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main_sub.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 13 | 14 | 18 | 19 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/domain/User.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.domain 2 | 3 | import lombok.Getter 4 | import lombok.NoArgsConstructor 5 | import lombok.Setter 6 | 7 | @Getter 8 | @Setter 9 | @NoArgsConstructor 10 | class User { 11 | var login: String = "" 12 | var id: Long = 0 13 | var node_id: String = "" 14 | var avatar_url: String = "" 15 | var gravatar_id: String = "" 16 | var url: String = "" 17 | var html_url: String = "" 18 | var followers_url: String = "" 19 | var following_url: String = "" 20 | var gists_url: String = "" 21 | var starred_url: String = "" 22 | var subscriptions_url: String = "" 23 | var organizations_url: String = "" 24 | var repos_url: String = "" 25 | var events_url: String = "" 26 | var receivedEvents_url: String = "" 27 | var type: String = "" 28 | var site_admin: Boolean = false 29 | var name: String = "" 30 | var company: String = "" 31 | var blog: String = "" 32 | var location: String = "" 33 | var email: Any? = null 34 | var hireable: Any? = null 35 | var bio: String = "" 36 | var public_repos: Long = 0 37 | var public_gists: Long = 0 38 | var followers: Long = 0 39 | var following: Long = 0 40 | var created_at: String = "" 41 | var updated_at: String = "" 42 | var code: Int = 0 43 | 44 | constructor(login: String, html_url: String, avatar_url: String, code : Int) { 45 | this.login = login 46 | this.html_url = html_url 47 | this.avatar_url = avatar_url 48 | this.code = code 49 | } 50 | constructor(login: String, html_url: String, avatar_url: String, id: Long, code : Int) { 51 | this.login = login 52 | this.html_url = html_url 53 | this.avatar_url = avatar_url 54 | this.id = id 55 | this.code = code 56 | } 57 | constructor(login: String, html_url: String, avatar_url: String){ 58 | this.login = login 59 | this.html_url = html_url 60 | this.avatar_url = avatar_url 61 | } 62 | constructor(code : Int){ 63 | this.code = code 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main2.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 30 | 31 | 32 | 33 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/adapter/GithubIdAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.EditText 7 | import androidx.fragment.app.FragmentActivity 8 | import androidx.lifecycle.ViewModelProviders 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.seok.gfd.R 11 | import com.seok.gfd.room.entity.GithubId 12 | import com.seok.gfd.viewmodel.GithubIdViewModel 13 | import kotlinx.android.synthetic.main.item_search_github_id.view.* 14 | 15 | class GithubIdAdapter(list: ArrayList, edtText : EditText) : RecyclerView.Adapter() { 16 | private val githubIds: ArrayList = list 17 | private val searchEditText = edtText 18 | private lateinit var githubIdsViewModel: GithubIdViewModel 19 | 20 | inner class SearchGithubIdViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ 21 | fun bind(githubId: GithubId){ 22 | itemView.item_search_txt_github_id.text = githubId.githubId 23 | itemView.item_search_img_close.setOnClickListener { 24 | githubIdsViewModel.deleteGithubId(githubId) 25 | githubIds.removeAt(this.adapterPosition) 26 | notifyItemRemoved(this.adapterPosition) 27 | notifyDataSetChanged() 28 | } 29 | itemView.item_search_card_view.setOnClickListener { 30 | searchEditText.setText(githubId.githubId) 31 | } 32 | } 33 | } 34 | 35 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 36 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_search_github_id, parent, false) 37 | githubIdsViewModel = ViewModelProviders.of(parent.context as FragmentActivity).get(GithubIdViewModel::class.java) 38 | return SearchGithubIdViewHolder(view) 39 | } 40 | 41 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 42 | val viewHolder = holder as SearchGithubIdViewHolder 43 | viewHolder.bind(githubIds[position]) 44 | } 45 | 46 | override fun getItemCount(): Int { 47 | return githubIds.size 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/v1/views/Main2Activity.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.v1.views 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.fragment.app.Fragment 6 | import com.google.android.gms.ads.AdRequest 7 | import com.google.android.gms.ads.MobileAds 8 | import com.seok.gfd.R 9 | import kotlinx.android.synthetic.main.activity_main2.* 10 | 11 | class Main2Activity : AppCompatActivity() { 12 | private val fragmentManager = supportFragmentManager 13 | 14 | private val mainFragment = MainFragment() 15 | // private val rankFragment = RankFragment() 16 | private val optionFragment = OptionFragment() 17 | 18 | private var activeFragment : Fragment = mainFragment 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | setContentView(R.layout.activity_main2) 23 | 24 | fragmentManager.beginTransaction().add(R.id.host_fragment, mainFragment, "1").commit() 25 | // fragmentManager.beginTransaction().add(R.id.host_fragment, rankFragment, "2").hide(rankFragment).commit() 26 | fragmentManager.beginTransaction().add(R.id.host_fragment, optionFragment, "3").hide(optionFragment).commit() 27 | bottom_navigation.setOnNavigationItemSelectedListener { 28 | when(it.itemId){ 29 | R.id.nav_menu_m -> { 30 | fragmentManager.beginTransaction().hide(activeFragment).show(mainFragment).commit() 31 | activeFragment = mainFragment 32 | } 33 | // R.id.nav_menu_r -> { 34 | // fragmentManager.beginTransaction().hide(activeFragment).show(rankFragment).commit() 35 | // activeFragment = rankFragment 36 | // } 37 | R.id.nav_menu_o -> { 38 | fragmentManager.beginTransaction().hide(activeFragment).show(optionFragment).commit() 39 | activeFragment = optionFragment 40 | } 41 | } 42 | true 43 | } 44 | 45 | MobileAds.initialize( 46 | this.application, 47 | getString(R.string.admob_app_id) 48 | ) 49 | val adRequest = AdRequest.Builder().build() 50 | github_login_ads_view.loadAd(adRequest) 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_search_github_id.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 23 | 24 | 35 | 36 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/RetrofitClient.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit 2 | 3 | import com.google.gson.GsonBuilder 4 | import com.seok.gfd.BuildConfig 5 | import com.seok.gfd.retrofit.service.UserService 6 | import com.seok.gfd.retrofit.service.CommitService 7 | import com.seok.gfd.retrofit.service.GithubApiService 8 | import com.seok.gfd.retrofit.service.GithubContributionService 9 | import retrofit2.Retrofit 10 | import retrofit2.converter.gson.GsonConverterFactory 11 | 12 | class RetrofitClient { 13 | companion object { 14 | lateinit var retrofit: Retrofit 15 | fun githubApiService(): GithubApiService { 16 | val gson = GsonBuilder().setLenient().create() 17 | retrofit = Retrofit.Builder() 18 | .baseUrl("https://api.github.com/") 19 | .addConverterFactory(GsonConverterFactory.create(gson)) 20 | .build() 21 | return retrofit.create(GithubApiService::class.java) 22 | } 23 | fun githubAuthService(): GithubApiService{ 24 | retrofit = Retrofit.Builder() 25 | .baseUrl("https://github.com/login/oauth/") 26 | .addConverterFactory(GsonConverterFactory.create()) 27 | .build() 28 | return retrofit.create(GithubApiService::class.java) 29 | } 30 | fun userService() : UserService{ 31 | val gson = GsonBuilder().setLenient().create() 32 | retrofit = Retrofit.Builder() 33 | .baseUrl("") 34 | .addConverterFactory(GsonConverterFactory.create(gson)) 35 | .build() 36 | return retrofit.create(UserService::class.java) 37 | } 38 | fun commitService() : CommitService{ 39 | val gson = GsonBuilder().setLenient().create() 40 | retrofit = Retrofit.Builder() 41 | .baseUrl("") 42 | .addConverterFactory(GsonConverterFactory.create(gson)) 43 | .build() 44 | return retrofit.create(CommitService::class.java) 45 | } 46 | fun githubContributionService(): GithubContributionService{ 47 | val gson = GsonBuilder().setLenient().create() 48 | retrofit = Retrofit.Builder() 49 | .baseUrl("https://github-contributions-api.now.sh/v1/") 50 | .addConverterFactory(GsonConverterFactory.create(gson)) 51 | .build() 52 | return retrofit.create(GithubContributionService::class.java) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/viewmodel/GithubContributionViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.viewmodel 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import com.seok.gfd.retrofit.RetrofitClient 8 | import com.seok.gfd.retrofit.domain.resopnse.CommitsResponseDto 9 | import com.seok.gfd.retrofit.domain.resopnse.NestedContributionsResponseDto 10 | import retrofit2.Call 11 | import retrofit2.Response 12 | 13 | class GithubContributionViewModel : ViewModel() { 14 | private val _contributions = MutableLiveData() 15 | private val _nestedContributions = MutableLiveData() 16 | 17 | val contributions: LiveData 18 | get() = _contributions 19 | val nestedContributions: LiveData 20 | get() = _nestedContributions 21 | 22 | 23 | fun getContributions(userId: String) { 24 | val getContributionService = RetrofitClient.githubContributionService() 25 | val getContributionCall = getContributionService.getContributions(userId) 26 | getContributionCall.enqueue(object : retrofit2.Callback { 27 | 28 | override fun onResponse( 29 | call: Call, 30 | response: Response 31 | ) { 32 | _contributions.value = response.body() 33 | } 34 | 35 | override fun onFailure(call: Call, t: Throwable) { 36 | Log.e(this.javaClass.simpleName, t.message.toString()) 37 | } 38 | }) 39 | } 40 | 41 | fun getNestedContributions(userId: String) { 42 | val nestedContributionService = RetrofitClient.githubContributionService() 43 | val nestedContributionCall = 44 | nestedContributionService.getNestedContributions(userId, "nested") 45 | nestedContributionCall.enqueue(object : retrofit2.Callback { 46 | 47 | override fun onResponse( 48 | call: Call, 49 | response: Response 50 | ) { 51 | val t = response.body() 52 | t 53 | } 54 | 55 | override fun onFailure(call: Call, t: Throwable) { 56 | Log.e(this.javaClass.simpleName, t.message.toString()) 57 | } 58 | }) 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/res/layout/rv_rank_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 31 | 32 | 43 | 44 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Daily Commit - 잔디 3 | 4 | 5 | Daily Commit - 잔디\n환영합니다 6 | Daily Commit - 잔디앱에 오신것을 환영합니다.\n로그인 버튼을 눌러주세요 7 | 로그인 토큰 발급을 실패하였습니다\n다시 시도 해주세요 8 | Today 9 | Max 10 | Year 11 | Home 12 | Rank 13 | Help 14 | SUN 15 | MON 16 | TUE 17 | WEN 18 | THU 19 | FRI 20 | SAT 21 | User ID 22 | https://github/gfd 23 | 금일 컨트리뷰션 랭킹 24 | 0 25 | 26 | 0 27 | Ctb 28 | 0 29 | gfd 30 | 0 31 | Github 32 | Login 33 | 누적 회원 수 34 | 도움을 주신 분 35 | 36 | 37 | 38 | empty token 39 | Empty key 40 | Empty user id 41 | Empty user url 42 | Empty user image 43 | user_id 44 | user_url 45 | user_today 46 | user_year 47 | user_max 48 | user_image 49 | user_gid 50 | user_info 51 | 52 | 53 | https://api.github.com/ 54 | https://github.com/login/oauth/authorize/ 55 | "https://github.com/login/oauth/" 56 | 57 | 58 | ca-app-pub-2766237193476584~2069127933 59 | ca-app-pub-2766237193476584/5214166022 60 | ca-app-pub-3940256099942544/6300978111 61 | 62 | 확인 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /.idea/navEditor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 82 | 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/v1/views/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.v1.views 2 | 3 | 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import android.os.Handler 8 | import android.view.View 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.lifecycle.Observer 11 | import androidx.lifecycle.ViewModelProviders 12 | import com.seok.gfd.R 13 | import com.seok.gfd.utils.SharedPreference 14 | import com.seok.gfd.viewmodel.UserViewModel 15 | import kotlinx.android.synthetic.main.activity_login.* 16 | import java.net.HttpURLConnection 17 | 18 | class LoginActivity : AppCompatActivity() { 19 | 20 | private lateinit var userViewModel: UserViewModel 21 | private lateinit var sharedPreference: SharedPreference 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | setContentView(R.layout.activity_login) 26 | 27 | init() 28 | initViewModelFun() 29 | setOnClickFun() 30 | 31 | } 32 | 33 | // ViewModel 세팅 및 초기화 34 | private fun init() { 35 | sharedPreference = SharedPreference(application) 36 | userViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java) 37 | userViewModel.getUsersCount() 38 | } 39 | 40 | // ViewModel 구현 41 | private fun initViewModelFun() { 42 | // 현재 사용자 수 43 | userViewModel.userCount.observe(this, Observer { 44 | login_tv_users_count.text = it.toString() 45 | }) 46 | // Github 접속을 위한 access_token 요청 47 | userViewModel.accessToken.observe(this, Observer { 48 | sharedPreference.setValue("", it) 49 | userViewModel.getUserInfoAndSignInGithub(it) 50 | }) 51 | // Github 로그인 성공 코드 200 / 401 52 | userViewModel.code.observe(this, Observer { 53 | if (it == HttpURLConnection.HTTP_OK) { 54 | goToMainActivity() 55 | } else { 56 | login_progress_bar.visibility = View.INVISIBLE 57 | } 58 | }) 59 | userViewModel.userInfo.observe(this, Observer { 60 | sharedPreference.setValueObject(application.getString(R.string.user_info), it) 61 | }) 62 | } 63 | 64 | override fun onNewIntent(intent: Intent?) { 65 | super.onNewIntent(intent) 66 | val uri = intent!!.data 67 | // 1회용 접속 Code 68 | val code = uri.toString().split("=")[1] 69 | userViewModel.getAccessTokenFromGithubApi(code) 70 | } 71 | 72 | private fun goToMainActivity() { 73 | startActivity(Intent(this, Main2Activity::class.java)) 74 | overridePendingTransition(R.anim.fade_in, R.anim.fade_out) 75 | } 76 | 77 | private fun setOnClickFun(){ 78 | // Guest 로그인 버튼을 눌렀을 경우 79 | login_img_guest.setOnClickListener { 80 | syncUI() 81 | startActivity(Intent(this, GuestMain::class.java)) 82 | overridePendingTransition(R.anim.fade_in, R.anim.fade_out) 83 | } 84 | 85 | // 로그인 버튼 눌렀을 경우 Github login 창으로 넘김 86 | login_img_login.setOnClickListener { 87 | syncUI() 88 | val intent = Intent( 89 | Intent.ACTION_VIEW, 90 | Uri.parse("") 91 | ) 92 | // onNewIntent() 리다이렉트 93 | startActivityForResult(intent, HttpURLConnection.HTTP_OK) 94 | } 95 | } 96 | 97 | private fun syncUI(){ 98 | login_progress_bar.visibility = View.VISIBLE 99 | login_img_login.isClickable = false 100 | login_img_guest.isClickable = false 101 | Handler().postDelayed({ 102 | login_progress_bar.visibility = View.INVISIBLE 103 | login_img_login.isClickable = true 104 | login_img_guest.isClickable = true 105 | }, 3000) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/v1/views/RankFragment.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.v1.views 2 | 3 | 4 | import android.annotation.SuppressLint 5 | import android.os.Bundle 6 | import android.util.Log 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.fragment.app.Fragment 11 | import androidx.lifecycle.Observer 12 | import androidx.lifecycle.ViewModelProviders 13 | import androidx.recyclerview.widget.LinearLayoutManager 14 | import androidx.recyclerview.widget.RecyclerView 15 | import com.bumptech.glide.Glide 16 | import com.bumptech.glide.request.RequestOptions 17 | import com.seok.gfd.R 18 | import com.seok.gfd.adapter.CommitsAdapter 19 | import com.seok.gfd.retrofit.domain.resopnse.CommitResponse 20 | import com.seok.gfd.utils.SharedPreference 21 | import com.seok.gfd.viewmodel.UserViewModel 22 | import kotlinx.android.synthetic.main.fragment_rank.* 23 | import java.lang.Exception 24 | 25 | /** 26 | * A simple [Fragment] subclass. 27 | */ 28 | class RankFragment : Fragment() { 29 | private lateinit var userViewModel: UserViewModel 30 | private lateinit var linearLayoutManager: LinearLayoutManager 31 | private lateinit var adapter: CommitsAdapter 32 | private var lastViesibleItemPosition: Int = 0 33 | get() = linearLayoutManager.findLastVisibleItemPosition() 34 | 35 | private lateinit var sharedPreference: SharedPreference 36 | 37 | override fun onCreateView( 38 | inflater: LayoutInflater, 39 | container: ViewGroup?, 40 | savedInstanceState: Bundle? 41 | ): View? { 42 | // Inflate the layout for this fragment 43 | return inflater.inflate(R.layout.fragment_rank, container, false) 44 | } 45 | 46 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 47 | super.onViewCreated(view, savedInstanceState) 48 | init() 49 | initViewModelFun() 50 | setRecyclerViewScrollListener() 51 | } 52 | 53 | private fun setRecyclerViewScrollListener() { 54 | rv_rank.addOnScrollListener(object : RecyclerView.OnScrollListener() { 55 | override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { 56 | super.onScrollStateChanged(recyclerView, newState) 57 | val totalItemCount = rv_rank.layoutManager!!.itemCount 58 | } 59 | }) 60 | } 61 | 62 | @SuppressLint("NewApi") 63 | private fun init() { 64 | sharedPreference = SharedPreference(this.activity!!.application) 65 | linearLayoutManager = LinearLayoutManager(this.context) 66 | userViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java) 67 | 68 | val user = sharedPreference.getValueObject(getString(R.string.user_info)) 69 | Glide.with(this).load(user.avatar_url).apply(RequestOptions.circleCropTransform()) 70 | .into(img_rv_profile) 71 | user.avatar_url 72 | rv_rank.layoutManager = linearLayoutManager 73 | tv_rv_commit.text = sharedPreference.getValue(getString(R.string.user_today)) 74 | userViewModel.getCommitsRank() 75 | 76 | img_rank_sync.setOnClickListener { 77 | userViewModel.getCommitsRank() 78 | } 79 | 80 | } 81 | 82 | private fun initViewModelFun() { 83 | val user = sharedPreference.getValueObject(getString(R.string.user_info)) 84 | userViewModel.commitList.observe(this, Observer { 85 | try { 86 | val user = it.find { it.user_id == user.login } 87 | Glide.with(this).load(user?.user_image).apply(RequestOptions.circleCropTransform()) 88 | .into(img_rv_profile) 89 | tv_rv_commit.text = user?.data_count.toString() 90 | adapter = CommitsAdapter(it as ArrayList) 91 | rv_rank.adapter = adapter 92 | } catch (e: Exception) { 93 | Log.e(this.javaClass.simpleName, e.toString()) 94 | } 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/retrofit/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.retrofit.repository 2 | 3 | import android.app.Application 4 | import android.util.Log 5 | import androidx.lifecycle.MutableLiveData 6 | import com.seok.gfd.BuildConfig 7 | import com.seok.gfd.retrofit.RetrofitClient 8 | import com.seok.gfd.retrofit.domain.GfdUser 9 | import com.seok.gfd.retrofit.domain.Token 10 | import com.seok.gfd.retrofit.domain.User 11 | import retrofit2.Call 12 | import retrofit2.Callback 13 | import retrofit2.Response 14 | import java.net.HttpURLConnection 15 | 16 | class UserRepository(application: Application) { 17 | private lateinit var user: User 18 | private lateinit var token: Token 19 | private val userMutableData = MutableLiveData() 20 | private val codeMutableData = MutableLiveData() 21 | private var application: Application = application 22 | 23 | fun getUserMutableData(token: String): MutableLiveData { 24 | val userService = RetrofitClient.githubApiService() 25 | val call = userService.getUserInfoFromGithubApi("token $token") 26 | // call.enqueue(object : Callback { 27 | // override fun onResponse(call: Call, response: Response) { 28 | // if (response.isSuccessful) { 29 | // val body = response.body()!! 30 | // user = 31 | // User(body.login, body.html_url, body.avatar_url, HttpURLConnection.HTTP_OK) 32 | // val signUpService = RetrofitClient.userService() 33 | // val signUpCall = signUpService.signUpUser( 34 | // BuildConfig.BASIC_AUTH_KEY 35 | // , GfdUser(body.login, body.html_url, body.avatar_url) 36 | // ) 37 | // signUpCall.enqueue(object :Callback{ 38 | // override fun onResponse(call: Call, response: Response) { 39 | // Log.d(this.javaClass.simpleName, "Success signUp") 40 | // } 41 | // override fun onFailure(call: Call, t: Throwable) { 42 | // Log.e(this.javaClass.simpleName, t.message.toString()) 43 | // } 44 | // }) 45 | // } else { 46 | // user = User(HttpURLConnection.HTTP_UNAUTHORIZED) 47 | // } 48 | // userMutableData.value = user 49 | // } 50 | // 51 | // override fun onFailure(call: Call, t: Throwable) { 52 | // Log.e(this.javaClass.simpleName, t.message.toString()) 53 | // } 54 | // }) 55 | return userMutableData 56 | } 57 | 58 | // fun getCodeMutableData( 59 | // clientId: String, 60 | // clientSecret: String, 61 | // code: String 62 | // ): MutableLiveData { 63 | // val codeService = RetrofitClient.githubAuthService() 64 | // val call = codeService.getAccessTokenFromGithubApi(clientId, clientSecret, code) 65 | // call.enqueue(object : Callback { 66 | // override fun onResponse(call: Call, response: Response) { 67 | // token = if (response.isSuccessful) { 68 | // val body = response.body()!! 69 | // Token( 70 | // body.access_token, 71 | // body.scope, 72 | // body.token_type, 73 | // HttpURLConnection.HTTP_OK 74 | // ) 75 | // } else { 76 | // Token(HttpURLConnection.HTTP_NOT_FOUND) 77 | // } 78 | // codeMutableData.value = token 79 | // } 80 | // 81 | // override fun onFailure(call: Call, t: Throwable) { 82 | // Log.e(this.javaClass.simpleName, t.message.toString()) 83 | // } 84 | // }) 85 | // return codeMutableData 86 | // } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/viewmodel/RankFragmentViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.viewmodel 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Application 5 | import android.util.Log 6 | import androidx.lifecycle.AndroidViewModel 7 | import androidx.lifecycle.LiveData 8 | import androidx.lifecycle.MutableLiveData 9 | import com.seok.gfd.BuildConfig 10 | import com.seok.gfd.retrofit.RetrofitClient 11 | import com.seok.gfd.retrofit.domain.request.CommitRequestDto 12 | import com.seok.gfd.retrofit.domain.resopnse.CommitResponse 13 | import retrofit2.Call 14 | import retrofit2.Callback 15 | import retrofit2.Response 16 | import java.text.SimpleDateFormat 17 | import java.util.* 18 | 19 | class RankFragmentViewModel(application: Application) : AndroidViewModel(application) { 20 | private val app = application 21 | 22 | private val _rankList = MutableLiveData>() 23 | val rankList: LiveData> 24 | get() = _rankList 25 | 26 | private val _serverResult = MutableLiveData() 27 | val serverResult: LiveData 28 | get() = _serverResult 29 | 30 | @SuppressLint("SimpleDateFormat") 31 | fun getTodayRankList() { 32 | val dateFormat = SimpleDateFormat("yyyy-MM-dd") 33 | 34 | val getTodayRankListService = RetrofitClient.commitService() 35 | val getTodayRankListCall = getTodayRankListService.getTRCommitList( 36 | "", 37 | dateFormat.format(Date()) 38 | ) 39 | getTodayRankListCall.enqueue(object : Callback> { 40 | override fun onResponse( 41 | call: Call>, 42 | response: Response> 43 | ) { 44 | val body = if (response.isSuccessful) { 45 | response.body() 46 | } else { 47 | null 48 | } 49 | _rankList.postValue(body) 50 | } 51 | 52 | override fun onFailure(call: Call>, t: Throwable) { 53 | Log.e(this.javaClass.simpleName, t.message.toString()) 54 | } 55 | }) 56 | } 57 | 58 | fun updateTodayRankCommit(userId: String, dataCount: Int) { 59 | val updateTodayRankCommitService = RetrofitClient.commitService() 60 | val updateTodayRankCommitCall = updateTodayRankCommitService.enrollCommit( 61 | "", 62 | CommitRequestDto(userId, dataCount) 63 | ) 64 | // updateTodayRankCommitCall.enqueue(object : Callback { 65 | // override fun onResponse(call: Call, response: Response) { 66 | // if (response.body().equals("CREATE")) { 67 | // _serverResult.postValue(true) 68 | // } 69 | // } 70 | // 71 | // override fun onFailure(call: Call, t: Throwable) { 72 | // Log.e(this.javaClass.simpleName, t.message.toString()) 73 | // } 74 | // }) 75 | } 76 | fun getTodayRankCommitList(){ 77 | val dateFormat = SimpleDateFormat("yyyy-MM-dd") 78 | val getTodayRankListService = RetrofitClient.commitService() 79 | val getTodayRankListCall = getTodayRankListService.getTRCommitList( 80 | "", 81 | dateFormat.format(Date())) 82 | getTodayRankListCall.enqueue(object : Callback>{ 83 | override fun onResponse( 84 | call: Call>, 85 | response: Response> 86 | ) { 87 | val body = if (response.isSuccessful) { 88 | response.body() 89 | } else { 90 | null 91 | } 92 | _rankList.postValue(body) 93 | } 94 | override fun onFailure(call: Call>, t: Throwable) { 95 | Log.e(this.javaClass.simpleName, t.message.toString()) 96 | } 97 | }) 98 | } 99 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/viewmodel/GithubCommitDataViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.viewmodel 2 | 3 | import android.annotation.SuppressLint 4 | import android.util.Log 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.ViewModel 8 | import com.seok.gfd.database.Commits 9 | import com.seok.gfd.retrofit.RetrofitClient 10 | import com.seok.gfd.retrofit.domain.resopnse.CommitsResponseDto 11 | import org.jsoup.Connection 12 | import org.jsoup.Jsoup 13 | import retrofit2.Call 14 | import retrofit2.Response 15 | import java.time.LocalDate 16 | 17 | class GithubCommitDataViewModel: ViewModel() { 18 | private val _commit = MutableLiveData() 19 | private val _commits = MutableLiveData>() 20 | private val _yearCommit = MutableLiveData() 21 | private val _todayCommit = MutableLiveData() 22 | private val _maxCommit = MutableLiveData() 23 | 24 | val commit: LiveData 25 | get() = _commit 26 | val commits: LiveData> 27 | get() = _commits 28 | val yearCommit: LiveData 29 | get() = _yearCommit 30 | val todayCommit: LiveData 31 | get() = _todayCommit 32 | val maxCommit: LiveData 33 | get() = _maxCommit 34 | 35 | // 사용자 깃허브에서 데이터 크롤링 36 | fun getCommitFromGithub(uri : String){ 37 | // Crawling 백그라운드 실행 38 | // doAsync { 39 | // val soup = Jsoup.connect(uri).method(Connection.Method.GET).execute().parse() 40 | // val partOfContributionData = soup.select("div[class=js-yearly-contributions]").first() 41 | // val lines = partOfContributionData.select("rect[class=day]") 42 | // 43 | // val contributions = ArrayList() 44 | // for (line in lines) { 45 | // val attrDataDate = line.attr("data-date") 46 | // val attrDataCount = line.attr("data-count") 47 | // val attrFill = line.attr("fill") 48 | // contributions.add(Commits(attrDataDate, attrDataCount.toInt(), attrFill)) 49 | // } 50 | //// _commits.postValue(contributions) 51 | // _commit.postValue(contributions[contributions.size-1]) 52 | // _maxCommit.postValue(contributions.maxBy { it.dataCount }?.dataCount.toString()) 53 | // } 54 | } 55 | 56 | // 사용자 1년 동안 커밋 데이 크롤링 57 | fun getYearCommitFromGithub(uri : String){ 58 | // Crawling 백그라운드 실행 59 | // doAsync { 60 | // val soup = Jsoup.connect(uri).method(Connection.Method.GET).execute().parse() 61 | // val contributionYearData = soup.select("h2[class=f4 text-normal mb-2]").first() 62 | // val contribution = contributionYearData.text().split(" ") 63 | // _yearCommit.postValue(contribution[0]) 64 | // } 65 | } 66 | 67 | // 깃허브 사용자 커밋 데이터 가져오기 68 | fun getCommitsInfo(userId : String){ 69 | val getCommitsService = RetrofitClient.githubContributionService() 70 | val getCommitsCall = getCommitsService.getContributions(userId) 71 | getCommitsCall.enqueue(object : retrofit2.Callback { 72 | @SuppressLint("NewApi") 73 | override fun onResponse(call: Call, response: Response) { 74 | val body = response.body() 75 | val max = body?.contributions?.maxBy { 76 | it.count 77 | } 78 | val today = body?.contributions?.find { 79 | it.date == LocalDate.now().toString() 80 | } 81 | _maxCommit.value = max?.count.toString() 82 | _yearCommit.value = body?.years?.get(0)?.total.toString() 83 | _todayCommit.value = today?.count.toString() 84 | _commits.value = body?.contributions 85 | } 86 | 87 | override fun onFailure(call: Call, t: Throwable) { 88 | Log.e(this.javaClass.simpleName, t.message.toString()) 89 | } 90 | }) 91 | } 92 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/v1/views/MainFragment.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.v1.views 2 | 3 | 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import androidx.lifecycle.Observer 10 | import androidx.lifecycle.ViewModelProviders 11 | import com.bumptech.glide.Glide 12 | import com.bumptech.glide.load.resource.bitmap.CenterCrop 13 | import com.bumptech.glide.load.resource.bitmap.RoundedCorners 14 | import com.bumptech.glide.request.RequestOptions 15 | import com.ogaclejapan.smarttablayout.utils.v4.FragmentPagerItemAdapter 16 | import com.ogaclejapan.smarttablayout.utils.v4.FragmentPagerItems 17 | import com.seok.gfd.R 18 | import com.seok.gfd.retrofit.domain.User 19 | import com.seok.gfd.retrofit.domain.resopnse.CommitsResponseDto 20 | import com.seok.gfd.utils.ProgressbarDialog 21 | import com.seok.gfd.utils.SharedPreference 22 | import com.seok.gfd.utils.ValidationCheck 23 | import com.seok.gfd.viewmodel.GithubContributionViewModel 24 | import kotlinx.android.synthetic.main.fragment_main.* 25 | import java.time.LocalDate 26 | 27 | class MainFragment : Fragment() { 28 | private lateinit var sharedPreference: SharedPreference 29 | private lateinit var progressbar: ProgressbarDialog 30 | private lateinit var githubContributionViewModel: GithubContributionViewModel 31 | 32 | private lateinit var user: User 33 | 34 | override fun onCreateView( 35 | inflater: LayoutInflater, 36 | container: ViewGroup?, 37 | savedInstanceState: Bundle? 38 | ): View? { 39 | return inflater.inflate(R.layout.fragment_main, container, false) 40 | } 41 | 42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 43 | super.onViewCreated(view, savedInstanceState) 44 | init() 45 | initSetUI() 46 | initViewModelFun() 47 | } 48 | 49 | private fun init() { 50 | progressbar = ProgressbarDialog(context!!) 51 | progressbar.show() 52 | sharedPreference = SharedPreference(this.activity!!.application) 53 | user = sharedPreference.getValueObject(getString(R.string.user_info)) 54 | githubContributionViewModel = 55 | ViewModelProviders.of(this).get(GithubContributionViewModel::class.java) 56 | githubContributionViewModel.getContributions(user.login) 57 | } 58 | 59 | private fun initViewModelFun() { 60 | githubContributionViewModel.contributions.observe(this, Observer { 61 | val fragmentPagerItems = FragmentPagerItems.with(activity) 62 | for (element in it.years!!) { 63 | val commitResponseDto = getYearContributionData(element.year, it) 64 | fragmentPagerItems.add(element.year + "(" + element.total + ")", MainSub::class.java, 65 | MainSub.arguments(commitResponseDto) 66 | ) 67 | } 68 | val adapter = FragmentPagerItemAdapter( 69 | activity?.supportFragmentManager, 70 | fragmentPagerItems.create() 71 | ) 72 | main_view_pager.adapter = adapter 73 | main_tab_smart_layout.setViewPager(main_view_pager) 74 | progressbar.hide() 75 | }) 76 | } 77 | 78 | private fun initSetUI() { 79 | main_tv_today.text = LocalDate.now().toString() 80 | main_tv_user_name.text = user.login 81 | 82 | val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(50)) 83 | Glide.with(this).load(user.avatar_url).apply(requestOptions).into(main_image_profile) 84 | main_tv_user_bio.text = user.bio 85 | } 86 | 87 | private fun getYearContributionData(year: String, commitsResponseDto: CommitsResponseDto): CommitsResponseDto { 88 | var yearContributionDtoItem = CommitsResponseDto(year) 89 | var resultList = yearContributionDtoItem.contributions as ArrayList 90 | for (element in commitsResponseDto.contributions!!) { 91 | if (element.date.contains(year)) { 92 | resultList.add(element) 93 | } 94 | } 95 | yearContributionDtoItem.contributions = resultList 96 | return yearContributionDtoItem 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/adapter/ContributionsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.adapter 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Color 6 | import android.view.Gravity 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.LinearLayout 11 | import android.widget.TextView 12 | import androidx.recyclerview.widget.RecyclerView 13 | import com.seok.gfd.R 14 | import com.seok.gfd.utils.Contribution 15 | import java.time.LocalDate 16 | import java.time.Month 17 | 18 | class ContributionsAdapter(list: ArrayList, context: Context) : 19 | RecyclerView.Adapter() { 20 | var list = list 21 | var context: Context = context 22 | 23 | override fun getItemCount() = list.size 24 | 25 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { 26 | val v = LayoutInflater.from(parent.context) 27 | .inflate(R.layout.contributions_layout, parent, false) 28 | return MyViewHolder(v) 29 | } 30 | 31 | 32 | @SuppressLint("SetTextI18n") 33 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) { 34 | val contribution = list[position] 35 | val formatYear = 36 | String.format("%s: %d Contributions", contribution.year, contribution.total) 37 | holder.yearContribution.text = formatYear 38 | 39 | var monthFlag = "" 40 | val size = contribution.list!!.size 41 | var lineLayout = LinearLayout(this.context) 42 | lineLayout.orientation = LinearLayout.VERTICAL 43 | val params = LinearLayout.LayoutParams(35, 35) 44 | // params.margin = 4 45 | params.gravity = Gravity.CENTER 46 | for (index in 0 until size) { 47 | var tempLayout = LinearLayout(this.context) 48 | if (index % 7 == 0) { 49 | val monthDataStr = getDayStartCount(LocalDate.parse(contribution.list!![index].date).month) 50 | lineLayout = LinearLayout(this.context) 51 | lineLayout.orientation = LinearLayout.VERTICAL 52 | // tempLayout.backgroundColor = Color.WHITE 53 | tempLayout.layoutParams = params 54 | if(monthFlag != monthDataStr){ 55 | val month = TextView(this.context) 56 | month.textSize = 7f 57 | month.text = monthDataStr 58 | tempLayout.addView(month) 59 | monthFlag = monthDataStr 60 | } 61 | lineLayout.addView(tempLayout) 62 | tempLayout = LinearLayout(this.context) 63 | // tempLayout.backgroundColor = Color.parseColor(contribution.list!![index].color) 64 | tempLayout.layoutParams = params 65 | lineLayout.addView(tempLayout) 66 | } else { 67 | // tempLayout.backgroundColor = Color.parseColor(contribution.list!![index].color) 68 | tempLayout.layoutParams = params 69 | lineLayout.addView(tempLayout) 70 | } 71 | if (index % 7 == 0) { 72 | // holder.contributionCanvas.addView(lineLayout) 73 | } 74 | if (index == size) { 75 | // holder.contributionCanvas.addView(lineLayout) 76 | } 77 | } 78 | } 79 | 80 | inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 81 | var yearContribution: TextView = itemView.findViewById(R.id.guest_year_contribution) 82 | // var contributionCanvas: GridLayout = itemView.findViewById(R.id.canvas) 83 | } 84 | 85 | private fun getDayStartCount(dayOfMonth: Month): String = when (dayOfMonth) { 86 | Month.JANUARY -> "Jan" 87 | Month.FEBRUARY -> "Feb" 88 | Month.MARCH -> "Mar" 89 | Month.APRIL -> "Apr" 90 | Month.MAY -> "May" 91 | Month.JUNE -> "Jun" 92 | Month.JULY -> "Jul" 93 | Month.AUGUST-> "Aug" 94 | Month.SEPTEMBER-> "Sep" 95 | Month.OCTOBER -> "Oct" 96 | Month.NOVEMBER-> "Nov" 97 | Month.DECEMBER -> "Dec" 98 | else -> "X" 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 27 | 28 | 38 | 39 | 40 | 52 | 53 | 64 | 65 | 82 | 83 | 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/v1/views/MainSub.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.v1.views 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.LinearLayout 9 | import android.widget.TextView 10 | import android.widget.Toast 11 | import androidx.fragment.app.Fragment 12 | import com.ogaclejapan.smarttablayout.utils.v4.Bundler 13 | import com.seok.gfd.R 14 | import com.seok.gfd.retrofit.domain.resopnse.CommitsResponseDto 15 | import kotlinx.android.synthetic.main.fragment_main_sub.* 16 | import java.time.DayOfWeek 17 | import java.time.LocalDate 18 | import java.time.format.DateTimeFormatter 19 | import kotlin.math.ceil 20 | 21 | class MainSub : Fragment() { 22 | 23 | private lateinit var commitResponse: CommitsResponseDto 24 | 25 | companion object { 26 | fun arguments(param: CommitsResponseDto): Bundle { 27 | // val str = CommonUtils.gson.toJson(param) 28 | return Bundler().putString("year", "str").get() 29 | } 30 | } 31 | 32 | override fun onCreateView( 33 | inflater: LayoutInflater, 34 | container: ViewGroup?, 35 | savedInstanceState: Bundle? 36 | ): View? { 37 | return inflater.inflate(R.layout.fragment_main_sub, container, false) 38 | } 39 | 40 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 41 | super.onViewCreated(view, savedInstanceState) 42 | 43 | initUI() 44 | 45 | } 46 | 47 | 48 | override fun onCreate(savedInstanceState: Bundle?) { 49 | super.onCreate(savedInstanceState) 50 | init() 51 | } 52 | 53 | private fun init() { 54 | val yearContribution = arguments?.getString("year") 55 | } 56 | 57 | private fun createContributionUI(commitSize: Int, startDay: Int, commits : List) { 58 | // Week 단위 표시 59 | var weekPosTop = 20f 60 | for (index in 1..ceil(commitSize.toDouble() / 7).toInt() step 2) { 61 | val weekText = TextView(activity) 62 | weekText.text = index.toString() + "W" 63 | main_sub_scalable_layout.addView(weekText, 20f, weekPosTop, 100f, 50f) 64 | main_sub_scalable_layout.setScale_TextSize(weekText, 35f) 65 | weekPosTop += 55f 66 | } 67 | weekPosTop = 20f 68 | for (index in 2..commitSize / 7 step 2) { 69 | val weekText = TextView(activity) 70 | weekText.text = index.toString() + "W" 71 | main_sub_scalable_layout.addView(weekText, 570f, weekPosTop, 100f, 50f) 72 | main_sub_scalable_layout.setScale_TextSize(weekText, 35f) 73 | weekPosTop += 55f 74 | } 75 | 76 | // Contribution 표시 77 | var layoutSize = 45f 78 | var layoutPScaleTop = 30f 79 | var layoutPScaleLeft = 110f 80 | var lineChange = 0 81 | 82 | layoutPScaleLeft += 55f * startDay 83 | lineChange += startDay 84 | 85 | for (index in commitSize - 1 downTo 0) { 86 | val commit = commits[index] 87 | val linearLayout = LinearLayout(activity) 88 | main_sub_scalable_layout.addView(linearLayout, layoutPScaleLeft, layoutPScaleTop, layoutSize, layoutSize) 89 | layoutPScaleLeft += 55f 90 | lineChange++ 91 | if (lineChange % 14 == 0) { 92 | layoutPScaleTop += 55f 93 | layoutPScaleLeft = 110f 94 | } else if (lineChange % 7 == 0) { 95 | layoutPScaleLeft += 170f 96 | } 97 | linearLayout.setOnClickListener { Toast.makeText(activity, String.format("%s (%d)", commit.date, commit.count) , Toast.LENGTH_SHORT).show() } 98 | } 99 | } 100 | 101 | private fun initUI() { 102 | val commits = commitResponse.contributions!! 103 | val commitSize = commits.size 104 | val date = commits[commitSize - 1].date 105 | val localDate = LocalDate.parse(date, DateTimeFormatter.ISO_DATE) 106 | val startDay = getDayStartCount(localDate.dayOfWeek) 107 | createContributionUI(commitSize, startDay, commits) 108 | } 109 | 110 | private fun getDayStartCount(dayOfWeek: DayOfWeek): Int = when (dayOfWeek) { 111 | DayOfWeek.MONDAY -> 0 112 | DayOfWeek.TUESDAY -> 1 113 | DayOfWeek.WEDNESDAY -> 2 114 | DayOfWeek.THURSDAY -> 3 115 | DayOfWeek.FRIDAY -> 4 116 | DayOfWeek.SATURDAY -> 5 117 | DayOfWeek.SUNDAY -> 6 118 | else -> 0 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/v1/views/OptionFragment.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.v1.views 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.bumptech.glide.Glide 9 | import com.bumptech.glide.request.RequestOptions 10 | import com.seok.gfd.R 11 | import kotlinx.android.synthetic.main.fragment_option.* 12 | 13 | class OptionFragment : Fragment() { 14 | override fun onCreateView( 15 | inflater: LayoutInflater, 16 | container: ViewGroup?, 17 | savedInstanceState: Bundle? 18 | ): View? { 19 | return inflater.inflate(R.layout.fragment_option, container, false) 20 | } 21 | 22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 23 | super.onViewCreated(view, savedInstanceState) 24 | setUI() 25 | } 26 | 27 | private fun init() { 28 | 29 | } 30 | private fun initViewModelFun() { 31 | 32 | } 33 | private fun setUI() { 34 | Glide.with(activity!!.applicationContext).load("https://avatars0.githubusercontent.com/u/32855880?v=4").apply(RequestOptions.circleCropTransform()).into(ot_img_user_1) 35 | ot_tv_user_name_1.text = "ellapresso" 36 | ot_tv_user_github_1.text = "https://github.com/ellapresso" 37 | ot_tv_user_etc_1.text = "Seoul" 38 | 39 | // val getUserInfoService = RetrofitClient.githubApiService() 40 | // var getUserInfoCall1 = getUserInfoService.getUserInfo("ellapresso") 41 | // getUserInfoCall1.enqueue(object : retrofit2.Callback{ 42 | // override fun onResponse(call: Call, response: Response) { 43 | // val userInfo = response.body() 44 | // Glide.with(activity!!.applicationContext).load(userInfo?.avatar_url).apply(RequestOptions.circleCropTransform()).into(ot_img_user_1) 45 | // ot_tv_user_name_1.text = userInfo?.login 46 | // ot_tv_user_github_1.text = userInfo?.html_url 47 | // ot_tv_user_etc_1.text = userInfo?.location 48 | // } 49 | // override fun onFailure(call: Call, t: Throwable) { 50 | // Log.e(this.javaClass.simpleName, t.message.toString()) 51 | // } 52 | // }) 53 | 54 | Glide.with(activity!!.applicationContext).load("https://avatars0.githubusercontent.com/u/28593727?v=4").apply(RequestOptions.circleCropTransform()).into(ot_img_user_2) 55 | ot_tv_user_name_2.text = "9992" 56 | ot_tv_user_github_2.text = "https://github.com/9992" 57 | ot_tv_user_etc_2.text = "Seoul" 58 | 59 | // val getUserInfoCall2 = getUserInfoService.getUserInfo("9992") 60 | // getUserInfoCall2.enqueue(object : retrofit2.Callback{ 61 | // override fun onResponse(call: Call, response: Response) { 62 | // val userInfo = response.body() 63 | // Glide.with(activity!!.applicationContext).load(userInfo?.avatar_url).apply(RequestOptions.circleCropTransform()).into(ot_img_user_2) 64 | // ot_tv_user_name_2.text = userInfo?.login 65 | // ot_tv_user_github_2.text = userInfo?.html_url 66 | // ot_tv_user_etc_2.text = userInfo?.location 67 | // } 68 | // override fun onFailure(call: Call, t: Throwable) { 69 | // Log.e(this.javaClass.simpleName, t.message.toString()) 70 | // } 71 | // }) 72 | 73 | Glide.with(activity!!.applicationContext).load("https://avatars1.githubusercontent.com/u/41931979?v=4").apply(RequestOptions.circleCropTransform()).into(ot_img_user_3) 74 | ot_tv_user_name_3.text = "dogcolley" 75 | ot_tv_user_github_3.text = "https://github.com/dogcolley" 76 | ot_tv_user_etc_3.text = "Seoul" 77 | 78 | // val getUserInfoCall3 = getUserInfoService.getUserInfo("dogcolley") 79 | // getUserInfoCall3.enqueue(object : retrofit2.Callback{ 80 | // override fun onResponse(call: Call, response: Response) { 81 | // val userInfo = response.body() 82 | // Glide.with(activity!!.applicationContext).load(userInfo?.avatar_url).apply(RequestOptions.circleCropTransform()).into(ot_img_user_3) 83 | // ot_tv_user_name_3.text = userInfo?.login 84 | // ot_tv_user_github_3.text = userInfo?.html_url 85 | // ot_tv_user_etc_3.text = userInfo?.location 86 | // } 87 | // override fun onFailure(call: Call, t: Throwable) { 88 | // Log.e(this.javaClass.simpleName, t.message.toString()) 89 | // } 90 | // }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/views/SearchActivity.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.views 2 | 3 | import android.os.Bundle 4 | import android.text.Editable 5 | import android.text.TextWatcher 6 | import android.view.WindowManager 7 | import android.view.animation.AnimationUtils 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.lifecycle.Observer 10 | import androidx.lifecycle.ViewModelProviders 11 | import androidx.recyclerview.widget.DividerItemDecoration 12 | import androidx.recyclerview.widget.GridLayoutManager 13 | import androidx.recyclerview.widget.LinearLayoutManager 14 | import com.google.android.material.snackbar.Snackbar 15 | import com.seok.gfd.R 16 | import com.seok.gfd.adapter.GithubIdAdapter 17 | import com.seok.gfd.room.entity.GithubId 18 | import com.seok.gfd.utils.ValidationCheck 19 | import com.seok.gfd.viewmodel.GithubIdViewModel 20 | import kotlinx.android.synthetic.main.activity_search.* 21 | 22 | 23 | class SearchActivity : AppCompatActivity() { 24 | private lateinit var githubIdsViewModel: GithubIdViewModel 25 | private lateinit var gridLayoutManager: GridLayoutManager 26 | private lateinit var githubIdAdapter: GithubIdAdapter 27 | private lateinit var githubIds: ArrayList 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_search) 32 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) 33 | 34 | // 초기 설정 35 | init() 36 | initViewModel() 37 | setAnimation() 38 | setListener() 39 | } 40 | 41 | private fun setListener() { 42 | search_edt_id.addTextChangedListener(object : TextWatcher { 43 | override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { 44 | githubIdsViewModel.getGithubId(search_edt_id.text.toString()) 45 | } 46 | 47 | override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { 48 | } 49 | 50 | override fun afterTextChanged(p0: Editable?) { 51 | } 52 | }) 53 | 54 | search_btn_ok.setOnClickListener { 55 | val githubId = search_edt_id.text.toString() 56 | if(ValidationCheck.validIsEmptyString(githubId)){ 57 | Snackbar.make(search_main_layout, "아이디칸이 비어있네요!", Snackbar.LENGTH_SHORT).show() 58 | }else{ 59 | if(ValidationCheck.isExistSite(githubId)){ 60 | val githubIdDto = GithubId(githubId) 61 | githubIdsViewModel.insertGithubId(githubIdDto) 62 | }else{ 63 | Snackbar.make(search_main_layout, "존재하지 않는 아이디 같아요.\n문제가 생겼다면 개발자에게 문의해주세요!", Snackbar.LENGTH_SHORT).show() 64 | } 65 | } 66 | } 67 | 68 | // githubIdsViewModel.closeDatabase() db 컨넥션 끊기 69 | } 70 | 71 | private fun init() { 72 | githubIds = ArrayList() 73 | githubIdAdapter = GithubIdAdapter(githubIds, search_edt_id) 74 | gridLayoutManager = GridLayoutManager(this, 1) 75 | search_recycler_view.layoutManager = gridLayoutManager 76 | search_recycler_view.adapter = githubIdAdapter 77 | search_recycler_view.addItemDecoration( 78 | DividerItemDecoration( 79 | this, 80 | LinearLayoutManager.VERTICAL 81 | ) 82 | ) 83 | } 84 | 85 | private fun initViewModel() { 86 | githubIdsViewModel = ViewModelProviders.of(this).get(GithubIdViewModel::class.java) 87 | githubIdsViewModel.githubIds.observe(this, Observer { 88 | githubIds.clear() 89 | githubIds.addAll(it) 90 | githubIdAdapter.notifyDataSetChanged() 91 | }) 92 | // 전체 검색해놓은 것 가져오기 93 | githubIdsViewModel.getGithubId("") 94 | } 95 | 96 | private fun setAnimation() { 97 | val bottomToTop = AnimationUtils.loadAnimation(this, R.anim.bottom_to_top) 98 | search_txt_info2.startAnimation(bottomToTop) 99 | val topToBottom = AnimationUtils.loadAnimation(this, R.anim.top_to_bottom) 100 | topToBottom.startOffset = 300 101 | search_txt_info1.startAnimation(topToBottom) 102 | val leftToRight = AnimationUtils.loadAnimation(this, R.anim.left_to_right) 103 | leftToRight.startOffset = 800 104 | search_layout_id.startAnimation(leftToRight) 105 | val rightToLeft = AnimationUtils.loadAnimation(this, R.anim.right_to_left) 106 | rightToLeft.startOffset = 1000 107 | search_recycler_view.startAnimation(rightToLeft) 108 | } 109 | } -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/adapter/CommitsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.adapter 2 | 3 | import android.util.Log 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.annotation.LayoutRes 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.bumptech.glide.Glide 10 | import com.bumptech.glide.request.RequestOptions 11 | import com.seok.gfd.R 12 | import com.seok.gfd.retrofit.domain.resopnse.CommitResponse 13 | import kotlinx.android.synthetic.main.rv_rank_item.view.* 14 | 15 | class CommitsAdapter(private val commit: ArrayList) : 16 | RecyclerView.Adapter() { 17 | private var index = 1 18 | override fun getItemCount() = commit.size 19 | override fun onCreateViewHolder( 20 | parent: ViewGroup, 21 | viewType: Int 22 | ): CommitsHolder { 23 | val inflatedView = parent.inflate(R.layout.rv_rank_item, false) 24 | return CommitsHolder(inflatedView) 25 | } 26 | 27 | fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View { 28 | return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot) 29 | } 30 | 31 | override fun onBindViewHolder(holder: CommitsHolder, position: Int) { 32 | val itemCommit = commit[position] 33 | holder.bindCommit(itemCommit) 34 | } 35 | 36 | class CommitsHolder(v: View) : RecyclerView.ViewHolder(v), View.OnClickListener { 37 | private var view: View = v 38 | private var commit: CommitResponse? = null 39 | init { 40 | v.setOnClickListener(this) 41 | } 42 | 43 | override fun onClick(v: View) { 44 | Log.d("RecyclerView", v.id.toString()) 45 | } 46 | 47 | companion object { 48 | private val COMMIT_KEY = "COMMIT" 49 | } 50 | 51 | fun bindCommit(commitResponse: CommitResponse) { 52 | this.commit = commitResponse 53 | view.tv_rank_commit.text = commitResponse.data_count.toString() 54 | view.tv_rv_rank_username.text = commitResponse.user_id 55 | Glide.with(view).load(commitResponse.user_image).apply(RequestOptions.circleCropTransform()).into(view.img_rv_user_profile) 56 | // view.tv_rv_rank_username.text = commitResponse.uid 57 | // view.tv_rv_rank_num.text = rank 58 | // Glide.with(view).load(commitResponse.profile_image) 59 | // .apply(RequestOptions.circleCropTransform()).into(view.img_rv_user_profile) 60 | } 61 | } 62 | 63 | } 64 | //class CommitsAdapter(private val application: Application, private val items: List, private val fragment: Fragment) : 65 | // RecyclerView.Adapter() { 66 | // 67 | // private lateinit var sharedPreferencesForUser: SharedPreferencesForUser 68 | // 69 | // override fun getItemCount() = items.size 70 | // 71 | // override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TRCommitRankViewHolder { 72 | // sharedPreferencesForUser = SharedPreferencesForUser(application) 73 | // val inflatedView = LayoutInflater.from(parent.context).inflate(R.layout.rv_rank_item, parent, false) 74 | // return TRCommitRankViewHolder(inflatedView) 75 | // } 76 | // 77 | // override fun onBindViewHolder(holder: TRCommitRankViewHolder, position: Int) { 78 | // val item = items[position] 79 | // val rank = (position+1).toString() 80 | // if(item.uid == sharedPreferencesForUser.getValue(application.getString(R.string.user_id))){ 81 | // fragment.tv_rv_rank.text = rank 82 | // fragment.tv_rv_commit.text = item.data_count.toString() 83 | // Glide.with(fragment).load(item.profile_image).apply(RequestOptions.circleCropTransform()).into(fragment.img_rv_profile) 84 | // } 85 | // holder.apply { 86 | // bind(rank, item, sharedPreferencesForUser.getValue(application.getString(R.string.user_id))) 87 | // itemView.tag = item 88 | // } 89 | // } 90 | // 91 | // class TRCommitRankViewHolder(private var view: View) : RecyclerView.ViewHolder(view) { 92 | // fun bind(rank: String, item: TRCommitResponseDto, userId: String) { 93 | // view.tv_rv_rank_num.text = rank 94 | // view.tv_rv_rank_username.text = item.uid 95 | // view.tv_rank_commit.text = item.data_count.toString() 96 | // Glide.with(view.context).load(item.profile_image).apply(RequestOptions.circleCropTransform()).into(view.img_rv_user_profile) 97 | // if(item.uid == userId){ 98 | // view.layout_rv_back.setBackgroundResource(R.color.nonCommit) 99 | // } 100 | // } 101 | // } 102 | //} -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | xmlns:android 30 | 31 | ^$ 32 | 33 | 34 | 35 |
36 |
37 | 38 | 39 | 40 | xmlns:.* 41 | 42 | ^$ 43 | 44 | 45 | BY_NAME 46 | 47 |
48 |
49 | 50 | 51 | 52 | .*:id 53 | 54 | http://schemas.android.com/apk/res/android 55 | 56 | 57 | 58 |
59 |
60 | 61 | 62 | 63 | .*:name 64 | 65 | http://schemas.android.com/apk/res/android 66 | 67 | 68 | 69 |
70 |
71 | 72 | 73 | 74 | name 75 | 76 | ^$ 77 | 78 | 79 | 80 |
81 |
82 | 83 | 84 | 85 | style 86 | 87 | ^$ 88 | 89 | 90 | 91 |
92 |
93 | 94 | 95 | 96 | .* 97 | 98 | ^$ 99 | 100 | 101 | BY_NAME 102 | 103 |
104 |
105 | 106 | 107 | 108 | .* 109 | 110 | http://schemas.android.com/apk/res/android 111 | 112 | 113 | ANDROID_ATTRIBUTE_ORDER 114 | 115 |
116 |
117 | 118 | 119 | 120 | .* 121 | 122 | .* 123 | 124 | 125 | BY_NAME 126 | 127 |
128 |
129 |
130 |
131 | 132 | 134 |
135 |
-------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/v1/views/GuestMain.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.v1.views 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.lifecycle.Observer 7 | import androidx.lifecycle.ViewModelProviders 8 | import androidx.recyclerview.widget.DividerItemDecoration 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import com.google.android.gms.ads.AdRequest 11 | import com.google.android.gms.ads.MobileAds 12 | import com.seok.gfd.R 13 | import com.seok.gfd.adapter.ContributionsAdapter 14 | import com.seok.gfd.retrofit.domain.resopnse.CommitsResponseDto 15 | import com.seok.gfd.utils.Contribution 16 | import com.seok.gfd.viewmodel.GithubContributionViewModel 17 | import kotlinx.android.synthetic.main.activity_guest_main.* 18 | 19 | class GuestMain : AppCompatActivity() { 20 | private lateinit var githubContributionViewModel: GithubContributionViewModel 21 | 22 | private var contributionList = ArrayList() 23 | 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | setContentView(R.layout.activity_guest_main) 28 | 29 | ads() 30 | init() 31 | initViewModelFun() 32 | } 33 | 34 | private fun refreshContributionsData() { 35 | contributionList = ArrayList() 36 | val userName = guest_main_search_edit.text.toString() 37 | githubContributionViewModel.getContributions(userName) 38 | val customAdapter = ContributionsAdapter(contributionList, this) 39 | recyclerView.adapter = customAdapter 40 | } 41 | 42 | private fun init() { 43 | githubContributionViewModel = 44 | ViewModelProviders.of(this).get(GithubContributionViewModel::class.java) 45 | val linearLayoutManager = LinearLayoutManager(applicationContext) 46 | recyclerView.layoutManager = linearLayoutManager 47 | recyclerView.addItemDecoration(DividerItemDecoration(this, 1)) 48 | simpleSwipeRefreshLayout.setOnRefreshListener { 49 | simpleSwipeRefreshLayout.isRefreshing = false 50 | refreshContributionsData() 51 | } 52 | guest_main_search_btn.setOnClickListener { 53 | contributionList = ArrayList() 54 | guest_main_recycler_layout.visibility = View.VISIBLE 55 | val userName = guest_main_search_edit.text.toString() 56 | githubContributionViewModel.getContributions(userName) 57 | } 58 | } 59 | 60 | private fun initViewModelFun() { 61 | githubContributionViewModel.contributions.observe(this, Observer { 62 | if (it != null) { 63 | if (it.years?.size != 0) { 64 | val helloName = 65 | String.format("@%s on Github", guest_main_search_edit.text.toString()) 66 | guest_main_tv_username.text = helloName 67 | getContributions(it) 68 | val customAdapter = ContributionsAdapter(contributionList, this) 69 | recyclerView.adapter = customAdapter 70 | guest_main_recycler_layout.visibility = View.INVISIBLE 71 | } else { 72 | guest_main_tv_username.text = "찾지 못했습니다." 73 | } 74 | } else { 75 | guest_main_tv_username.text = "찾지 못했습니다." 76 | } 77 | guest_main_tv_username.visibility = View.VISIBLE 78 | }) 79 | } 80 | 81 | private fun ads() { 82 | MobileAds.initialize( 83 | this.application, 84 | getString(R.string.admob_app_id) 85 | ) 86 | val adRequest = AdRequest.Builder().build() 87 | guest_login_ads_view.loadAd(adRequest) 88 | } 89 | 90 | 91 | private fun getContributions(commitsResponseDto: CommitsResponseDto) { 92 | for (year in commitsResponseDto.years!!) { 93 | var contribution = Contribution() 94 | contribution.year = year.year 95 | contribution.total = year.total 96 | var contributionList = ArrayList() 97 | for (index in commitsResponseDto.contributions!!.size - 1 downTo 0) { 98 | val commit = commitsResponseDto.contributions!![index] 99 | if (commit.date.contains(year.year)) { 100 | val contributionInfo = Contribution.ContributionInfo( 101 | commit.date, 102 | commit.count, 103 | commit.color, 104 | commit.intensity 105 | ) 106 | contributionList.add(contributionInfo) 107 | } 108 | } 109 | contribution.list = contributionList 110 | this.contributionList.add(contribution) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/gfd_logo_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | //noinspection LifecycleAnnotationProcessorWithJava8 2 | apply plugin: 'com.android.application' 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'kotlin-android-extensions' 6 | // https://blog.yena.io/studynote/2018/09/08/Android-Kotlin-Room.html 7 | // https://kotlinlang.org/docs/reference/using-gradle.html 8 | 9 | android { 10 | compileSdkVersion 28 11 | defaultConfig { 12 | applicationId "com.seok.gfd" 13 | minSdkVersion 26 14 | targetSdkVersion 28 15 | versionCode 10 16 | versionName "3.20.12" 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | testCoverageEnabled false 24 | } 25 | debug { 26 | testCoverageEnabled true 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | testOptions { 34 | unitTests { 35 | includeAndroidResources = true 36 | returnDefaultValues = true 37 | } 38 | } 39 | lintOptions { 40 | abortOnError false 41 | disable 'GradleDependency' // noinspection GradleDependency 경고 비활성화 42 | } 43 | buildToolsVersion '28.0.3' 44 | ndkVersion '20.1.5948944' 45 | } 46 | 47 | dependencies { 48 | implementation fileTree(dir: 'libs', include: ['*.jar']) 49 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 50 | implementation 'com.google.android.material:material:1.3.0-beta01' 51 | implementation 'androidx.appcompat:appcompat:1.2.0' 52 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 53 | implementation "androidx.recyclerview:recyclerview:1.1.0" 54 | 55 | // 머티리얼 디자인 라이브러리 56 | implementation 'com.rengwuxian.materialedittext:library:2.1.4' 57 | 58 | // room 59 | implementation "androidx.room:room-runtime:$room_version" 60 | implementation "androidx.room:room-ktx:$room_version" 61 | kapt "androidx.room:room-compiler:$room_version" 62 | androidTestImplementation "androidx.room:room-testing:$room_version" 63 | 64 | // jsoup 65 | implementation 'org.jsoup:jsoup:1.13.1' 66 | 67 | // test implementation 68 | testImplementation 'junit:junit:4.12' 69 | androidTestImplementation 'androidx.test:runner:1.2.0' 70 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 71 | androidTestImplementation 'androidx.test:rules:1.2.0' 72 | androidTestImplementation 'androidx.test:core:1.2.0' 73 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 74 | androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0' 75 | // implementation 'androidx.legacy:legacy-support-v4:1.0.0' 76 | // testImplementation 'org.robolectric:robolectric:4.3' 77 | // testImplementation 'org.powermock:powermock-api-mockito:1.4.12' 78 | // testImplementation 'org.powermock:powermock-module-junit4:1.6.2' 79 | // testImplementation 'androidx.arch.core:core-testing:2.1.0' 80 | 81 | 82 | // Google Ads 83 | implementation 'com.google.android.gms:play-services-ads:18.2.0' 84 | 85 | // OkHttp && logger 86 | implementation 'com.squareup.okhttp3:okhttp:3.12.0' 87 | implementation 'com.squareup.okhttp3:logging-interceptor:3.4.2' 88 | 89 | // Retrofit2 90 | implementation 'com.squareup.retrofit2:retrofit:2.6.1' 91 | implementation 'com.squareup.retrofit2:converter-gson:2.4.0' 92 | implementation 'com.google.code.gson:gson:2.8.5' 93 | 94 | // Lifecycle components 95 | implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' 96 | implementation 'android.arch.lifecycle:viewmodel:1.1.1' 97 | annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.1.0' 98 | implementation 'android.arch.lifecycle:extensions:1.0.0'; 99 | //noinspection LifecycleAnnotationProcessorWithJava8 100 | annotationProcessor 'android.arch.lifecycle:compiler:1.0.0'; 101 | 102 | // Coroutines 103 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" 104 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" 105 | 106 | // Using the Glide Library for Image loading 107 | implementation 'com.github.bumptech.glide:glide:4.9.0' 108 | kapt 'com.github.bumptech.glide:compiler:4.9.0' 109 | 110 | // jsoup, anko 111 | // implementation "org.jetbrains.anko:anko:$rootProject.anko_version" 112 | // implementation 'androidx.gridlayout:gridlayout:1.0.0' 113 | 114 | // lombok 115 | compileOnly 'org.projectlombok:lombok:1.18.8' 116 | annotationProcessor 'org.projectlombok:lombok:1.18.8' 117 | 118 | // ProgressBar 119 | implementation 'com.wang.avi:library:2.1.3' 120 | 121 | // NavigationBar 122 | implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0' 123 | implementation 'androidx.navigation:navigation-ui-ktx:2.1.0' 124 | 125 | // UI Scale 126 | implementation 'com.ssomai:android.scalablelayout:2.1.6' 127 | 128 | // Tab Layout 129 | implementation 'com.ogaclejapan.smarttablayout:library:2.0.0@aar' 130 | implementation 'com.ogaclejapan.smarttablayout:utils-v4:2.0.0@aar' 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 17 | 18 | 27 | 28 | 29 | 41 | 42 | 54 | 55 | 64 | 65 | 76 | 77 | 78 | 84 | 116 | 117 | 125 | 126 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/java/com/seok/gfd/viewmodel/UserViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.seok.gfd.viewmodel 2 | 3 | import android.os.Build 4 | import android.util.Log 5 | import androidx.annotation.RequiresApi 6 | import androidx.lifecycle.LiveData 7 | import androidx.lifecycle.MutableLiveData 8 | import androidx.lifecycle.ViewModel 9 | import com.seok.gfd.BuildConfig 10 | import com.seok.gfd.retrofit.RetrofitClient 11 | import com.seok.gfd.retrofit.domain.* 12 | import com.seok.gfd.retrofit.domain.request.CommitRequestDto 13 | import com.seok.gfd.retrofit.domain.resopnse.CommitResponse 14 | import retrofit2.Call 15 | import retrofit2.Response 16 | import java.net.HttpURLConnection 17 | import java.time.LocalDate 18 | 19 | class UserViewModel : ViewModel() { 20 | private val _usersCount = MutableLiveData() 21 | private val _accessToken = MutableLiveData() 22 | private val _code = MutableLiveData() 23 | private val _userInfo = MutableLiveData() 24 | private val _commitList = MutableLiveData>() 25 | 26 | val userCount: LiveData 27 | get() = _usersCount 28 | val accessToken: LiveData 29 | get() = _accessToken 30 | val code: LiveData 31 | get() = _code 32 | val userInfo: LiveData 33 | get() = _userInfo 34 | val commitList: LiveData> 35 | get() = _commitList 36 | 37 | // User 인원 수 가져오기 38 | fun getUsersCount() { 39 | val userService = RetrofitClient.userService() 40 | val userCall = userService.getUsersCount("") 41 | userCall.enqueue(object : retrofit2.Callback> { 42 | override fun onResponse( 43 | call: Call>, 44 | response: Response> 45 | ) { 46 | _usersCount.value = response.body()?.data 47 | } 48 | 49 | override fun onFailure(call: Call>, t: Throwable) { 50 | Log.e(this.javaClass.simpleName, t.message.toString()) 51 | } 52 | }) 53 | } 54 | 55 | // Access Token 가져오기 56 | fun getAccessTokenFromGithubApi(code: String) { 57 | val githubAuthService = RetrofitClient.githubAuthService() 58 | val githubAuthCall = githubAuthService.getAccessTokenFromGithubApi( 59 | "", 60 | "", 61 | code 62 | ) 63 | githubAuthCall.enqueue(object : retrofit2.Callback { 64 | override fun onResponse(call: Call, response: Response) { 65 | _accessToken.value = response.body()?.access_token 66 | } 67 | 68 | override fun onFailure(call: Call, t: Throwable) { 69 | Log.e(this.javaClass.simpleName, t.message.toString()) 70 | } 71 | }) 72 | } 73 | 74 | // Github 로그인 및 User 정보 가져오기 75 | fun getUserInfoAndSignInGithub(accessToken: String) { 76 | val githubApiService = RetrofitClient.githubApiService() 77 | val githubApiCall = githubApiService.getUserInfoFromGithubApi("token $accessToken") 78 | githubApiCall.enqueue(object : retrofit2.Callback { 79 | override fun onResponse(call: Call, response: Response) { 80 | _code.value = response.code() 81 | if (response.code() == HttpURLConnection.HTTP_OK) { 82 | _userInfo.value = response.body() 83 | } 84 | } 85 | 86 | override fun onFailure(call: Call, t: Throwable) { 87 | Log.e(this.javaClass.simpleName, t.message.toString()) 88 | } 89 | }) 90 | } 91 | 92 | fun signInUserInfo(user: User){ 93 | val gfdSignInService = RetrofitClient.userService() 94 | val requestUserDto = GfdUser(user.login, user.html_url, user.avatar_url) 95 | val gfdSignInCall = gfdSignInService.signUpUser("", requestUserDto) 96 | gfdSignInCall.enqueue(object : retrofit2.Callback>{ 97 | override fun onResponse( 98 | call: Call>, 99 | response: Response> 100 | ) { 101 | response 102 | } 103 | override fun onFailure(call: Call>, t: Throwable) { 104 | Log.e(this.javaClass.simpleName, t.message.toString()) 105 | } 106 | 107 | }) 108 | } 109 | 110 | // 금일 커밋 랭킹 가져오기 111 | @RequiresApi(Build.VERSION_CODES.O) 112 | fun getCommitsRank() { 113 | val dataDate = LocalDate.now().toString() 114 | val getCommitsService = RetrofitClient.commitService() 115 | val getCommitCall = getCommitsService.getCommitList("", dataDate) 116 | getCommitCall.enqueue(object : retrofit2.Callback> { 117 | override fun onResponse(call: Call>, response: Response>) { 118 | _commitList.value = response.body()?.list 119 | } 120 | 121 | override fun onFailure(call: Call>, t: Throwable) { 122 | Log.e(this.javaClass.simpleName, t.message.toString()) 123 | } 124 | 125 | }) 126 | } 127 | 128 | // 금일 커밋 등록하기 129 | fun enrollCommit(commit: CommitRequestDto){ 130 | val enrollCommitService = RetrofitClient.commitService() 131 | val enrollCommitCall = enrollCommitService.enrollCommit("", commit) 132 | enrollCommitCall.enqueue(object : retrofit2.Callback>{ 133 | override fun onResponse( 134 | call: Call>, 135 | response: Response> 136 | ) { 137 | 138 | } 139 | 140 | override fun onFailure(call: Call>, t: Throwable) { 141 | 142 | } 143 | }) 144 | } 145 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_rank.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 19 | 20 | 30 | 31 | 40 | 41 | 54 | 55 | 67 | 68 | 76 | 77 | 88 | 89 | 98 | 99 | 100 | 101 | 105 | 106 | 116 | 117 | 118 | 119 | 120 | --------------------------------------------------------------------------------