├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── render.experimental.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── app │ │ └── android │ │ └── githubservice │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── fonts │ │ │ └── sansserif.ttf │ ├── ic_launcher-playstore.png │ ├── ic_launcher_app-playstore.png │ ├── java │ │ └── app │ │ │ └── android │ │ │ └── githubservice │ │ │ ├── GithubApplication.kt │ │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseBottomSheet.kt │ │ │ └── BaseFragment.kt │ │ │ ├── di │ │ │ └── AppModule.kt │ │ │ ├── entity │ │ │ ├── event │ │ │ │ └── Events.kt │ │ │ ├── follower_following │ │ │ │ └── FollowerFollowingResponse.kt │ │ │ ├── repo │ │ │ │ └── RepositoryResponse.kt │ │ │ ├── search │ │ │ │ ├── Item.kt │ │ │ │ └── SearchResponse.kt │ │ │ └── starred │ │ │ │ └── StarredResponse.kt │ │ │ ├── interfaces │ │ │ ├── AppDialogCallback.kt │ │ │ └── GlobalBottomSheetCallBack.kt │ │ │ ├── model │ │ │ ├── db │ │ │ │ ├── GitHubDao.kt │ │ │ │ └── GitHubDatabase.kt │ │ │ └── network │ │ │ │ └── GitHubApi.kt │ │ │ ├── repository │ │ │ ├── AuthRepository.kt │ │ │ ├── BaseRepository.kt │ │ │ ├── EventsRepository.kt │ │ │ ├── FollowersRepository.kt │ │ │ ├── FollowingRepository.kt │ │ │ ├── ReposRepository.kt │ │ │ ├── SearchRepository.kt │ │ │ └── StarredRepository.kt │ │ │ ├── ui │ │ │ ├── BottomSheetProfile.kt │ │ │ ├── activity │ │ │ │ ├── ActivitySplash.kt │ │ │ │ ├── LoginActivity.kt │ │ │ │ └── MainActivity.kt │ │ │ ├── adapter │ │ │ │ ├── EventsAdapter.kt │ │ │ │ ├── FavoriteAdapter.kt │ │ │ │ ├── FollowersFollowingAdapter.kt │ │ │ │ ├── ReposAdapter.kt │ │ │ │ ├── SearchAdapter.kt │ │ │ │ ├── StarredAdapter.kt │ │ │ │ └── ViewPagerProfileAdapter.kt │ │ │ └── fragment │ │ │ │ ├── EventsFragment.kt │ │ │ │ ├── FavoriteFragment.kt │ │ │ │ ├── FollowersFragment.kt │ │ │ │ ├── FollowingFragment.kt │ │ │ │ ├── ProfileFragment.kt │ │ │ │ ├── ReposFragment.kt │ │ │ │ ├── SearchFragment.kt │ │ │ │ └── StarredFragment.kt │ │ │ ├── util │ │ │ ├── Constants.kt │ │ │ ├── LanguageColorGenerator.java │ │ │ ├── Resource.kt │ │ │ ├── Utility.kt │ │ │ └── docs.txt │ │ │ └── viewmodel │ │ │ ├── AuthViewModel.kt │ │ │ ├── EventsViewModel.kt │ │ │ ├── FollowersViewModel.kt │ │ │ ├── FollowingViewModel.kt │ │ │ ├── RepositoriesViewModel.kt │ │ │ ├── SearchViewModel.kt │ │ │ └── StarredViewModel.kt │ └── res │ │ ├── anim │ │ └── left_side.xml │ │ ├── color │ │ └── bnv_tab_item_foreground.xml │ │ ├── drawable │ │ ├── bg_button_login.xml │ │ ├── bg_dot_language.xml │ │ ├── bg_overview_titles.xml │ │ ├── bg_search.xml │ │ ├── divider_list.xml │ │ ├── ic_arrow.xml │ │ ├── ic_delete.xml │ │ ├── ic_events.xml │ │ ├── ic_fav.xml │ │ ├── ic_fork.xml │ │ ├── ic_github.xml │ │ ├── ic_launcher_app_foreground.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_lock.xml │ │ ├── ic_logout.xml │ │ ├── ic_profile.xml │ │ ├── ic_repo.xml │ │ ├── ic_search.xml │ │ ├── ic_setting.xml │ │ ├── ic_star.xml │ │ ├── shape_dialog.xml │ │ ├── shape_round_corner_bottom_sheet.xml │ │ └── shape_round_corner_button.xml │ │ ├── font │ │ ├── font.xml │ │ └── sansserif.ttf │ │ ├── layout │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_splash.xml │ │ ├── fragment_bottom_sheet.xml │ │ ├── fragment_events.xml │ │ ├── fragment_favorite.xml │ │ ├── fragment_followers.xml │ │ ├── fragment_following.xml │ │ ├── fragment_profile.xml │ │ ├── fragment_repos.xml │ │ ├── fragment_search.xml │ │ ├── fragment_starred.xml │ │ ├── item_list_events.xml │ │ ├── item_list_favorite.xml │ │ ├── item_list_followers_following.xml │ │ ├── item_list_repos.xml │ │ └── item_list_searched_users.xml │ │ ├── menu │ │ └── menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ ├── ic_launcher_app.xml │ │ ├── ic_launcher_app_round.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_app.png │ │ ├── ic_launcher_app_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_app.png │ │ ├── ic_launcher_app_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_app.png │ │ ├── ic_launcher_app_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_app.png │ │ ├── ic_launcher_app_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_app.png │ │ ├── ic_launcher_app_round.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ └── nav_graph.xml │ │ ├── raw │ │ └── colors.json │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_app_background.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── app │ └── android │ └── githubservice │ └── ExampleUnitTest.kt ├── bash.exe.stackdump ├── build.gradle ├── dependencies.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── GitHubApplication.png ├── arch.png ├── name.png └── poster1a.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/render.experimental.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 | 6 | 7 | [![Kotlin Version](https://img.shields.io/badge/kotlin-1.3.72-blue.svg)](https://kotlinlang.org) 8 | [![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21) 9 | ![](https://img.shields.io/github/languages/count/faramarzaf/GitHubApplication.svg) 10 | ![](https://img.shields.io/github/repo-size/faramarzaf/GitHubApplication.svg) 11 | ![](https://img.shields.io/github/last-commit/faramarzaf/GitHubApplication.svg) 12 | 13 | 14 | In this program, we have tried to use new and modern Android technologies to be an example of a program for those who want to learn how to use new tools. 15 | 16 |

17 | 18 |

19 | 20 | 21 | # Screenshots ✨ 22 | 23 |

24 | 25 |

26 | 27 | 28 | 29 | # Architecture 30 | 31 | GitHubApplication is based on MVVM architecture 32 |

33 | 34 |

35 | 36 | # Built With 🛠 37 | - [Kotlin](https://kotlinlang.org/) 38 | - [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) 39 | - [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) 40 | - [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) 41 | - [ViewBinding](https://developer.android.com/topic/libraries/view-binding) 42 | - [Room](https://developer.android.com/topic/libraries/architecture/room) 43 | - [Hilt-Dagger](https://dagger.dev/hilt/) 44 | - [Hilt-ViewModel](https://developer.android.com/training/dependency-injection/hilt-jetpack) 45 | - [Retrofit](https://square.github.io/retrofit/) 46 | - [Glide](https://github.com/bumptech/glide) 47 | 48 | 49 | 50 | # Contribute 51 | 52 | Feeling Awesome! Thanks for thinking about this. 53 | 54 | You can contribute us by filing issues, bugs and PRs. 55 | Contributing: 56 | 57 | - Open issue regarding proposed change. 58 | - If your proposed change is approved, Fork this repo and do changes. 59 | - Open PR in develop branch with full description in PR. 60 | - You're done! 61 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: "androidx.navigation.safeargs.kotlin" 6 | apply plugin: 'dagger.hilt.android.plugin' 7 | 8 | android { 9 | compileSdkVersion 29 10 | buildToolsVersion "29.0.3" 11 | defaultConfig { 12 | applicationId "app.android.githubservice" 13 | minSdkVersion 21 14 | targetSdkVersion 29 15 | versionCode 1 16 | versionName "1.0" 17 | multiDexEnabled true 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | 22 | buildFeatures { 23 | viewBinding = true 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | kotlinOptions { 37 | jvmTarget = JavaVersion.VERSION_1_8.toString() 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation fileTree(dir: "libs", include: ["*.jar"]) 43 | implementation coreKtx.core 44 | implementation coreKtx.kotlin_stdlib 45 | 46 | implementation appCompat.appcompat 47 | 48 | implementation legacy.legacy 49 | 50 | implementation constraintLayout.constraintlayout 51 | 52 | implementation room.room_runtime 53 | kapt room.room_compiler 54 | implementation room.room_ktx 55 | 56 | implementation coroutines.core 57 | implementation coroutines.android 58 | 59 | implementation lifecycle.runtime 60 | implementation lifecycle.viewmodel 61 | 62 | implementation retrofit.retrofit 63 | implementation retrofit.converter_gson 64 | implementation retrofit.okhttp3 65 | 66 | implementation navigation.fragment 67 | implementation navigation.ui 68 | 69 | implementation glide.glide 70 | kapt glide.compiler 71 | 72 | implementation paging.paging3 73 | 74 | implementation faramarzSDK.sdk 75 | 76 | 77 | implementation dagger_hilt.daggerHiltAndroid 78 | implementation dagger_hilt.daggerHilt 79 | kapt dagger_hilt.daggerHiltAndroidCompiler 80 | kapt dagger_hilt.daggerHiltCompiler 81 | 82 | 83 | implementation unitTest.core 84 | testImplementation unitTest.junit 85 | testImplementation unitTest.hamcrest 86 | testImplementation unitTest.androidx_arch_core 87 | testImplementation unitTest.robolectric 88 | testImplementation unitTest.kotlinx_coroutines_test 89 | testImplementation unitTest.truth 90 | testImplementation unitTest.mockito 91 | 92 | 93 | androidTestImplementation uiTest.core 94 | androidTestImplementation uiTest.junit 95 | androidTestImplementation uiTest.hamcrest 96 | androidTestImplementation uiTest.androidx_arch_core 97 | androidTestImplementation uiTest.robolectric 98 | androidTestImplementation uiTest.kotlinx_coroutines_test 99 | androidTestImplementation uiTest.truth 100 | androidTestImplementation uiTest.mockito 101 | androidTestImplementation uiTest.dexmaker_mockito 102 | androidTestImplementation uiTest.espresso 103 | androidTestImplementation uiTest.test_hilt_android 104 | kaptAndroidTest uiTest.test_hilt_compiler 105 | implementation uiTest.ext_junit 106 | debugImplementation uiTest.fragment 107 | androidTestImplementation uiTest.contrib 108 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/app/android/githubservice/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("app.android.githubservice", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/assets/fonts/sansserif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/assets/fonts/sansserif.ttf -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher_app-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/ic_launcher_app-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/GithubApplication.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class GithubApplication : Application() -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.base 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.graphics.Color 7 | import android.os.Bundle 8 | import android.view.View 9 | import android.widget.ProgressBar 10 | import android.widget.Toast 11 | import androidx.appcompat.app.AppCompatActivity 12 | import androidx.core.app.ActivityOptionsCompat 13 | import androidx.core.view.ViewCompat 14 | import androidx.fragment.app.Fragment 15 | import androidx.fragment.app.FragmentManager 16 | import androidx.fragment.app.FragmentTransaction 17 | import app.android.githubservice.R 18 | import app.android.githubservice.interfaces.AppDialogCallback 19 | import app.android.githubservice.util.KEY_SESSION_ID 20 | import com.faramarzaf.sdk.af_android_sdk.core.helper.NetworkHelper 21 | import com.faramarzaf.sdk.af_android_sdk.core.helper.ScreenHelper 22 | import com.faramarzaf.sdk.af_android_sdk.core.interfaces.DialogCallback 23 | import com.faramarzaf.sdk.af_android_sdk.core.ui.dialog.ProgressDialogCustom 24 | import com.faramarzaf.sdk.af_android_sdk.core.ui.dialog.SimpleDialog 25 | import com.faramarzaf.sdk.af_android_sdk.core.util.MyDataStore 26 | import com.faramarzaf.sdk.af_android_sdk.core.util.MyPreferences 27 | import java.util.* 28 | 29 | 30 | abstract class BaseActivity : AppCompatActivity() { 31 | 32 | private var progressDialog: ProgressDialogCustom? = null 33 | 34 | 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | super.onCreate(savedInstanceState) 37 | } 38 | 39 | open fun toActivity(classOf: Class<*>) { 40 | startActivity(Intent(applicationContext, classOf)) 41 | overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) 42 | } 43 | 44 | fun toActivityWithSharedElement(activity: Activity, destination: Class<*>, view: View) { 45 | val intent = Intent(activity, destination) 46 | val options = ActivityOptionsCompat.makeSceneTransitionAnimation( 47 | activity, view, Objects.requireNonNull(ViewCompat.getTransitionName(view)) 48 | ) 49 | startActivity(intent, options.toBundle()) 50 | } 51 | 52 | fun setFragments(layout: Int, fragment: Fragment, addToBackStack: Boolean) { 53 | val fragmentManager: FragmentManager = supportFragmentManager 54 | val transaction: FragmentTransaction = fragmentManager.beginTransaction() 55 | transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) 56 | transaction.replace(layout, fragment) 57 | if (addToBackStack) 58 | transaction.addToBackStack(fragment.javaClass.simpleName) 59 | transaction.commitAllowingStateLoss() 60 | } 61 | 62 | fun getSessionId(): String { 63 | return MyPreferences.readString(this, KEY_SESSION_ID, "") 64 | } 65 | 66 | fun toast(msg: String) { 67 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() 68 | } 69 | 70 | open fun showProgressDialog() { 71 | try { 72 | if (progressDialog == null) { 73 | progressDialog = ProgressDialogCustom( 74 | this, 75 | R.layout.layout_dialog_progress, 76 | R.drawable.ic_launcher_background, 77 | false 78 | ) 79 | } 80 | progressDialog!!.show() 81 | 82 | } catch (exception: Exception) { 83 | exception.printStackTrace() 84 | } 85 | } 86 | 87 | open fun hideProgressDialog() { 88 | try { 89 | if (progressDialog != null) { 90 | progressDialog!!.dismiss() 91 | } 92 | } catch (exception: Exception) { 93 | exception.printStackTrace() 94 | } 95 | } 96 | 97 | fun showSimpleDialog(content: String, negativeText: String, positiveText: String, callback: AppDialogCallback) { 98 | SimpleDialog(this) 99 | .cancelable(true) 100 | .setText(content) 101 | .setPositiveButton(positiveText) 102 | .setNegativeButton(negativeText) 103 | .showDialog() 104 | .setTypeface(getString(R.string.font_address)) 105 | .setTitleColor(Color.rgb(0, 0, 0)) 106 | .setTextSize(16f, 13f) 107 | .setPositiveBackground(R.drawable.shape_round_corner_button, Color.WHITE) 108 | .setNegativeBackground(R.drawable.shape_round_corner_button, Color.WHITE) 109 | .setImageDialogBackground(R.drawable.ic_launcher_background) 110 | .setDialogBackground(R.drawable.shape_dialog) 111 | .setCallBack(object : DialogCallback { 112 | override fun onPositiveButtonClicked() { 113 | callback.onConfirm() 114 | } 115 | 116 | override fun onNegativeButtonClicked() { 117 | callback.onCancel() 118 | } 119 | }) 120 | } 121 | 122 | fun transparentToolbar(activity: Activity) { 123 | ScreenHelper.hideToolbar(activity) 124 | } 125 | 126 | 127 | fun isNetworkConnected(context: Context): Boolean { 128 | return NetworkHelper.checkNetwork(context) 129 | } 130 | 131 | 132 | fun showProgressBar(pg: ProgressBar) { 133 | pg.visibility = View.VISIBLE 134 | } 135 | 136 | fun hideProgressBar(pg: ProgressBar) { 137 | pg.visibility = View.GONE 138 | } 139 | 140 | override fun onBackPressed() { 141 | super.onBackPressed() 142 | overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/base/BaseBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.base 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import app.android.githubservice.R 9 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 10 | 11 | 12 | abstract class BaseBottomSheet : BottomSheetDialogFragment() { 13 | 14 | protected abstract val fragmentLayout: Int 15 | 16 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 17 | val dialog = super.onCreateDialog(savedInstanceState) 18 | dialog.window!!.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE 19 | or View.SYSTEM_UI_FLAG_FULLSCREEN 20 | or View.SYSTEM_UI_FLAG_LAYOUT_STABLE 21 | or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 22 | or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 23 | or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) 24 | return dialog 25 | } 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | // to disappear white default background of bottom sheet 30 | setStyle(STYLE_NORMAL, R.style.AppBottomSheetDialogTheme) 31 | super.onCreate(savedInstanceState) 32 | } 33 | 34 | override fun getTheme(): Int { 35 | return R.style.AppBottomSheetDialogTheme 36 | } 37 | 38 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 39 | return inflater.inflate(fragmentLayout, container, false) 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.base 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.ProgressBar 11 | import android.widget.Toast 12 | import androidx.core.app.ActivityOptionsCompat 13 | import androidx.core.view.ViewCompat 14 | import androidx.fragment.app.Fragment 15 | import androidx.recyclerview.widget.RecyclerView 16 | import app.android.githubservice.R 17 | import com.faramarzaf.sdk.af_android_sdk.core.helper.NetworkHelper.Companion.checkNetwork 18 | import com.faramarzaf.sdk.af_android_sdk.core.helper.RecyclerviewHelper 19 | import com.faramarzaf.sdk.af_android_sdk.core.helper.ScreenHelper 20 | import com.faramarzaf.sdk.af_android_sdk.core.ui.dialog.ProgressDialogCustom 21 | import java.util.* 22 | 23 | abstract class BaseFragment : Fragment() { 24 | protected var mProgressDialog: ProgressDialogCustom? = null 25 | 26 | protected abstract val getFragmentLayout: Int 27 | 28 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 29 | return inflater.inflate(getFragmentLayout, container, false) 30 | } 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | activity?.let { transparentToolbar(it) } 35 | } 36 | 37 | override fun onStart() { 38 | super.onStart() 39 | } 40 | 41 | override fun onStop() { 42 | super.onStop() 43 | } 44 | 45 | 46 | override fun onDetach() { 47 | super.onDetach() 48 | } 49 | 50 | protected fun showProgressDialog() { 51 | try { 52 | if (mProgressDialog == null) { 53 | mProgressDialog = ProgressDialogCustom( 54 | activity, 55 | R.layout.layout_dialog_progress, 56 | R.drawable.ic_launcher_background, false 57 | ) 58 | } 59 | mProgressDialog!!.show() 60 | } catch (e: Exception) { 61 | e.printStackTrace() 62 | } 63 | } 64 | 65 | protected fun hideProgressDialog() { 66 | try { 67 | if (activity != null && mProgressDialog != null) { 68 | mProgressDialog!!.dismiss() 69 | } 70 | } catch (e: Exception) { 71 | e.printStackTrace() 72 | } 73 | } 74 | 75 | fun showProgressBar(pg: ProgressBar) { 76 | pg.visibility = View.VISIBLE 77 | } 78 | 79 | fun hideProgressBar(pg: ProgressBar) { 80 | pg.visibility = View.GONE 81 | } 82 | 83 | 84 | fun toast(msg: String?) { 85 | Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show() 86 | } 87 | 88 | fun setRecyclerviewDivider(context: Context, recyclerView: RecyclerView, resId: Int) { 89 | RecyclerviewHelper.setRecyclerviewDivider(context, recyclerView, resId) 90 | } 91 | 92 | 93 | fun setFragments(layout: Int, fragment: Fragment, addToBackStack: Boolean) { 94 | val fragmentManager = requireActivity().supportFragmentManager 95 | val transaction = fragmentManager.beginTransaction() 96 | transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) 97 | transaction.replace(layout, fragment) 98 | if (addToBackStack) transaction.addToBackStack(fragment.javaClass.simpleName) 99 | transaction.commitAllowingStateLoss() 100 | } 101 | 102 | fun toActivityWithSharedElement(activity: Activity?, destination: Class<*>?, view: View?) { 103 | val intent = Intent(activity, destination) 104 | val options = ActivityOptionsCompat.makeSceneTransitionAnimation( 105 | requireActivity(), requireView(), 106 | Objects.requireNonNull(ViewCompat.getTransitionName(requireView())).toString() 107 | ) 108 | startActivity(intent, options.toBundle()) 109 | } 110 | 111 | 112 | open fun toActivity(activity: Activity?, classOf: Class<*>) { 113 | startActivity(Intent(activity, classOf)) 114 | requireActivity().overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) 115 | } 116 | 117 | 118 | fun transparentToolbar(activity: Activity) { 119 | ScreenHelper.hideToolbar(activity) 120 | } 121 | 122 | fun isNetworkConnected(): Boolean { 123 | return checkNetwork(requireContext()) 124 | } 125 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import app.android.githubservice.R 6 | import app.android.githubservice.model.db.GitHubDatabase 7 | import app.android.githubservice.model.network.GitHubApi 8 | import app.android.githubservice.repository.* 9 | import app.android.githubservice.util.BASE_URL 10 | import app.android.githubservice.util.DATABASE_NAME 11 | import com.bumptech.glide.Glide 12 | import com.bumptech.glide.request.RequestOptions 13 | import com.faramarzaf.sdk.af_android_sdk.core.network.ServiceRepository 14 | import dagger.Module 15 | import dagger.Provides 16 | import dagger.hilt.InstallIn 17 | import dagger.hilt.android.components.ApplicationComponent 18 | import dagger.hilt.android.qualifiers.ApplicationContext 19 | import retrofit2.Retrofit 20 | import retrofit2.converter.gson.GsonConverterFactory 21 | import javax.inject.Singleton 22 | 23 | @Module 24 | @InstallIn(ApplicationComponent::class) 25 | object AppModule { 26 | 27 | @Singleton 28 | @Provides 29 | fun provideGitHubDatabase(@ApplicationContext context: Context) = 30 | Room.databaseBuilder(context, GitHubDatabase::class.java, DATABASE_NAME) 31 | .allowMainThreadQueries() 32 | .build() 33 | 34 | 35 | @Singleton 36 | @Provides 37 | fun provideGitHubDao(database: GitHubDatabase) = database.getGitHubDao() 38 | 39 | 40 | @Singleton 41 | @Provides 42 | fun provideGitHubApi(): GitHubApi { 43 | val api by lazy { ServiceRepository.ServiceBuilder.buildService(BASE_URL, GitHubApi::class.java) } 44 | return api 45 | } 46 | 47 | @Singleton 48 | @Provides 49 | fun provideAuthRepository(api: GitHubApi) = AuthRepository(api) 50 | 51 | @Singleton 52 | @Provides 53 | fun provideFollowersRepository(api: GitHubApi) = FollowersRepository(api) 54 | 55 | @Singleton 56 | @Provides 57 | fun provideFollowingRepository(api: GitHubApi) = FollowingRepository(api) 58 | 59 | @Singleton 60 | @Provides 61 | fun provideReposRepository(api: GitHubApi) = ReposRepository(api) 62 | 63 | @Singleton 64 | @Provides 65 | fun provideSearchRepository(api: GitHubApi, db: GitHubDatabase) = SearchRepository(api, db) 66 | 67 | @Singleton 68 | @Provides 69 | fun provideStarredRepository(api: GitHubApi) = StarredRepository(api) 70 | 71 | @Singleton 72 | @Provides 73 | fun provideEventsRepository(api: GitHubApi) = EventsRepository(api) 74 | 75 | 76 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/entity/event/Events.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.entity.event 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | class Events : ArrayList(){ 7 | data class EventsItem( 8 | @SerializedName("actor") 9 | val actor: Actor, 10 | @SerializedName("created_at") 11 | val createdAt: String, 12 | @SerializedName("id") 13 | val id: String, 14 | @SerializedName("org") 15 | val org: Org, 16 | @SerializedName("payload") 17 | val payload: Payload, 18 | @SerializedName("public") 19 | val `public`: Boolean, 20 | @SerializedName("repo") 21 | val repo: Repo, 22 | @SerializedName("type") 23 | val type: String 24 | ) { 25 | data class Actor( 26 | @SerializedName("avatar_url") 27 | val avatarUrl: String, 28 | @SerializedName("display_login") 29 | val displayLogin: String, 30 | @SerializedName("gravatar_id") 31 | val gravatarId: String, 32 | @SerializedName("id") 33 | val id: Int, 34 | @SerializedName("login") 35 | val login: String, 36 | @SerializedName("url") 37 | val url: String 38 | ) 39 | 40 | data class Org( 41 | @SerializedName("avatar_url") 42 | val avatarUrl: String, 43 | @SerializedName("gravatar_id") 44 | val gravatarId: String, 45 | @SerializedName("id") 46 | val id: Int, 47 | @SerializedName("login") 48 | val login: String, 49 | @SerializedName("url") 50 | val url: String 51 | ) 52 | 53 | data class Payload( 54 | @SerializedName("action") 55 | val action: String 56 | ) 57 | 58 | data class Repo( 59 | @SerializedName("id") 60 | val id: Int, 61 | @SerializedName("name") 62 | val name: String, 63 | @SerializedName("url") 64 | val url: String 65 | ) 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/entity/follower_following/FollowerFollowingResponse.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.entity.follower_following 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | class FollowerFollowingResponse : ArrayList(){ 7 | data class FollowerFollowingItem( 8 | @SerializedName("avatar_url") 9 | val avatarUrl: String?, 10 | @SerializedName("events_url") 11 | val eventsUrl: String?, 12 | @SerializedName("followers_url") 13 | val followersUrl: String?, 14 | @SerializedName("following_url") 15 | val followingUrl: String?, 16 | @SerializedName("gists_url") 17 | val gistsUrl: String?, 18 | @SerializedName("gravatar_id") 19 | val gravatarId: String?, 20 | @SerializedName("html_url") 21 | val htmlUrl: String?, 22 | @SerializedName("id") 23 | val id: Int?, 24 | @SerializedName("login") 25 | val login: String?, 26 | @SerializedName("node_id") 27 | val nodeId: String?, 28 | @SerializedName("organizations_url") 29 | val organizationsUrl: String?, 30 | @SerializedName("received_events_url") 31 | val receivedEventsUrl: String?, 32 | @SerializedName("repos_url") 33 | val reposUrl: String?, 34 | @SerializedName("site_admin") 35 | val siteAdmin: Boolean?, 36 | @SerializedName("starred_url") 37 | val starredUrl: String?, 38 | @SerializedName("subscriptions_url") 39 | val subscriptionsUrl: String?, 40 | @SerializedName("type") 41 | val type: String?, 42 | @SerializedName("url") 43 | val url: String? 44 | ) 45 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/entity/search/Item.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.entity.search 2 | 3 | 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.google.gson.annotations.SerializedName 7 | import java.io.Serializable 8 | 9 | @Entity( 10 | tableName = "users" 11 | ) 12 | data class Item( 13 | @SerializedName("avatar_url") 14 | val avatarUrl: String?, 15 | @SerializedName("events_url") 16 | val eventsUrl: String?, 17 | @SerializedName("followers_url") 18 | val followersUrl: String?, 19 | @SerializedName("following_url") 20 | val followingUrl: String?, 21 | @SerializedName("gists_url") 22 | val gistsUrl: String?, 23 | @SerializedName("gravatar_id") 24 | val gravatarId: String?, 25 | @SerializedName("html_url") 26 | val htmlUrl: String?, 27 | @PrimaryKey(autoGenerate = true) 28 | @SerializedName("id") 29 | val id: Int? = null, 30 | @SerializedName("login") 31 | val login: String?, 32 | @SerializedName("node_id") 33 | val nodeId: String?, 34 | @SerializedName("organizations_url") 35 | val organizationsUrl: String?, 36 | @SerializedName("received_events_url") 37 | val receivedEventsUrl: String?, 38 | @SerializedName("repos_url") 39 | val reposUrl: String?, 40 | @SerializedName("score") 41 | val score: Double?, 42 | @SerializedName("site_admin") 43 | val siteAdmin: Boolean?, 44 | @SerializedName("starred_url") 45 | val starredUrl: String?, 46 | @SerializedName("subscriptions_url") 47 | val subscriptionsUrl: String?, 48 | @SerializedName("type") 49 | val type: String?, 50 | @SerializedName("url") 51 | val url: String? 52 | ) : Serializable -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/entity/search/SearchResponse.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.entity.search 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class SearchResponse( 7 | @SerializedName("incomplete_results") 8 | val incompleteResults: Boolean, 9 | @SerializedName("items") 10 | val items: MutableList, 11 | @SerializedName("total_count") 12 | val totalCount: Int 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/interfaces/AppDialogCallback.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.interfaces 2 | 3 | interface AppDialogCallback { 4 | fun onConfirm() 5 | fun onCancel() 6 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/interfaces/GlobalBottomSheetCallBack.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.interfaces 2 | 3 | interface GlobalBottomSheetCallBack { 4 | fun onLogoutClick() 5 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/model/db/GitHubDao.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.model.db 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.* 5 | import app.android.githubservice.entity.search.Item 6 | 7 | @Dao 8 | interface GitHubDao { 9 | 10 | @Insert(onConflict = OnConflictStrategy.REPLACE) 11 | suspend fun insert(article: Item): Long 12 | 13 | @Query("SELECT * FROM users") 14 | fun getAllUsers(): LiveData> 15 | 16 | @Delete 17 | suspend fun deleteUser(user: Item) 18 | 19 | @Query("DELETE FROM users") 20 | suspend fun deleteAll() 21 | 22 | @Query("SELECT EXISTS (SELECT 1 FROM users WHERE id = :id)") 23 | fun userExist(id: Int?): Boolean 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/model/db/GitHubDatabase.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.model.db 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import app.android.githubservice.entity.search.Item 6 | 7 | @Database(entities = [Item::class], version = 1, exportSchema = false) 8 | abstract class GitHubDatabase : RoomDatabase() { 9 | 10 | abstract fun getGitHubDao(): GitHubDao 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/model/network/GitHubApi.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.model.network 2 | 3 | import app.android.githubservice.entity.event.Events 4 | import app.android.githubservice.entity.follower_following.FollowerFollowingResponse 5 | import app.android.githubservice.entity.repo.RepositoryResponse 6 | import app.android.githubservice.entity.search.SearchResponse 7 | import app.android.githubservice.entity.starred.StarredResponse 8 | import retrofit2.http.GET 9 | import retrofit2.http.Path 10 | import retrofit2.http.Query 11 | 12 | interface GitHubApi { 13 | 14 | @GET("/search/users") 15 | suspend fun checkUserIsValid(@Query("q") username: String): SearchResponse 16 | 17 | 18 | @GET("/search/users") 19 | suspend fun searchUser( 20 | @Query("q") username: String 21 | , @Query("page") page: Int 22 | , @Query("per_page") per_page: Int 23 | ): SearchResponse 24 | 25 | 26 | @GET("/users/{user}/repos") 27 | suspend fun getRepositories( 28 | @Path("user") username: String 29 | , @Query("page") page: Int 30 | , @Query("per_page") per_page: Int 31 | ): RepositoryResponse 32 | 33 | 34 | @GET("/users/{user}/starred") 35 | suspend fun getStarredRepositories( 36 | @Path("user") username: String 37 | , @Query("page") page: Int 38 | , @Query("per_page") per_page: Int 39 | ): StarredResponse 40 | 41 | 42 | @GET("/users/{user}/followers") 43 | suspend fun getFollowers( 44 | @Path("user") username: String 45 | , @Query("page") page: Int 46 | , @Query("per_page") per_page: Int 47 | ): FollowerFollowingResponse 48 | 49 | 50 | @GET("/users/{user}/following") 51 | suspend fun getFollowing( 52 | @Path("user") username: String 53 | , @Query("page") page: Int 54 | , @Query("per_page") per_page: Int 55 | ): FollowerFollowingResponse 56 | 57 | @GET("/events") 58 | suspend fun getEvents(): Events 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/repository/AuthRepository.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.repository 2 | 3 | import app.android.githubservice.model.network.GitHubApi 4 | import javax.inject.Inject 5 | 6 | class AuthRepository @Inject constructor(private val api: GitHubApi) : BaseRepository() { 7 | 8 | suspend fun authUser(username: String) = safeCallApi { 9 | api.checkUserIsValid(username) 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/repository/BaseRepository.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.repository 2 | 3 | import app.android.githubservice.util.Resource 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | import retrofit2.HttpException 7 | 8 | abstract class BaseRepository { 9 | 10 | suspend fun safeCallApi(apiCall: suspend () -> T): Resource { 11 | return withContext(Dispatchers.IO) { 12 | try { 13 | Resource.Success(apiCall.invoke()) 14 | } catch (throwable: Throwable) { 15 | when (throwable) { 16 | is HttpException -> 17 | Resource.Failure(false, throwable.code(), throwable.response()?.errorBody()!!) 18 | else -> 19 | Resource.Failure(true, null, null) 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/repository/EventsRepository.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.repository 2 | 3 | import app.android.githubservice.model.network.GitHubApi 4 | import javax.inject.Inject 5 | 6 | class EventsRepository @Inject constructor(private val api: GitHubApi) : BaseRepository() { 7 | 8 | suspend fun getEvents() = safeCallApi { 9 | api.getEvents() 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/repository/FollowersRepository.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.repository 2 | 3 | import app.android.githubservice.model.network.GitHubApi 4 | import javax.inject.Inject 5 | 6 | class FollowersRepository @Inject constructor(private val api: GitHubApi) : BaseRepository() { 7 | 8 | suspend fun getFollowers(username: String, page: Int, per_page: Int) = safeCallApi { 9 | api.getFollowers(username,page, per_page) 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/repository/FollowingRepository.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.repository 2 | 3 | import app.android.githubservice.model.network.GitHubApi 4 | import javax.inject.Inject 5 | 6 | class FollowingRepository @Inject constructor(private val api: GitHubApi) : BaseRepository() { 7 | 8 | suspend fun getFollowing(username: String, page: Int, per_page: Int) = safeCallApi { 9 | api.getFollowing(username,page, per_page) 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/repository/ReposRepository.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.repository 2 | 3 | import app.android.githubservice.model.network.GitHubApi 4 | import javax.inject.Inject 5 | 6 | class ReposRepository @Inject constructor(private val api: GitHubApi) : BaseRepository() { 7 | 8 | suspend fun getRepos(username: String, page: Int, per_page: Int) = safeCallApi { 9 | api.getRepositories(username, page, per_page) 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/repository/SearchRepository.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.repository 2 | 3 | import app.android.githubservice.entity.search.Item 4 | import app.android.githubservice.model.db.GitHubDatabase 5 | import app.android.githubservice.model.network.GitHubApi 6 | import javax.inject.Inject 7 | 8 | class SearchRepository @Inject constructor(private val api: GitHubApi, private val db: GitHubDatabase) : BaseRepository() { 9 | 10 | suspend fun searchUser(username: String, page: Int, per_page: Int) = safeCallApi { 11 | api.searchUser(username, page, per_page) 12 | } 13 | 14 | suspend fun insert(user: Item) = db.getGitHubDao().insert(user) 15 | 16 | fun getSavedUsers() = db.getGitHubDao().getAllUsers() 17 | 18 | fun userExists(user: Item) = db.getGitHubDao().userExist(user.id) 19 | 20 | suspend fun deleteUser(user: Item) = db.getGitHubDao().deleteUser(user) 21 | 22 | suspend fun deleteAll() = db.getGitHubDao().deleteAll() 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/repository/StarredRepository.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.repository 2 | 3 | import app.android.githubservice.model.network.GitHubApi 4 | import javax.inject.Inject 5 | 6 | class StarredRepository @Inject constructor(private val api: GitHubApi) : BaseRepository() { 7 | 8 | suspend fun getStarredRepositories(username: String, page: Int, per_page: Int) = safeCallApi { 9 | api.getStarredRepositories(username,page, per_page) 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/BottomSheetProfile.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import app.android.githubservice.R 6 | import app.android.githubservice.base.BaseBottomSheet 7 | import app.android.githubservice.interfaces.GlobalBottomSheetCallBack 8 | import app.android.githubservice.util.DELAY_GUARD_CLICK 9 | import com.faramarzaf.sdk.af_android_sdk.core.interfaces.DoGuardTask 10 | import com.faramarzaf.sdk.af_android_sdk.core.util.ClickGuard 11 | import kotlinx.android.synthetic.main.fragment_bottom_sheet.* 12 | 13 | open class BottomSheetProfile : BaseBottomSheet(), DoGuardTask { 14 | 15 | override val fragmentLayout: Int 16 | get() = R.layout.fragment_bottom_sheet 17 | 18 | private var globalBottomSheetCallBack: GlobalBottomSheetCallBack? = null 19 | 20 | 21 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 22 | super.onViewCreated(view, savedInstanceState) 23 | ClickGuard.guardView(imgLogout, DELAY_GUARD_CLICK, this) 24 | } 25 | 26 | override fun onGuard(view: View) { 27 | when (view.id) { 28 | R.id.imgLogout -> { 29 | globalBottomSheetCallBack!!.onLogoutClick() 30 | } 31 | } 32 | } 33 | 34 | fun setOnBottomSheetClickListener(listener: (GlobalBottomSheetCallBack)) { 35 | globalBottomSheetCallBack = listener 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/activity/ActivitySplash.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.activity 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import app.android.githubservice.R 6 | import app.android.githubservice.base.BaseActivity 7 | 8 | class ActivitySplash : BaseActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_splash) 12 | transparentToolbar(this) 13 | loadSplash() 14 | } 15 | 16 | private fun loadSplash() { 17 | Handler().postDelayed({ 18 | toActivity(LoginActivity::class.java) 19 | finish() 20 | }, 1600) 21 | } 22 | 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/activity/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.activity 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.activity.viewModels 6 | import androidx.lifecycle.Observer 7 | import androidx.lifecycle.lifecycleScope 8 | import app.android.githubservice.base.BaseActivity 9 | import app.android.githubservice.databinding.ActivityLoginBinding 10 | import app.android.githubservice.entity.search.SearchResponse 11 | import app.android.githubservice.util.* 12 | import app.android.githubservice.viewmodel.AuthViewModel 13 | import com.faramarzaf.sdk.af_android_sdk.core.helper.HashHelper 14 | import com.faramarzaf.sdk.af_android_sdk.core.helper.StringHelper 15 | import com.faramarzaf.sdk.af_android_sdk.core.util.MyDataStore 16 | import com.faramarzaf.sdk.af_android_sdk.core.util.MyPreferences 17 | import dagger.hilt.android.AndroidEntryPoint 18 | import kotlinx.coroutines.launch 19 | 20 | @AndroidEntryPoint 21 | class LoginActivity : BaseActivity(), View.OnClickListener { 22 | 23 | private val viewModel: AuthViewModel by viewModels() 24 | private lateinit var binding: ActivityLoginBinding 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | binding = ActivityLoginBinding.inflate(layoutInflater) 29 | setContentView(binding.root) 30 | checkUserIsAuth() 31 | transparentToolbar(this) 32 | handleAuthResponse() 33 | binding.btnLogin.setOnClickListener(this) 34 | } 35 | 36 | override fun onClick(v: View?) { 37 | val username = binding.editTextUsername.text.toString().trim() 38 | showProgressBar(binding.authProgressBar) 39 | if (binding.checkboxRememberMe.isChecked && !StringHelper.stringIsEmptyOrNull(binding.editTextUsername.text.toString().trim())) { 40 | MyPreferences.writeString(this, KEY_USERNAME, username) 41 | MyPreferences.writeString(this, KEY_SESSION_ID, HashHelper.sha256(username)) 42 | viewModel.auth(username) 43 | } else if (!binding.checkboxRememberMe.isChecked && !StringHelper.stringIsEmptyOrNull(binding.editTextUsername.text.toString().trim())) { 44 | MyPreferences.writeString(this, KEY_USERNAME, username) 45 | viewModel.auth(username) 46 | } else if (StringHelper.stringIsEmptyOrNull(binding.editTextUsername.text.toString().trim())) { 47 | hideProgressBar(binding.authProgressBar) 48 | toast("Fill fields!") 49 | } 50 | } 51 | 52 | private fun checkUserIsAuth() { 53 | if (!StringHelper.stringIsEmptyOrNull(getSessionId())) { 54 | toActivity(MainActivity::class.java) 55 | finish() 56 | } 57 | } 58 | 59 | private fun handleAuthResponse() { 60 | viewModel.loginResponse.observe(this, Observer { 61 | when (it) { 62 | is Resource.Success -> { 63 | hideProgressBar(binding.authProgressBar) 64 | if (it.value.totalCount == 0) { 65 | toast("User not found!") 66 | } else { 67 | hideProgressBar(binding.authProgressBar) 68 | saveUsefulUrls(it.value) 69 | toActivity(MainActivity::class.java) 70 | finish() 71 | } 72 | } 73 | is Resource.Failure -> { 74 | hideProgressBar(binding.authProgressBar) 75 | if (it.isNetworkError) { 76 | toast("Check your connection!") 77 | } 78 | toast(it.toString()) 79 | } 80 | } 81 | }) 82 | } 83 | 84 | private fun saveUsefulUrls(response: SearchResponse) { 85 | for (info in response.items) { 86 | MyPreferences.writeString(this, KEY_AVATAR_URL, info.avatarUrl.toString()) 87 | MyPreferences.writeString(this, KEY_HTML_URL, info.htmlUrl.toString()) 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.activity 2 | 3 | import android.os.Bundle 4 | import androidx.navigation.NavController 5 | import androidx.navigation.Navigation 6 | import androidx.navigation.ui.NavigationUI 7 | import androidx.navigation.ui.setupWithNavController 8 | import app.android.githubservice.R 9 | import app.android.githubservice.base.BaseActivity 10 | import app.android.githubservice.databinding.ActivityLoginBinding 11 | import app.android.githubservice.databinding.ActivityMainBinding 12 | import dagger.hilt.android.AndroidEntryPoint 13 | import kotlinx.android.synthetic.main.activity_main.* 14 | 15 | @AndroidEntryPoint 16 | class MainActivity : BaseActivity() { 17 | 18 | private lateinit var navController: NavController 19 | private lateinit var binding:ActivityMainBinding 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | binding = ActivityMainBinding.inflate(layoutInflater) 24 | setContentView(binding.root) 25 | navController = Navigation.findNavController(this, R.id.fragment) 26 | binding.bottomNavigationView.setupWithNavController(navController) 27 | transparentToolbar(this) 28 | } 29 | 30 | override fun onSupportNavigateUp(): Boolean { 31 | return NavigationUI.navigateUp(navController, null) 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/adapter/EventsAdapter.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.AsyncListDiffer 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.RecyclerView 8 | import app.android.githubservice.databinding.ItemListEventsBinding 9 | import app.android.githubservice.entity.event.Events 10 | import app.android.githubservice.util.URL_GITHUB 11 | import com.faramarzaf.sdk.af_android_sdk.core.helper.GlideHelper 12 | 13 | 14 | class EventsAdapter : RecyclerView.Adapter() { 15 | 16 | inner class EventsViewHolder(private val itemBinding: ItemListEventsBinding) : RecyclerView.ViewHolder(itemBinding.root) { 17 | fun bind(item: Events.EventsItem) { 18 | val urlRepo = URL_GITHUB + item.repo.name 19 | itemView.apply { 20 | with(itemBinding) { 21 | textEventType.text = "${item.type} on " 22 | textUsernameEvent.text = item.actor.login 23 | if (item.repo.name.contains("/")) { 24 | val parts: List = item.repo.name.split("/") 25 | val repoName = parts[1] // item.repo.name= faramarzaf/GithubApplication --> parts[1] = GithubApplication 26 | textRepoNameEvent.text = repoName 27 | } else 28 | textRepoNameEvent.text = item.repo.name 29 | GlideHelper.circularImage(context, item.actor.avatarUrl, avatarEvent) 30 | } 31 | itemView.setOnClickListener { 32 | onItemClickListener?.let { 33 | it(urlRepo) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | private val differCallback = object : DiffUtil.ItemCallback() { 41 | 42 | override fun areItemsTheSame(oldItem: Events.EventsItem, newItem: Events.EventsItem): Boolean { 43 | return oldItem.id == newItem.id 44 | } 45 | 46 | override fun areContentsTheSame(oldItem: Events.EventsItem, newItem: Events.EventsItem): Boolean { 47 | return oldItem == newItem 48 | } 49 | } 50 | 51 | val differ = AsyncListDiffer(this, differCallback) 52 | 53 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EventsViewHolder { 54 | val itemBinding = ItemListEventsBinding.inflate(LayoutInflater.from(parent.context), parent, false) 55 | return EventsViewHolder(itemBinding) 56 | } 57 | 58 | override fun getItemCount(): Int { 59 | return differ.currentList.size 60 | } 61 | 62 | private var onItemClickListener: ((String) -> Unit)? = null 63 | 64 | override fun onBindViewHolder(holder: EventsViewHolder, position: Int) { 65 | val repoInfo = differ.currentList[position] 66 | holder.bind(repoInfo) 67 | } 68 | 69 | fun setOnRepoClickListener(listener: (String) -> Unit) { 70 | onItemClickListener = listener 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/adapter/FavoriteAdapter.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.AsyncListDiffer 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.RecyclerView 8 | import app.android.githubservice.databinding.ItemListFavoriteBinding 9 | import app.android.githubservice.entity.search.Item 10 | import com.faramarzaf.sdk.af_android_sdk.core.helper.GlideHelper 11 | 12 | 13 | class FavoriteAdapter : RecyclerView.Adapter() { 14 | 15 | inner class FavoriteViewHolder(private val itemBinding: ItemListFavoriteBinding) : RecyclerView.ViewHolder(itemBinding.root) { 16 | fun bind(item: Item) { 17 | val urlRepo = item.htmlUrl 18 | itemView.apply { 19 | itemBinding.textSaveName.text = item.login.toString() 20 | GlideHelper.circularImage(context, item.avatarUrl.toString(), itemBinding.imageSavedUsers) 21 | itemView.setOnClickListener { 22 | onItemClickListener?.let { 23 | it(urlRepo.toString()) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | private val differCallback = object : DiffUtil.ItemCallback() { 31 | 32 | override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean { 33 | return oldItem.id == newItem.id 34 | } 35 | 36 | override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean { 37 | return oldItem == newItem 38 | } 39 | } 40 | 41 | val differ = AsyncListDiffer(this, differCallback) 42 | 43 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavoriteViewHolder { 44 | val itemBinding = ItemListFavoriteBinding.inflate(LayoutInflater.from(parent.context), parent, false) 45 | return FavoriteViewHolder(itemBinding) 46 | } 47 | 48 | override fun getItemCount(): Int { 49 | return differ.currentList.size 50 | } 51 | 52 | private var onItemClickListener: ((String) -> Unit)? = null 53 | 54 | override fun onBindViewHolder(holder: FavoriteViewHolder, position: Int) { 55 | val repoInfo = differ.currentList[position] 56 | holder.bind(repoInfo) 57 | } 58 | 59 | fun setOnRepoClickListener(listener: (String) -> Unit) { 60 | onItemClickListener = listener 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/adapter/FollowersFollowingAdapter.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.AsyncListDiffer 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.RecyclerView 8 | import app.android.githubservice.databinding.ItemListFollowersFollowingBinding 9 | import app.android.githubservice.entity.follower_following.FollowerFollowingResponse 10 | import com.faramarzaf.sdk.af_android_sdk.core.helper.GlideHelper 11 | 12 | 13 | class FollowersFollowingAdapter : RecyclerView.Adapter() { 14 | 15 | inner class FollowersFollowingViewHolder(private val itemBinding: ItemListFollowersFollowingBinding) : RecyclerView.ViewHolder(itemBinding.root) { 16 | fun bind(item: FollowerFollowingResponse.FollowerFollowingItem) { 17 | val urlRepo = item.htmlUrl 18 | itemView.apply { 19 | GlideHelper.circularImage(context, item.avatarUrl.toString(), itemBinding.imageFollowersFollowing) 20 | itemBinding.textFollowersFollowing.text = item.login 21 | itemView.setOnClickListener { 22 | onItemClickListener?.let { 23 | it(urlRepo.toString()) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | private val differCallback = object : DiffUtil.ItemCallback() { 31 | override fun areItemsTheSame(oldItem: FollowerFollowingResponse.FollowerFollowingItem, newItem: FollowerFollowingResponse.FollowerFollowingItem): Boolean { 32 | return oldItem.id == newItem.id 33 | } 34 | 35 | override fun areContentsTheSame(oldItem: FollowerFollowingResponse.FollowerFollowingItem, newItem: FollowerFollowingResponse.FollowerFollowingItem): Boolean { 36 | return oldItem == newItem 37 | } 38 | } 39 | 40 | val differ = AsyncListDiffer(this, differCallback) 41 | 42 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowersFollowingViewHolder { 43 | val itemBinding = ItemListFollowersFollowingBinding.inflate(LayoutInflater.from(parent.context), parent, false) 44 | return FollowersFollowingViewHolder(itemBinding) 45 | } 46 | 47 | override fun getItemCount(): Int { 48 | return differ.currentList.size 49 | } 50 | 51 | private var onItemClickListener: ((String) -> Unit)? = null 52 | 53 | override fun onBindViewHolder(holder: FollowersFollowingViewHolder, position: Int) { 54 | val info = differ.currentList[position] 55 | holder.bind(info) 56 | } 57 | 58 | fun setOnRepoClickListener(listener: (String) -> Unit) { 59 | onItemClickListener = listener 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/adapter/ReposAdapter.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.adapter 2 | 3 | import android.content.res.ColorStateList 4 | import android.graphics.Color 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.core.view.ViewCompat 9 | import androidx.recyclerview.widget.AsyncListDiffer 10 | import androidx.recyclerview.widget.DiffUtil 11 | import androidx.recyclerview.widget.RecyclerView 12 | import app.android.githubservice.databinding.ItemListReposBinding 13 | import app.android.githubservice.entity.repo.RepositoryResponse.RepositoryModelItem 14 | import app.android.githubservice.util.LanguageColorGenerator 15 | import app.android.githubservice.util.URL_GITHUB 16 | import app.android.githubservice.util.thousandPrinter 17 | 18 | 19 | class ReposAdapter : RecyclerView.Adapter() { 20 | 21 | inner class ReposViewHolder(private val itemBinding: ItemListReposBinding) : RecyclerView.ViewHolder(itemBinding.root) { 22 | fun bind(item: RepositoryModelItem) { 23 | val urlRepo = URL_GITHUB + item.fullName 24 | itemView.apply { 25 | itemBinding.textRepoName.text = item.name 26 | itemBinding.textRepoDesc.text = item.description 27 | if (item.stargazersCount!! > 1000 || item.forksCount!! > 1000) { 28 | val startValue = thousandPrinter(item.stargazersCount.toString()) 29 | val forkValue = thousandPrinter(item.forksCount.toString()) 30 | itemBinding.textStars.text = startValue 31 | itemBinding.textForks.text = forkValue 32 | } else { 33 | itemBinding.textStars.text = "☆ " + item.stargazersCount.toString() 34 | itemBinding.textForks.text = item.forksCount.toString() 35 | } 36 | 37 | val keyColor = item.language.toString() 38 | val codeColor = LanguageColorGenerator.getColors(context, keyColor) 39 | if (codeColor != null) 40 | ViewCompat.setBackgroundTintList(itemBinding.viewColoredLanguage, ColorStateList.valueOf(Color.parseColor(codeColor))) 41 | 42 | val language = item.language 43 | itemBinding.textLanguage.text = language 44 | if (language.isNullOrEmpty()) { 45 | itemBinding.textLanguage.text = "-" 46 | itemBinding.viewColoredLanguage.visibility = View.GONE 47 | } 48 | 49 | itemView.setOnClickListener { 50 | onItemClickListener?.let { 51 | it(urlRepo) 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | private val differCallback = object : DiffUtil.ItemCallback() { 59 | override fun areItemsTheSame(oldItem: RepositoryModelItem, newItem: RepositoryModelItem): Boolean { 60 | return oldItem.id == newItem.id 61 | } 62 | 63 | override fun areContentsTheSame(oldItem: RepositoryModelItem, newItem: RepositoryModelItem): Boolean { 64 | return oldItem == newItem 65 | } 66 | } 67 | 68 | val differ = AsyncListDiffer(this, differCallback) 69 | 70 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReposViewHolder { 71 | val itemBinding = ItemListReposBinding.inflate(LayoutInflater.from(parent.context), parent, false) 72 | return ReposViewHolder(itemBinding) 73 | } 74 | 75 | override fun getItemCount(): Int { 76 | return differ.currentList.size 77 | } 78 | 79 | private var onItemClickListener: ((String) -> Unit)? = null 80 | 81 | override fun onBindViewHolder(holder: ReposViewHolder, position: Int) { 82 | val repoInfo = differ.currentList[position] 83 | holder.bind(repoInfo) 84 | } 85 | 86 | fun setOnRepoClickListener(listener: (String) -> Unit) { 87 | onItemClickListener = listener 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/adapter/SearchAdapter.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import android.widget.ImageView 6 | import androidx.recyclerview.widget.AsyncListDiffer 7 | import androidx.recyclerview.widget.DiffUtil 8 | import androidx.recyclerview.widget.RecyclerView 9 | import app.android.githubservice.databinding.ItemListSearchedUsersBinding 10 | import app.android.githubservice.entity.search.Item 11 | import com.faramarzaf.sdk.af_android_sdk.core.helper.GlideHelper 12 | import kotlinx.android.synthetic.main.item_list_searched_users.view.* 13 | 14 | 15 | class SearchAdapter : RecyclerView.Adapter() { 16 | 17 | inner class SearchViewHolder(private val itemBinding: ItemListSearchedUsersBinding) : RecyclerView.ViewHolder(itemBinding.root) { 18 | fun bind(items: Item) { 19 | val imageViewFav = itemView.imageFav 20 | itemBinding.imageFav.setOnClickListener { 21 | onSaveUserClickListener?.let { item -> 22 | item(items, imageViewFav) 23 | } 24 | } 25 | getItemInstance?.let { 26 | it(items) 27 | } 28 | 29 | itemView.apply { 30 | GlideHelper.circularImage(context, items.avatarUrl.toString(), itemBinding.avatarUser) 31 | itemBinding.textUser.text = items.login 32 | setOnClickListener { 33 | onItemClickListener?.let { it(items) } 34 | } 35 | getViewFromAdapter?.let { views -> 36 | views(imageFav) 37 | } 38 | } 39 | } 40 | } 41 | 42 | private val differCallback = object : DiffUtil.ItemCallback() { 43 | override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean { 44 | return oldItem.id == newItem.id 45 | } 46 | 47 | override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean { 48 | return oldItem == newItem 49 | } 50 | } 51 | 52 | val differ = AsyncListDiffer(this, differCallback) 53 | 54 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder { 55 | val itemBinding = ItemListSearchedUsersBinding.inflate(LayoutInflater.from(parent.context), parent, false) 56 | return SearchViewHolder(itemBinding) 57 | } 58 | 59 | override fun getItemCount(): Int { 60 | return differ.currentList.size 61 | } 62 | 63 | private var onItemClickListener: ((Item) -> Unit)? = null 64 | private var onSaveUserClickListener: ((Item, ImageView) -> Unit)? = null 65 | private var getItemInstance: ((Item) -> Unit)? = null 66 | private var getViewFromAdapter: ((ImageView) -> Unit)? = null 67 | 68 | override fun onBindViewHolder(holder: SearchViewHolder, position: Int) { 69 | val searchInfo = differ.currentList[position] 70 | holder.bind(searchInfo) 71 | } 72 | 73 | fun setOnItemClickListener(listener: (Item) -> Unit) { 74 | onItemClickListener = listener 75 | } 76 | 77 | fun setOnSaveUserClickListener(listener: (Item, ImageView) -> Unit) { 78 | onSaveUserClickListener = listener 79 | } 80 | 81 | fun getItemInstance(listener: (Item) -> Unit) { 82 | getItemInstance = listener 83 | } 84 | 85 | fun getViewFromAdapter(listener: (ImageView) -> Unit) { 86 | getViewFromAdapter = listener 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/adapter/StarredAdapter.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.adapter 2 | 3 | import android.content.res.ColorStateList 4 | import android.graphics.Color 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.core.view.ViewCompat 9 | import androidx.recyclerview.widget.AsyncListDiffer 10 | import androidx.recyclerview.widget.DiffUtil 11 | import androidx.recyclerview.widget.RecyclerView 12 | import app.android.githubservice.databinding.ItemListReposBinding 13 | import app.android.githubservice.entity.starred.StarredResponse 14 | import app.android.githubservice.util.LanguageColorGenerator 15 | import app.android.githubservice.util.URL_GITHUB 16 | import app.android.githubservice.util.thousandPrinter 17 | 18 | 19 | class StarredAdapter : RecyclerView.Adapter() { 20 | 21 | inner class StarredViewHolder(private val itemBinding: ItemListReposBinding) : RecyclerView.ViewHolder(itemBinding.root) { 22 | fun bind(item: StarredResponse.StarredModelItem) { 23 | val urlRepo = URL_GITHUB + item.fullName 24 | itemView.apply { 25 | with(itemBinding) { 26 | textRepoName.text = item.fullName 27 | textRepoDesc.text = item.description 28 | textForks.text = item.forksCount.toString() 29 | if (item.stargazersCount!! > 1000 || item.forksCount!! > 1000) { 30 | val startValue = thousandPrinter(item.stargazersCount.toString()) 31 | val forkValue = thousandPrinter(item.forksCount.toString()) 32 | textStars.text = startValue 33 | textForks.text = forkValue 34 | } else { 35 | textStars.text = "☆ " + item.stargazersCount.toString() 36 | textForks.text = item.forksCount.toString() 37 | } 38 | 39 | val keyColor = item.language.toString() 40 | val codeColor = LanguageColorGenerator.getColors(context, keyColor) 41 | if (codeColor != null) 42 | ViewCompat.setBackgroundTintList(viewColoredLanguage, ColorStateList.valueOf(Color.parseColor(codeColor))) 43 | 44 | val language = item.language 45 | textLanguage.text = language 46 | if (language.isNullOrEmpty()) { 47 | textLanguage.text = "-" 48 | viewColoredLanguage.visibility = View.GONE 49 | } 50 | itemView.setOnClickListener { 51 | onItemClickListener?.let { 52 | it(urlRepo) 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | 61 | private val differCallback = object : DiffUtil.ItemCallback() { 62 | 63 | 64 | override fun areItemsTheSame(oldItem: StarredResponse.StarredModelItem, newItem: StarredResponse.StarredModelItem): Boolean { 65 | return oldItem.id == newItem.id 66 | } 67 | 68 | override fun areContentsTheSame(oldItem: StarredResponse.StarredModelItem, newItem: StarredResponse.StarredModelItem): Boolean { 69 | return oldItem == newItem 70 | } 71 | } 72 | 73 | val differ = AsyncListDiffer(this, differCallback) 74 | 75 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StarredViewHolder { 76 | val itemBinding = ItemListReposBinding.inflate(LayoutInflater.from(parent.context), parent, false) 77 | return StarredViewHolder(itemBinding) 78 | } 79 | 80 | override fun getItemCount(): Int { 81 | return differ.currentList.size 82 | } 83 | 84 | private var onItemClickListener: ((String) -> Unit)? = null 85 | 86 | override fun onBindViewHolder(holder: StarredViewHolder, position: Int) { 87 | val repoInfo = differ.currentList[position] 88 | holder.bind(repoInfo) 89 | } 90 | 91 | fun setOnRepoClickListener(listener: (String) -> Unit) { 92 | onItemClickListener = listener 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/adapter/ViewPagerProfileAdapter.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.adapter 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.fragment.app.FragmentStatePagerAdapter 6 | import app.android.githubservice.ui.fragment.FollowersFragment 7 | import app.android.githubservice.ui.fragment.FollowingFragment 8 | import app.android.githubservice.ui.fragment.ReposFragment 9 | 10 | class ViewPagerProfileAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) { 11 | 12 | 13 | override fun getItem(position: Int): Fragment { 14 | return when (position) { 15 | 0 -> return ReposFragment() 16 | 1 -> return FollowersFragment() 17 | 2 -> return FollowingFragment() 18 | else -> FollowersFragment() 19 | } 20 | } 21 | 22 | 23 | override fun getCount(): Int { 24 | return 3 25 | } 26 | 27 | override fun getPageTitle(position: Int): CharSequence? { 28 | when (position) { 29 | 0 -> return "Repository" 30 | 1 -> return "Followers" 31 | 2 -> return "Following" 32 | } 33 | return super.getPageTitle(position) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/fragment/FavoriteFragment.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.fragment 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.view.View 6 | import androidx.fragment.app.viewModels 7 | import androidx.lifecycle.Observer 8 | import androidx.recyclerview.widget.ItemTouchHelper 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import androidx.recyclerview.widget.RecyclerView 11 | import app.android.githubservice.R 12 | import app.android.githubservice.base.BaseFragment 13 | import app.android.githubservice.databinding.FragmentFavoriteBinding 14 | import app.android.githubservice.ui.adapter.FavoriteAdapter 15 | import app.android.githubservice.viewmodel.SearchViewModel 16 | import com.faramarzaf.sdk.af_android_sdk.core.helper.IntentHelper 17 | import com.faramarzaf.sdk.af_android_sdk.core.interfaces.CallbackSnackBar 18 | import com.faramarzaf.sdk.af_android_sdk.core.interfaces.DialogCallback 19 | import com.faramarzaf.sdk.af_android_sdk.core.ui.SimpleSnackbar 20 | import com.faramarzaf.sdk.af_android_sdk.core.ui.dialog.PublicDialog 21 | import dagger.hilt.android.AndroidEntryPoint 22 | import kotlinx.android.synthetic.main.fragment_favorite.* 23 | 24 | @AndroidEntryPoint 25 | class FavoriteFragment : BaseFragment() { 26 | 27 | private lateinit var favoriteAdapter: FavoriteAdapter 28 | private lateinit var binding: FragmentFavoriteBinding 29 | private val viewModel: SearchViewModel by viewModels() 30 | 31 | override val getFragmentLayout: Int 32 | get() = R.layout.fragment_favorite 33 | 34 | 35 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 36 | super.onViewCreated(view, savedInstanceState) 37 | binding = FragmentFavoriteBinding.bind(view) 38 | setupRecyclerView() 39 | swipeRemoving(view) 40 | observeUsersList() 41 | favoriteAdapter.setOnRepoClickListener { 42 | IntentHelper.openUrl(requireContext(),it) 43 | } 44 | binding.imgDeleteAll.setOnClickListener { 45 | deleteAllFavorites() 46 | } 47 | } 48 | 49 | private fun observeUsersList() { 50 | viewModel.getAllUsers().observe(viewLifecycleOwner, Observer { listFavorite -> 51 | favoriteAdapter.differ.submitList(listFavorite) 52 | if (listFavorite.isEmpty()) { 53 | binding.imgDeleteAll.isEnabled = false 54 | noDataAvailable() 55 | } else { 56 | binding.imgDeleteAll.isEnabled = true 57 | dataAvailable() 58 | } 59 | }) 60 | } 61 | 62 | private fun deleteAllFavorites() { 63 | PublicDialog.yesNoDialog(requireContext(), getString(R.string.remove_all_title), getString(R.string.msg_dialog_remove_all) 64 | , getString(R.string.yes), getString(R.string.no), R.drawable.ic_delete, object : DialogCallback { 65 | override fun onNegativeButtonClicked() { 66 | return 67 | } 68 | 69 | override fun onPositiveButtonClicked() { 70 | viewModel.deleteAll() 71 | } 72 | }) 73 | } 74 | 75 | private fun swipeRemoving(view: View) { 76 | val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { 77 | override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { 78 | return true 79 | } 80 | 81 | override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { 82 | val position = viewHolder.adapterPosition 83 | val user = favoriteAdapter.differ.currentList[position] 84 | viewModel.deleteUser(user) 85 | SimpleSnackbar.show(view, "Successfully deleted user", "Undo", Color.GRAY, 86 | Color.WHITE, Color.BLUE, true, object : CallbackSnackBar { 87 | override fun onActionClick() { 88 | viewModel.saveUser(user) 89 | } 90 | }) 91 | } 92 | } 93 | 94 | ItemTouchHelper(itemTouchHelperCallback).apply { 95 | attachToRecyclerView(rvSaved) 96 | } 97 | } 98 | 99 | 100 | private fun setupRecyclerView() { 101 | favoriteAdapter = FavoriteAdapter() 102 | binding.rvSaved.apply { 103 | setRecyclerviewDivider(context, this, R.drawable.divider_list) 104 | layoutManager = LinearLayoutManager(activity) 105 | adapter = favoriteAdapter 106 | } 107 | } 108 | 109 | private fun noDataAvailable() { 110 | binding.textNoFavUser.visibility = View.VISIBLE 111 | } 112 | 113 | private fun dataAvailable() { 114 | binding.textNoFavUser.visibility = View.GONE 115 | } 116 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/fragment/FollowersFragment.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.fragment 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.View 6 | import androidx.fragment.app.viewModels 7 | import androidx.lifecycle.Observer 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import app.android.githubservice.R 10 | import app.android.githubservice.base.BaseFragment 11 | import app.android.githubservice.databinding.FragmentFollowersBinding 12 | import app.android.githubservice.ui.adapter.FollowersFollowingAdapter 13 | import app.android.githubservice.util.* 14 | import app.android.githubservice.viewmodel.FollowersViewModel 15 | import com.faramarzaf.sdk.af_android_sdk.core.helper.IntentHelper 16 | import com.faramarzaf.sdk.af_android_sdk.core.util.MyPreferences 17 | import dagger.hilt.android.AndroidEntryPoint 18 | 19 | @AndroidEntryPoint 20 | class FollowersFragment : BaseFragment() { 21 | 22 | private lateinit var followersAdapter: FollowersFollowingAdapter 23 | private lateinit var binding: FragmentFollowersBinding 24 | private val viewModel: FollowersViewModel by viewModels() 25 | 26 | override val getFragmentLayout: Int 27 | get() = R.layout.fragment_followers 28 | 29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 30 | super.onViewCreated(view, savedInstanceState) 31 | binding = FragmentFollowersBinding.bind(view) 32 | getFollowers() 33 | observeFollowersRepositoryData() 34 | setupRecyclerView() 35 | followersAdapter.setOnRepoClickListener { 36 | IntentHelper.openUrl(requireContext(), it) 37 | } 38 | } 39 | 40 | 41 | private fun getFollowers() { 42 | viewModel.getFollowers(MyPreferences.readString(requireActivity(), KEY_USERNAME, DEFAULT_USER), MIN_PAGE, MAX_PAGE) 43 | } 44 | 45 | private fun observeFollowersRepositoryData() { 46 | showProgressBar(binding.followersProgressBar) 47 | viewModel.followersResponse.observe(viewLifecycleOwner, Observer { response -> 48 | when (response) { 49 | is Resource.Success -> { 50 | if (response.value.isEmpty()) { 51 | noDataAvailable() 52 | } else { 53 | dataAvailable() 54 | } 55 | hideProgressBar(binding.followersProgressBar) 56 | followersAdapter.differ.submitList(response.value) 57 | binding.rvFollowers.setPadding(0, 0, 0, 0) 58 | } 59 | is Resource.Failure -> { 60 | hideProgressBar(binding.followersProgressBar) 61 | if (response.isNetworkError) { 62 | toast("Check your connection!") 63 | } 64 | Log.d(TAG_LOG, "handleFollowersRepositoryData: $response") 65 | } 66 | } 67 | }) 68 | } 69 | 70 | private fun setupRecyclerView() { 71 | followersAdapter = FollowersFollowingAdapter() 72 | binding.rvFollowers.apply { 73 | setRecyclerviewDivider(context, this, R.drawable.divider_list) 74 | adapter = followersAdapter 75 | layoutManager = LinearLayoutManager(activity) 76 | 77 | } 78 | } 79 | 80 | private fun noDataAvailable() { 81 | binding.textNoFollowers.visibility = View.VISIBLE 82 | } 83 | 84 | private fun dataAvailable() { 85 | binding.textNoFollowers.visibility = View.GONE 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/fragment/FollowingFragment.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.fragment 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.View 6 | import androidx.fragment.app.viewModels 7 | import androidx.lifecycle.Observer 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import app.android.githubservice.R 10 | import app.android.githubservice.base.BaseFragment 11 | import app.android.githubservice.databinding.FragmentFollowingBinding 12 | import app.android.githubservice.ui.adapter.FollowersFollowingAdapter 13 | import app.android.githubservice.util.* 14 | import app.android.githubservice.viewmodel.FollowingViewModel 15 | import com.faramarzaf.sdk.af_android_sdk.core.helper.IntentHelper 16 | import com.faramarzaf.sdk.af_android_sdk.core.util.MyPreferences 17 | import dagger.hilt.android.AndroidEntryPoint 18 | 19 | @AndroidEntryPoint 20 | class FollowingFragment : BaseFragment() { 21 | 22 | private lateinit var followingAdapter: FollowersFollowingAdapter 23 | private lateinit var binding: FragmentFollowingBinding 24 | private val viewModel: FollowingViewModel by viewModels() 25 | 26 | override val getFragmentLayout: Int 27 | get() = R.layout.fragment_following 28 | 29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 30 | super.onViewCreated(view, savedInstanceState) 31 | binding = FragmentFollowingBinding.bind(view) 32 | getFollowing() 33 | setupRecyclerView() 34 | observeFollowingRepositoryData() 35 | followingAdapter.setOnRepoClickListener { 36 | IntentHelper.openUrl(requireContext(), it) 37 | } 38 | } 39 | 40 | private fun getFollowing() { 41 | viewModel.getFollowing(MyPreferences.readString(requireActivity(), KEY_USERNAME, DEFAULT_USER), MIN_PAGE, MAX_PAGE) 42 | } 43 | 44 | private fun observeFollowingRepositoryData() { 45 | showProgressBar(binding.followingProgressBar) 46 | viewModel.followingResponse.observe(viewLifecycleOwner, Observer { response -> 47 | when (response) { 48 | is Resource.Success -> { 49 | if (response.value.isEmpty()) { 50 | noDataAvailable() 51 | } else { 52 | dataAvailable() 53 | } 54 | hideProgressBar(binding.followingProgressBar) 55 | followingAdapter.differ.submitList(response.value) 56 | binding.rvFollowing.setPadding(0, 0, 0, 0) 57 | } 58 | is Resource.Failure -> { 59 | hideProgressBar(binding.followingProgressBar) 60 | if (response.isNetworkError) { 61 | toast("Check your connection!") 62 | } 63 | Log.d(TAG_LOG, "handleFollowingRepositoryData: $response") 64 | } 65 | } 66 | }) 67 | } 68 | 69 | private fun setupRecyclerView() { 70 | followingAdapter = FollowersFollowingAdapter() 71 | binding.rvFollowing.apply { 72 | setRecyclerviewDivider(context, this, R.drawable.divider_list) 73 | adapter = followingAdapter 74 | layoutManager = LinearLayoutManager(activity) 75 | } 76 | } 77 | 78 | private fun noDataAvailable() { 79 | binding.textNoFollowing.visibility = View.VISIBLE 80 | } 81 | 82 | private fun dataAvailable() { 83 | binding.textNoFollowing.visibility = View.GONE 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/fragment/ProfileFragment.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.viewModels 6 | import app.android.githubservice.R 7 | import app.android.githubservice.base.BaseFragment 8 | import app.android.githubservice.databinding.FragmentProfileBinding 9 | import app.android.githubservice.interfaces.GlobalBottomSheetCallBack 10 | import app.android.githubservice.ui.BottomSheetProfile 11 | import app.android.githubservice.ui.activity.LoginActivity 12 | import app.android.githubservice.ui.adapter.ViewPagerProfileAdapter 13 | import app.android.githubservice.util.* 14 | import app.android.githubservice.viewmodel.SearchViewModel 15 | import com.faramarzaf.sdk.af_android_sdk.core.helper.GlideHelper 16 | import com.faramarzaf.sdk.af_android_sdk.core.interfaces.DialogCallback 17 | import com.faramarzaf.sdk.af_android_sdk.core.interfaces.DoGuardTask 18 | import com.faramarzaf.sdk.af_android_sdk.core.ui.dialog.PublicDialog 19 | import com.faramarzaf.sdk.af_android_sdk.core.util.ClickGuard 20 | import com.faramarzaf.sdk.af_android_sdk.core.util.MyPreferences 21 | import dagger.hilt.android.AndroidEntryPoint 22 | import kotlinx.android.synthetic.main.fragment_profile.* 23 | 24 | 25 | @AndroidEntryPoint 26 | class ProfileFragment : BaseFragment(), DoGuardTask { 27 | 28 | private val viewModel: SearchViewModel by viewModels() 29 | private lateinit var binding: FragmentProfileBinding 30 | private val bottomSheetTheme = BottomSheetProfile() 31 | 32 | override val getFragmentLayout: Int 33 | get() = R.layout.fragment_profile 34 | 35 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 36 | super.onViewCreated(view, savedInstanceState) 37 | binding = FragmentProfileBinding.bind(view) 38 | with(binding) { 39 | viewPagerOverview.adapter = ViewPagerProfileAdapter(requireActivity().supportFragmentManager) 40 | tabLayoutOverview.setupWithViewPager(viewPagerOverview) 41 | } 42 | fillProfileInfo() 43 | bottomSheetOperations() 44 | ClickGuard.guardView(imgSetting, 800, this) 45 | } 46 | 47 | 48 | private fun bottomSheetOperations() { 49 | bottomSheetTheme.setOnBottomSheetClickListener(object : GlobalBottomSheetCallBack { 50 | override fun onLogoutClick() { 51 | openLogoutDialog() 52 | } 53 | }) 54 | } 55 | 56 | private fun fillProfileInfo() { 57 | with(binding) { 58 | GlideHelper.circularImage(requireContext(), MyPreferences.readString(requireContext(), KEY_AVATAR_URL, ""), avatarProfile) 59 | textUserNameProfile.text = MyPreferences.readString(requireContext(), KEY_USERNAME, "") 60 | textRepositoryProfile.text = MyPreferences.readString(requireContext(), KEY_SIZE_LIST_REPO, "") 61 | textFollowersProfile.text = MyPreferences.readString(requireContext(), KEY_NUMBER_FOLLOWERS, "") 62 | textFollowingProfile.text = MyPreferences.readString(requireContext(), KEY_NUMBER_FOLLOWING, "") 63 | } 64 | } 65 | 66 | override fun onGuard(view: View) { 67 | bottomSheetTheme.show(requireActivity().supportFragmentManager, TAG_BOTTOM_SHEET) 68 | } 69 | 70 | private fun openLogoutDialog() { 71 | PublicDialog.yesNoDialog(requireContext(), getString(R.string.logout_title), getString(R.string.msg_dialog_logout) 72 | , getString(R.string.yes), getString(R.string.no), R.drawable.ic_logout, object : DialogCallback { 73 | override fun onNegativeButtonClicked() { 74 | return 75 | } 76 | 77 | override fun onPositiveButtonClicked() { 78 | logout() 79 | } 80 | }) 81 | } 82 | 83 | private fun logout() { 84 | viewModel.deleteAll() 85 | MyPreferences.clearAll(requireContext()) 86 | toActivity(activity, LoginActivity::class.java) 87 | requireActivity().finish() 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/fragment/ReposFragment.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.fragment 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.View 6 | import androidx.fragment.app.viewModels 7 | import androidx.lifecycle.Observer 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import app.android.githubservice.R 10 | import app.android.githubservice.base.BaseFragment 11 | import app.android.githubservice.databinding.FragmentReposBinding 12 | import app.android.githubservice.ui.adapter.ReposAdapter 13 | import app.android.githubservice.util.* 14 | import app.android.githubservice.viewmodel.RepositoriesViewModel 15 | import com.faramarzaf.sdk.af_android_sdk.core.helper.IntentHelper 16 | import com.faramarzaf.sdk.af_android_sdk.core.util.MyPreferences 17 | import dagger.hilt.android.AndroidEntryPoint 18 | import kotlinx.android.synthetic.main.fragment_repos.* 19 | 20 | @AndroidEntryPoint 21 | class ReposFragment : BaseFragment() { 22 | 23 | 24 | private lateinit var reposAdapter: ReposAdapter 25 | private lateinit var binding: FragmentReposBinding 26 | private val viewModel: RepositoriesViewModel by viewModels() 27 | 28 | override val getFragmentLayout: Int 29 | get() = R.layout.fragment_repos 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | binding = FragmentReposBinding.bind(view) 34 | getRepos() 35 | observeRepositoryData() 36 | setupRecyclerView() 37 | reposAdapter.setOnRepoClickListener { 38 | IntentHelper.openUrl(requireContext(), it) 39 | } 40 | } 41 | 42 | private fun setupRecyclerView() { 43 | reposAdapter = ReposAdapter() 44 | binding.rvRepos.apply { 45 | setRecyclerviewDivider(context, this, R.drawable.divider_list) 46 | adapter = reposAdapter 47 | layoutManager = LinearLayoutManager(context) 48 | setItemViewCacheSize(CACHE_SIZE) 49 | } 50 | } 51 | 52 | private fun getRepos() { 53 | viewModel.getRepos(MyPreferences.readString(requireActivity(), KEY_USERNAME, DEFAULT_USER), MIN_PAGE, MAX_PAGE) 54 | } 55 | 56 | 57 | private fun observeRepositoryData() { 58 | showProgressBar(binding.reposProgressBar) 59 | viewModel.reposResponse.observe(viewLifecycleOwner, Observer { response -> 60 | when (response) { 61 | is Resource.Success -> { 62 | if (response.value.isEmpty()) { 63 | noDataAvailable() 64 | } else { 65 | dataAvailable() 66 | } 67 | hideProgressBar(binding.reposProgressBar) 68 | reposAdapter.differ.submitList(response.value) 69 | rvRepos.setPadding(0, 0, 0, 0) 70 | MyPreferences.writeString(requireContext(), KEY_SIZE_LIST_REPO, response.value.size.toString()) 71 | } 72 | is Resource.Failure -> { 73 | hideProgressBar(binding.reposProgressBar) 74 | if (response.isNetworkError) { 75 | toast("Check your connection!") 76 | } 77 | Log.d(TAG_LOG, "fetchRepositoryData: $response") 78 | } 79 | } 80 | }) 81 | } 82 | 83 | private fun noDataAvailable() { 84 | binding.textNoRepos.visibility = View.VISIBLE 85 | } 86 | 87 | private fun dataAvailable() { 88 | binding.textNoRepos.visibility = View.GONE 89 | } 90 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/fragment/SearchFragment.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.fragment 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.view.View 7 | import androidx.core.widget.addTextChangedListener 8 | import androidx.fragment.app.viewModels 9 | import androidx.lifecycle.Observer 10 | import androidx.lifecycle.lifecycleScope 11 | import androidx.recyclerview.widget.LinearLayoutManager 12 | import app.android.githubservice.R 13 | import app.android.githubservice.base.BaseFragment 14 | import app.android.githubservice.databinding.FragmentSearchBinding 15 | import app.android.githubservice.entity.search.Item 16 | import app.android.githubservice.ui.adapter.SearchAdapter 17 | import app.android.githubservice.util.MAX_PAGE 18 | import app.android.githubservice.util.MIN_PAGE 19 | import app.android.githubservice.util.Resource 20 | import app.android.githubservice.util.TAG_LOG 21 | import app.android.githubservice.viewmodel.SearchViewModel 22 | import dagger.hilt.android.AndroidEntryPoint 23 | import kotlinx.coroutines.Job 24 | import kotlinx.coroutines.delay 25 | import kotlinx.coroutines.launch 26 | 27 | @AndroidEntryPoint 28 | class SearchFragment : BaseFragment() { 29 | 30 | private lateinit var searchAdapter: SearchAdapter 31 | private lateinit var binding: FragmentSearchBinding 32 | private val viewModel: SearchViewModel by viewModels() 33 | 34 | override val getFragmentLayout: Int 35 | get() = R.layout.fragment_search 36 | 37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 38 | super.onViewCreated(view, savedInstanceState) 39 | binding = FragmentSearchBinding.bind(view) 40 | getUsersList() 41 | setupRecyclerView() 42 | observeSearchRepositoryData() 43 | favoriteUserOperation() 44 | } 45 | 46 | private fun favoriteUserOperation() { 47 | with(searchAdapter) { 48 | getItemInstance { 49 | getViewFromAdapter { views -> 50 | if (!viewModel.userExists(it)) { 51 | views.setColorFilter(Color.WHITE) 52 | } else 53 | views.setColorFilter(Color.RED) 54 | } 55 | } 56 | setOnItemClickListener { 57 | toast(it.login) 58 | } 59 | setOnSaveUserClickListener { item, favImg -> 60 | 61 | if (!viewModel.userExists(item)) { 62 | favImg.setColorFilter(Color.RED) 63 | saveUser(item) 64 | } else if (viewModel.userExists(item)) { 65 | favImg.setColorFilter(Color.WHITE) 66 | viewModel.deleteUser(item) 67 | } 68 | } 69 | } 70 | } 71 | 72 | private fun getUsersList() { 73 | var job: Job? = null 74 | binding.editTextSearch.addTextChangedListener { editable -> 75 | job?.cancel() 76 | job = lifecycleScope.launch { 77 | showProgressBar(binding.searchProgressBar) 78 | delay(500) 79 | editable?.let { 80 | if (editable.toString().isNotEmpty()) { 81 | viewModel.searchUser(editable.toString(), MIN_PAGE, MAX_PAGE) 82 | } else { 83 | hideProgressBar(binding.searchProgressBar) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | private fun observeSearchRepositoryData() { 91 | viewModel.searchResponse.observe(viewLifecycleOwner, Observer { response -> 92 | when (response) { 93 | is Resource.Success -> { 94 | hideProgressBar(binding.searchProgressBar) 95 | searchAdapter.differ.submitList(response.value.items) 96 | binding.rvSearch.setPadding(0, 0, 0, 0) 97 | } 98 | is Resource.Failure -> { 99 | hideProgressBar(binding.searchProgressBar) 100 | if (response.isNetworkError) { 101 | toast("Check your connection!") 102 | } 103 | Log.d(TAG_LOG, "handleSearchRepositoryData: $response") 104 | } 105 | } 106 | }) 107 | } 108 | 109 | private fun saveUser(user: Item) { 110 | viewModel.saveUser(user) 111 | } 112 | 113 | private fun setupRecyclerView() { 114 | searchAdapter = SearchAdapter() 115 | binding.rvSearch.apply { 116 | setRecyclerviewDivider(context, this, R.drawable.divider_list) 117 | adapter = searchAdapter 118 | layoutManager = LinearLayoutManager(activity) 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/ui/fragment/StarredFragment.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.ui.fragment 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.View 6 | import androidx.fragment.app.viewModels 7 | import androidx.lifecycle.Observer 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import app.android.githubservice.R 10 | import app.android.githubservice.base.BaseFragment 11 | import app.android.githubservice.databinding.FragmentStarredBinding 12 | import app.android.githubservice.ui.adapter.StarredAdapter 13 | import app.android.githubservice.util.* 14 | import app.android.githubservice.viewmodel.StarredViewModel 15 | import com.faramarzaf.sdk.af_android_sdk.core.helper.IntentHelper 16 | import com.faramarzaf.sdk.af_android_sdk.core.util.MyPreferences 17 | import dagger.hilt.android.AndroidEntryPoint 18 | import kotlinx.android.synthetic.main.fragment_starred.* 19 | 20 | @AndroidEntryPoint 21 | class StarredFragment : BaseFragment() { 22 | 23 | private lateinit var starredAdapter: StarredAdapter 24 | private lateinit var binding: FragmentStarredBinding 25 | private val viewModel: StarredViewModel by viewModels() 26 | 27 | override val getFragmentLayout: Int 28 | get() = R.layout.fragment_starred 29 | 30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 31 | super.onViewCreated(view, savedInstanceState) 32 | binding = FragmentStarredBinding.bind(view) 33 | viewModel.getStarredRepositories(MyPreferences.readString(requireActivity(), KEY_USERNAME, DEFAULT_USER), MIN_PAGE, MAX_PAGE) 34 | setupRecyclerView() 35 | observeStarredRepositoryData() 36 | starredAdapter.setOnRepoClickListener { 37 | IntentHelper.openUrl(requireContext(), it) 38 | } 39 | } 40 | 41 | private fun observeStarredRepositoryData() { 42 | showProgressBar(binding.starredProgressBar) 43 | viewModel.starredResponse.observe(viewLifecycleOwner, Observer { response -> 44 | when (response) { 45 | is Resource.Success -> { 46 | if (response.value.isEmpty()) { 47 | noDataAvailable() 48 | } else { 49 | dataAvailable() 50 | } 51 | hideProgressBar(binding.starredProgressBar) 52 | starredAdapter.differ.submitList(response.value) 53 | rvStarred.setPadding(0, 0, 0, 0) 54 | } 55 | is Resource.Failure -> { 56 | hideProgressBar(binding.starredProgressBar) 57 | if (response.isNetworkError) { 58 | toast("Check your connection!") 59 | } 60 | Log.d(TAG_LOG, "fetchStarredRepositoryData: $response") 61 | } 62 | } 63 | }) 64 | } 65 | 66 | 67 | private fun setupRecyclerView() { 68 | starredAdapter = StarredAdapter() 69 | binding.rvStarred.apply { 70 | setRecyclerviewDivider(context, this, R.drawable.divider_list) 71 | adapter = starredAdapter 72 | layoutManager = LinearLayoutManager(context) 73 | setItemViewCacheSize(CACHE_SIZE) 74 | } 75 | } 76 | 77 | private fun noDataAvailable() { 78 | binding.textNoStarred.visibility = View.VISIBLE 79 | } 80 | 81 | private fun dataAvailable() { 82 | binding.textNoStarred.visibility = View.GONE 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/util/Constants.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.util 2 | 3 | 4 | const val KEY_USERNAME = "username" 5 | const val KEY_SESSION_ID = "session-id" 6 | const val KEY_HTML_URL = "htmlurl" 7 | const val KEY_AVATAR_URL = "avatarturl" 8 | const val BASE_URL = "https://api.github.com" 9 | const val URL_GITHUB = "https://github.com/" 10 | const val DEFAULT_USER = "faramarzaf" 11 | const val MAX_PAGE = 10000 12 | const val MIN_PAGE = 1 13 | const val KEY_SIZE_LIST_REPO = "key_size_list_repo" 14 | const val KEY_NUMBER_FOLLOWERS = "key_number_followers" 15 | const val KEY_NUMBER_FOLLOWING = "key_number_following" 16 | const val TAG_LOG = "TAG00" 17 | const val DATABASE_NAME = "github_db.db" 18 | const val DELAY_GUARD_CLICK = 800L 19 | const val TAG_BOTTOM_SHEET = "bottom_sheet" 20 | const val CACHE_SIZE = 500 21 | -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/util/LanguageColorGenerator.java: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.util; 2 | 3 | import android.content.Context; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.reflect.TypeToken; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.io.Reader; 13 | import java.io.StringWriter; 14 | import java.io.Writer; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import app.android.githubservice.R; 19 | 20 | public class LanguageColorGenerator { 21 | 22 | public static String getColors(Context context, String key) { 23 | InputStream is = context.getResources().openRawResource(R.raw.colors); 24 | Writer writer = new StringWriter(); 25 | char[] buffer = new char[1024]; 26 | 27 | try { 28 | Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); 29 | int n; 30 | while ((n = reader.read(buffer)) != -1) { 31 | writer.write(buffer, 0, n); 32 | } 33 | } catch (IOException e) { 34 | e.printStackTrace(); 35 | } finally { 36 | try { 37 | is.close(); 38 | } catch (IOException e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | String jsonstring = writer.toString(); 43 | Map map = new Gson().fromJson(jsonstring, new TypeToken>() { 44 | }.getType()); 45 | 46 | 47 | return map.get(key); 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/util/Resource.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.util 2 | 3 | import okhttp3.ResponseBody 4 | 5 | sealed class Resource { 6 | data class Success(val value: T) : Resource() 7 | data class Failure(val isNetworkError: Boolean, val errorCode: Int?, val errorBody: ResponseBody?) : Resource() 8 | 9 | /* data class Loading(val isLoading: Boolean) : Resource() 10 | object Loading2 : Resource()*/ 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/util/Utility.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.util 2 | 3 | fun thousandPrinter(string: String): String { 4 | return string.substring(0, 2) + "K" 5 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/util/docs.txt: -------------------------------------------------------------------------------- 1 | "current_user_url": "https://api.github.com/user", 2 | "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}", 3 | "authorizations_url": "https://api.github.com/authorizations", 4 | "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}", 5 | "commit_search_url": "https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}", 6 | "emails_url": "https://api.github.com/user/emails", 7 | "emojis_url": "https://api.github.com/emojis", 8 | "events_url": "https://api.github.com/events", 9 | "feeds_url": "https://api.github.com/feeds", 10 | "followers_url": "https://api.github.com/user/followers", 11 | "following_url": "https://api.github.com/user/following{/target}", 12 | "gists_url": "https://api.github.com/gists{/gist_id}", 13 | "hub_url": "https://api.github.com/hub", 14 | "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}", 15 | "issues_url": "https://api.github.com/issues", 16 | "keys_url": "https://api.github.com/user/keys", 17 | "label_search_url": "https://api.github.com/search/labels?q={query}&repository_id={repository_id}{&page,per_page}", 18 | "notifications_url": "https://api.github.com/notifications", 19 | "organization_url": "https://api.github.com/orgs/{org}", 20 | "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}", 21 | "organization_teams_url": "https://api.github.com/orgs/{org}/teams", 22 | "public_gists_url": "https://api.github.com/gists/public", 23 | "rate_limit_url": "https://api.github.com/rate_limit", 24 | "repository_url": "https://api.github.com/repos/{owner}/{repo}", 25 | "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}", 26 | "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}", 27 | "starred_url": "https://api.github.com/user/starred{/owner}{/repo}", 28 | "starred_gists_url": "https://api.github.com/gists/starred", 29 | "user_url": "https://api.github.com/users/{user}", 30 | "user_organizations_url": "https://api.github.com/user/orgs", 31 | "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}", 32 | "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order} 33 | 34 | 35 | 36 | 37 | https://api.github.com/search/users?q=USERNAME 38 | https://api.github.com/users/USERNAME/repos 39 | https://api.github.com/events 40 | https://api.github.com/feeds 41 | https://api.github.com/users/USERNAME/followers 42 | https://api.github.com/users/USERNAME/following 43 | https://api.github.com/users/USERNAME/starred 44 | -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/viewmodel/AuthViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.viewmodel 2 | 3 | import androidx.hilt.lifecycle.ViewModelInject 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import app.android.githubservice.entity.search.SearchResponse 9 | import app.android.githubservice.repository.AuthRepository 10 | import app.android.githubservice.util.Resource 11 | import kotlinx.coroutines.launch 12 | 13 | class AuthViewModel @ViewModelInject constructor(private val authRepository: AuthRepository) : ViewModel() { 14 | 15 | private var _loginResponse: MutableLiveData> = MutableLiveData() 16 | val loginResponse: LiveData> 17 | get() = _loginResponse 18 | 19 | 20 | fun auth(username: String) = viewModelScope.launch { 21 | _loginResponse.value = authRepository.authUser(username) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/viewmodel/EventsViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.viewmodel 2 | 3 | import androidx.hilt.lifecycle.ViewModelInject 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import app.android.githubservice.entity.event.Events 9 | import app.android.githubservice.repository.EventsRepository 10 | import app.android.githubservice.util.Resource 11 | import kotlinx.coroutines.launch 12 | 13 | class EventsViewModel @ViewModelInject constructor(private val eventsRepository: EventsRepository) : ViewModel() { 14 | 15 | private var _eventsResponse: MutableLiveData> = MutableLiveData() 16 | val eventsResponse: LiveData> 17 | get() = _eventsResponse 18 | 19 | fun getEvents() = viewModelScope.launch { 20 | _eventsResponse.value = eventsRepository.getEvents() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/viewmodel/FollowersViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.viewmodel 2 | 3 | import androidx.hilt.lifecycle.ViewModelInject 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import app.android.githubservice.entity.follower_following.FollowerFollowingResponse 9 | import app.android.githubservice.entity.search.SearchResponse 10 | import app.android.githubservice.repository.FollowersRepository 11 | import app.android.githubservice.util.Resource 12 | import kotlinx.coroutines.launch 13 | 14 | class FollowersViewModel @ViewModelInject constructor(private val followersRepository: FollowersRepository) : ViewModel() { 15 | 16 | private var _followersResponse: MutableLiveData> = MutableLiveData() 17 | val followersResponse: LiveData> 18 | get() = _followersResponse 19 | 20 | 21 | fun getFollowers(username: String, page: Int, per_page: Int) = viewModelScope.launch { 22 | _followersResponse.value = followersRepository.getFollowers(username,page, per_page) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/viewmodel/FollowingViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.viewmodel 2 | 3 | import androidx.hilt.lifecycle.ViewModelInject 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import app.android.githubservice.entity.follower_following.FollowerFollowingResponse 9 | import app.android.githubservice.repository.FollowingRepository 10 | import app.android.githubservice.util.Resource 11 | import kotlinx.coroutines.launch 12 | 13 | class FollowingViewModel @ViewModelInject constructor(val followingRepository: FollowingRepository) : ViewModel() { 14 | 15 | private var _followingResponse: MutableLiveData> = MutableLiveData() 16 | val followingResponse: LiveData> 17 | get() = _followingResponse 18 | 19 | 20 | fun getFollowing(username: String, page: Int, per_page: Int) = viewModelScope.launch { 21 | _followingResponse.value = followingRepository.getFollowing(username,page, per_page) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/viewmodel/RepositoriesViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.viewmodel 2 | 3 | import androidx.hilt.lifecycle.ViewModelInject 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import app.android.githubservice.entity.repo.RepositoryResponse 9 | import app.android.githubservice.repository.ReposRepository 10 | import app.android.githubservice.util.Resource 11 | import kotlinx.coroutines.launch 12 | 13 | class RepositoriesViewModel @ViewModelInject constructor(private val reposRepository: ReposRepository) : ViewModel() { 14 | 15 | private var _reposResponse: MutableLiveData> = MutableLiveData() 16 | val reposResponse: LiveData> 17 | get() = _reposResponse 18 | 19 | 20 | fun getRepos(username: String, page: Int, per_page: Int) = viewModelScope.launch { 21 | _reposResponse.value = reposRepository.getRepos(username, page, per_page) 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/viewmodel/SearchViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.viewmodel 2 | 3 | import androidx.hilt.lifecycle.ViewModelInject 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import app.android.githubservice.entity.search.Item 9 | import app.android.githubservice.entity.search.SearchResponse 10 | import app.android.githubservice.util.Resource 11 | import app.android.githubservice.repository.SearchRepository 12 | import kotlinx.coroutines.launch 13 | 14 | class SearchViewModel @ViewModelInject constructor (private val searchRepository: SearchRepository) : ViewModel() { 15 | 16 | private var _searchResponse: MutableLiveData> = MutableLiveData() 17 | val searchResponse: LiveData> 18 | get() = _searchResponse 19 | 20 | 21 | fun searchUser(username: String, page: Int, per_page: Int) = 22 | viewModelScope.launch { 23 | _searchResponse.value = searchRepository.searchUser(username, page, per_page) 24 | } 25 | 26 | fun saveUser(user: Item) = viewModelScope.launch { 27 | searchRepository.insert(user) 28 | } 29 | 30 | fun deleteUser(user: Item) = viewModelScope.launch { 31 | searchRepository.deleteUser(user) 32 | } 33 | 34 | fun deleteAll() = viewModelScope.launch { 35 | searchRepository.deleteAll() 36 | } 37 | 38 | fun getAllUsers() = searchRepository.getSavedUsers() 39 | 40 | fun userExists(user: Item) = searchRepository.userExists(user) 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/app/android/githubservice/viewmodel/StarredViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice.viewmodel 2 | 3 | import androidx.hilt.lifecycle.ViewModelInject 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import app.android.githubservice.entity.starred.StarredResponse 9 | import app.android.githubservice.util.Resource 10 | import app.android.githubservice.repository.StarredRepository 11 | import kotlinx.coroutines.launch 12 | 13 | class StarredViewModel @ViewModelInject constructor(val starredRepository: StarredRepository) : ViewModel() { 14 | 15 | private var _starredResponse: MutableLiveData> = MutableLiveData() 16 | val starredResponse: LiveData> 17 | get() = _starredResponse 18 | 19 | 20 | fun getStarredRepositories(username: String, page: Int, per_page: Int) = viewModelScope.launch { 21 | _starredResponse.value = starredRepository.getStarredRepositories(username, page, per_page) 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/left_side.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/color/bnv_tab_item_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_button_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_dot_language.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_overview_titles.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/divider_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_events.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fav.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fork.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_app_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lock.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 15 | 18 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logout.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_repo.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_setting.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_corner_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_corner_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/font.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/font/sansserif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/font/sansserif.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 32 | 33 | 48 | 49 | 65 | 66 | 75 | 76 | 87 | 88 | 103 | 104 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 14 | 15 | 24 | 25 | 26 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 20 | 21 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 19 | 20 | 21 | 28 | 29 | 44 | 45 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_events.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 22 | 23 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_favorite.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 24 | 25 | 36 | 37 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_followers.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 31 | 32 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_following.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 20 | 21 | 32 | 33 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 16 | 17 | 22 | 23 | 33 | 34 | 42 | 43 | 44 | 45 | 46 | 51 | 52 | 61 | 62 | 71 | 72 | 73 | 82 | 83 | 84 | 85 | 98 | 99 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_repos.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 32 | 33 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 29 | 30 | 42 | 43 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_starred.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 31 | 32 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_list_events.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 14 | 20 | 21 | 22 | 28 | 29 | 38 | 39 | 47 | 48 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_list_favorite.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_list_followers_following.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_list_repos.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 28 | 29 | 34 | 35 | 44 | 45 | 51 | 52 | 61 | 62 | 73 | 74 | 75 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_list_searched_users.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 35 | 36 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_app_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-hdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_app_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-hdpi/ic_launcher_app_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-mdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_app_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-mdpi/ic_launcher_app_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xhdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_app_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xhdpi/ic_launcher_app_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xxhdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_app_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xxhdpi/ic_launcher_app_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xxxhdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_app_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xxxhdpi/ic_launcher_app_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 18 | 23 | 28 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/raw/colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "Mercury": "#ff2b2b", 3 | "TypeScript": "#2b7489", 4 | "PureBasic": "#5a6986", 5 | "Objective-C++": "#6866fb", 6 | "Self": "#0579aa", 7 | "edn": "#db5855", 8 | "NewLisp": "#87AED7", 9 | "Jupyter Notebook": "#DA5B0B", 10 | "Rebol": "#358a5b", 11 | "Frege": "#00cafe", 12 | "Dart": "#00B4AB", 13 | "AspectJ": "#a957b0", 14 | "Shell": "#89e051", 15 | "Web Ontology Language": "#9cc9dd", 16 | "xBase": "#403a40", 17 | "Eiffel": "#946d57", 18 | "Nix": "#7e7eff", 19 | "RAML": "#77d9fb", 20 | "MTML": "#b7e1f4", 21 | "Racket": "#22228f", 22 | "Elixir": "#6e4a7e", 23 | "SAS": "#B34936", 24 | "Agda": "#315665", 25 | "wisp": "#7582D1", 26 | "D": "#ba595e", 27 | "Kotlin": "#F18E33", 28 | "Opal": "#f7ede0", 29 | "Crystal": "#776791", 30 | "Objective-C": "#438eff", 31 | "ColdFusion CFC": "#ed2cd6", 32 | "Oz": "#fab738", 33 | "Mirah": "#c7a938", 34 | "Objective-J": "#ff0c5a", 35 | "Gosu": "#82937f", 36 | "FreeMarker": "#0050b2", 37 | "Ruby": "#701516", 38 | "Component Pascal": "#b0ce4e", 39 | "Arc": "#aa2afe", 40 | "Brainfuck": "#2F2530", 41 | "Nit": "#009917", 42 | "APL": "#5A8164", 43 | "Go": "#375eab", 44 | "Visual Basic": "#945db7", 45 | "PHP": "#4F5D95", 46 | "Cirru": "#ccccff", 47 | "SQF": "#3F3F3F", 48 | "Glyph": "#e4cc98", 49 | "Java": "#b07219", 50 | "MAXScript": "#00a6a6", 51 | "Scala": "#DC322F", 52 | "Makefile": "#427819", 53 | "ColdFusion": "#ed2cd6", 54 | "Perl": "#0298c3", 55 | "Lua": "#000080", 56 | "Vue": "#2c3e50", 57 | "Verilog": "#b2b7f8", 58 | "Factor": "#636746", 59 | "Haxe": "#df7900", 60 | "Pure Data": "#91de79", 61 | "Forth": "#341708", 62 | "Red": "#ee0000", 63 | "Hy": "#7790B2", 64 | "Volt": "#1F1F1F", 65 | "LSL": "#3d9970", 66 | "eC": "#913960", 67 | "CoffeeScript": "#244776", 68 | "HTML": "#e44b23", 69 | "Lex": "#DBCA00", 70 | "API Blueprint": "#2ACCA8", 71 | "Swift": "#ffac45", 72 | "C": "#555555", 73 | "AutoHotkey": "#6594b9", 74 | "Isabelle": "#FEFE00", 75 | "Metal": "#8f14e9", 76 | "Clarion": "#db901e", 77 | "JSONiq": "#40d47e", 78 | "Boo": "#d4bec1", 79 | "AutoIt": "#1C3552", 80 | "Clojure": "#db5855", 81 | "Rust": "#dea584", 82 | "Prolog": "#74283c", 83 | "SourcePawn": "#5c7611", 84 | "AMPL": "#E6EFBB", 85 | "FORTRAN": "#4d41b1", 86 | "ANTLR": "#9DC3FF", 87 | "Harbour": "#0e60e3", 88 | "Tcl": "#e4cc98", 89 | "BlitzMax": "#cd6400", 90 | "PigLatin": "#fcd7de", 91 | "Lasso": "#999999", 92 | "ECL": "#8a1267", 93 | "VHDL": "#adb2cb", 94 | "Elm": "#60B5CC", 95 | "Propeller Spin": "#7fa2a7", 96 | "X10": "#4B6BEF", 97 | "IDL": "#a3522f", 98 | "ATS": "#1ac620", 99 | "Ada": "#02f88c", 100 | "Unity3D Asset": "#ab69a1", 101 | "Nu": "#c9df40", 102 | "LFE": "#004200", 103 | "SuperCollider": "#46390b", 104 | "Oxygene": "#cdd0e3", 105 | "ASP": "#6a40fd", 106 | "Assembly": "#6E4C13", 107 | "Gnuplot": "#f0a9f0", 108 | "JFlex": "#DBCA00", 109 | "NetLinx": "#0aa0ff", 110 | "Turing": "#45f715", 111 | "Vala": "#fbe5cd", 112 | "Processing": "#0096D8", 113 | "Arduino": "#bd79d1", 114 | "FLUX": "#88ccff", 115 | "NetLogo": "#ff6375", 116 | "C Sharp": "#178600", 117 | "CSS": "#563d7c", 118 | "Emacs Lisp": "#c065db", 119 | "Stan": "#b2011d", 120 | "SaltStack": "#646464", 121 | "QML": "#44a51c", 122 | "Pike": "#005390", 123 | "LOLCODE": "#cc9900", 124 | "ooc": "#b0b77e", 125 | "Handlebars": "#01a9d6", 126 | "J": "#9EEDFF", 127 | "Mask": "#f97732", 128 | "EmberScript": "#FFF4F3", 129 | "TeX": "#3D6117", 130 | "Nemerle": "#3d3c6e", 131 | "KRL": "#28431f", 132 | "Ren'Py": "#ff7f7f", 133 | "Unified Parallel C": "#4e3617", 134 | "Golo": "#88562A", 135 | "Fancy": "#7b9db4", 136 | "OCaml": "#3be133", 137 | "Shen": "#120F14", 138 | "Pascal": "#b0ce4e", 139 | "F#": "#b845fc", 140 | "Puppet": "#302B6D", 141 | "ActionScript": "#882B0F", 142 | "Diff": "#88dddd", 143 | "Ragel in Ruby Host": "#9d5200", 144 | "Fantom": "#dbded5", 145 | "Zephir": "#118f9e", 146 | "Click": "#E4E6F3", 147 | "Smalltalk": "#596706", 148 | "DM": "#447265", 149 | "Ioke": "#078193", 150 | "PogoScript": "#d80074", 151 | "LiveScript": "#499886", 152 | "JavaScript": "#f1e05a", 153 | "VimL": "#199f4b", 154 | "PureScript": "#1D222D", 155 | "ABAP": "#E8274B", 156 | "Matlab": "#bb92ac", 157 | "Slash": "#007eff", 158 | "R": "#198ce7", 159 | "Erlang": "#B83998", 160 | "Pan": "#cc0000", 161 | "LookML": "#652B81", 162 | "Eagle": "#814C05", 163 | "Scheme": "#1e4aec", 164 | "PLSQL": "#dad8d8", 165 | "Python": "#3572A5", 166 | "Max": "#c4a79c", 167 | "Common Lisp": "#3fb68b", 168 | "Latte": "#A8FF97", 169 | "XQuery": "#5232e7", 170 | "Omgrofl": "#cabbff", 171 | "XC": "#99DA07", 172 | "Nimrod": "#37775b", 173 | "SystemVerilog": "#DAE1C2", 174 | "Chapel": "#8dc63f", 175 | "Groovy": "#e69f56", 176 | "Dylan": "#6c616e", 177 | "E": "#ccce35", 178 | "Parrot": "#f3ca0a", 179 | "Grammatical Framework": "#79aa7a", 180 | "Game Maker Language": "#8fb200", 181 | "Papyrus": "#6600cc", 182 | "NetLinx+ERB": "#747faa", 183 | "Clean": "#3F85AF", 184 | "Alloy": "#64C800", 185 | "Squirrel": "#800000", 186 | "PAWN": "#dbb284", 187 | "UnrealScript": "#a54c4d", 188 | "Standard ML": "#dc566d", 189 | "Slim": "#ff8f77", 190 | "Perl6": "#0000fb", 191 | "Julia": "#a270ba", 192 | "Haskell": "#29b544", 193 | "NCL": "#28431f", 194 | "Io": "#a9188d", 195 | "Rouge": "#cc0088", 196 | "cpp": "#f34b7d", 197 | "AGS Script": "#B9D9FF", 198 | "Dogescript": "#cca760", 199 | "nesC": "#94B0C7" 200 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #6200EE 5 | #3700B3 6 | #03DAC5 7 | 8 | #000000 9 | #FFFFFF 10 | 11 | #03DAC5 12 | #fff 13 | #6F0AFF 14 | #7524E8 15 | #9055E4 16 | #43A047 17 | #101010 18 | #151515 19 | #232323 20 | #303030 21 | #252525 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2dp 5 | 4dp 6 | 6dp 7 | 8dp 8 | 10dp 9 | 12dp 10 | 16dp 11 | 24dp 12 | 32dp 13 | 64dp 14 | 15 | 16 | 2dp 17 | 4dp 18 | 6dp 19 | 8dp 20 | 10dp 21 | 12dp 22 | 16dp 23 | 24dp 24 | 32dp 25 | 64dp 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_app_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | GitHubApp 3 | Hello blank fragment 4 | fonts/sansserif.ttf 5 | Login 6 | Remember me 7 | username 8 | Repositories 9 | Events 10 | Repository 11 | starred 12 | Search 13 | Favorites 14 | Settings 15 | No favorite user yet! 16 | Profile 17 | Followers 18 | Following 19 | Logout! 20 | Are you sure you want to logout? 21 | Yes 22 | No 23 | Remove all! 24 | Are you sure you want to remove saved users? 25 | You don\'t have any repository yet! 26 | No starred repository yet! 27 | You don\'t have followers yet! 28 | You don\'t have following yet! 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 14 | 15 | 19 | 20 | 25 | 26 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/test/java/app/android/githubservice/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package app.android.githubservice 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /bash.exe.stackdump: -------------------------------------------------------------------------------- 1 | Stack trace: 2 | Frame Function Args 3 | 000FFFFA1C8 0018005E0DE (0018024F1ED, 00180230C26, 000FFFFA1C8, 000FFFF90C0) 4 | 000FFFFA1C8 001800468F9 (00000000000, 00000000000, 00000000000, 001005F0D44) 5 | 000FFFFA1C8 00180046932 (0018024F2A9, 000FFFFA078, 000FFFFA1C8, 00000000000) 6 | 000FFFFA1C8 001800AA8D8 (00000000000, 00000000000, 00000000000, 00000000000) 7 | 000FFFFA1C8 001800AAA5D (000FFFFA1E0, 00000000000, 00000000000, 00000000000) 8 | 000FFFFA450 001800ABE73 (000FFFFA1E0, 00000000000, 00000000000, 00000000000) 9 | End of stack trace 10 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | apply from: 'dependencies.gradle' 4 | ext.kotlin_version = "1.3.72" 5 | 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | dependencies { 11 | classpath "com.android.tools.build:gradle:4.0.2" 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.2" 14 | classpath "com.google.dagger:hilt-android-gradle-plugin:2.28.3-alpha" 15 | 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | google() 24 | jcenter() 25 | maven { url 'https://jitpack.io' } 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } -------------------------------------------------------------------------------- /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=-Xmx4608m 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 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 19 12:08:34 IRDT 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.1.1-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /screenshots/GitHubApplication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/screenshots/GitHubApplication.png -------------------------------------------------------------------------------- /screenshots/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/screenshots/arch.png -------------------------------------------------------------------------------- /screenshots/name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/screenshots/name.png -------------------------------------------------------------------------------- /screenshots/poster1a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faramarzaf/GitHubApplication/f3b75a69c57ee0d07b04b6b09e1e30e52edb1c22/screenshots/poster1a.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' --------------------------------------------------------------------------------