Skip to content

Commit 1b2036d

Browse files
committed
Release v2.1. Merge branch 'develop'
2 parents e32189d + 8ab2586 commit 1b2036d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1378
-829
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ GitStat is a simple android app designed to aggregate Github profile data into i
1414
### Technology (some notes)
1515

1616
- Used single activity approach and [Navigation component](https://developer.android.com/guide/navigation) to navigate across fragments.
17+
- Used [Hilt](https://dagger.dev/hilt/) for dependency injection.
1718
- [View binding](https://developer.android.com/topic/libraries/view-binding) is used to interact with views within fragments and recyclerview adapters.
1819
- Kotlin coroutines are used for asynchronous operations.
1920
- [Retrofit](https://github.com/square/retrofit) is used to perform [Github API](https://docs.github.com/en/rest) calls to obtain the data. Also use [OkHttp Logging Interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor) to log requests.
@@ -31,4 +32,4 @@ used to implement languages filter (see third screenshot).
3132
- Create personal access token with ```read:user``` and ```repo``` access scopes. (**Note**: full ```repo``` scope is used only to have access to your private repos data. No malicious write operations are performed by the app).
3233
- Use the obtained token as auth credetial in the application login form.
3334

34-
More user-friendly auth method may be implemented later. Authorization process in the app **needs additional research and refactoring**.
35+
More user-friendly **OAuth** method may be implemented later.

app/build.gradle

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id 'com.android.application'
33
id 'kotlin-android'
44
id 'kotlin-kapt'
5+
id("dagger.hilt.android.plugin")
56
}
67

78
def keystorePropertiesFile = rootProject.file("keystore.properties")
@@ -25,8 +26,8 @@ android {
2526
applicationId 'com.alexandr7035.gitstat'
2627
minSdkVersion 21
2728
targetSdkVersion 30
28-
versionCode 100
29-
versionName "1.0"
29+
versionCode 300
30+
versionName "2.1"
3031

3132
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
3233
}
@@ -71,7 +72,7 @@ dependencies {
7172
implementation 'androidx.navigation:navigation-ui-ktx:2.3.3'
7273
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
7374
implementation 'androidx.gridlayout:gridlayout:1.0.0'
74-
testImplementation 'junit:junit:4.+'
75+
testImplementation 'junit:junit:4.13.2'
7576
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
7677
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
7778

@@ -90,4 +91,15 @@ dependencies {
9091
annotationProcessor "androidx.room:room-compiler:$room_version"
9192
kapt("androidx.room:room-compiler:$room_version")
9293
implementation 'com.google.android.flexbox:flexbox:3.0.0'
94+
95+
96+
// Hilt
97+
implementation("com.google.dagger:hilt-android:2.38.1")
98+
kapt("com.google.dagger:hilt-android-compiler:2.38.1")
99+
100+
}
101+
102+
// Allow references to generated code
103+
kapt {
104+
correctErrorTypes true
93105
}

app/src/main/java/com/alexandr7035/gitstat/core/App.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.alexandr7035.gitstat.core
22

33
import android.app.Application
4+
import dagger.hilt.android.HiltAndroidApp
45

6+
@HiltAndroidApp
57
class App: Application() {
68

79
lateinit var progLangManager: ProgLangManager
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.alexandr7035.gitstat.core
2+
3+
import android.app.Application
4+
import android.content.Context
5+
import com.alexandr7035.gitstat.R
6+
import javax.inject.Inject
7+
8+
class AppPreferences @Inject constructor(private val application: Application) {
9+
private val PREFS_NAME = application.getString(R.string.app_name)
10+
private val prefs = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
11+
12+
var token: String?
13+
get() = prefs.getString(application.getString(R.string.shared_pref_token), null)
14+
set(value) = prefs.edit().putString(application.getString(R.string.shared_pref_token), value).apply()
15+
16+
var repositoriesFilters: String?
17+
get() = prefs.getString(application.getString(R.string.shared_prefs_filters), null)
18+
set(value) = prefs.edit().putString(application.getString(R.string.shared_prefs_filters), value).apply()
19+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.alexandr7035.gitstat.core
2+
3+
interface BaseViewModel {
4+
// Presumably, we should logout when receive 401 code in any fragment
5+
// (E.g. the token has expired
6+
// So make it necessary by default
7+
fun doLogOut()
8+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.alexandr7035.gitstat.data
2+
3+
import androidx.lifecycle.MutableLiveData
4+
import com.alexandr7035.gitstat.core.AppPreferences
5+
import com.alexandr7035.gitstat.data.local.CacheDao
6+
import com.alexandr7035.gitstat.data.remote.NetworkModule
7+
import kotlinx.coroutines.CoroutineScope
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.launch
10+
import kotlinx.coroutines.withContext
11+
import javax.inject.Inject
12+
13+
class LoginRepository @Inject constructor(private val appPreferences: AppPreferences, private val api: NetworkModule, private val dao: CacheDao) {
14+
15+
fun doLoginRequest(loginLiveData: MutableLiveData<Int>, token: String) {
16+
17+
CoroutineScope(Dispatchers.IO).launch {
18+
try {
19+
val res = api.loginRequest(token)
20+
21+
withContext(Dispatchers.Main) {
22+
loginLiveData.value = res.code()
23+
}
24+
}
25+
catch (e: Exception) {
26+
withContext(Dispatchers.Main) {
27+
loginLiveData.value = 0
28+
}
29+
}
30+
}
31+
32+
}
33+
34+
35+
fun saveToken(token: String) {
36+
appPreferences.token = token
37+
}
38+
39+
fun checkIfLoggedIn(): Boolean {
40+
return when (appPreferences.token) {
41+
null -> false
42+
else -> true
43+
}
44+
}
45+
46+
fun clearToken() {
47+
appPreferences.token = null
48+
}
49+
50+
suspend fun clearCache() {
51+
dao.clearUserCache()
52+
dao.clearRepositoriesCache()
53+
}
54+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.alexandr7035.gitstat.data
2+
3+
import androidx.lifecycle.MutableLiveData
4+
import com.alexandr7035.gitstat.core.AppPreferences
5+
import com.alexandr7035.gitstat.core.Language
6+
import com.alexandr7035.gitstat.core.ProgLangManager
7+
import com.alexandr7035.gitstat.core.SyncStatus
8+
import com.alexandr7035.gitstat.data.local.CacheDao
9+
import com.alexandr7035.gitstat.data.local.model.RepositoryEntity
10+
import com.alexandr7035.gitstat.data.remote.NetworkModule
11+
import com.alexandr7035.gitstat.data.remote.mappers.RepositoryRemoteToCacheMapper
12+
import com.alexandr7035.gitstat.data.remote.model.RepositoryModel
13+
import com.alexandr7035.gitstat.view.repositories.filters.ReposFilters
14+
import com.google.gson.Gson
15+
import javax.inject.Inject
16+
17+
class ReposRepository @Inject constructor(
18+
private val api: NetworkModule,
19+
private val dao: CacheDao,
20+
private val repoMapper: RepositoryRemoteToCacheMapper,
21+
private val appPreferences: AppPreferences,
22+
private val gson: Gson,
23+
private val langManager: ProgLangManager
24+
) {
25+
private val syncStateLiveData = MutableLiveData<SyncStatus>()
26+
27+
suspend fun syncRepositoriesData() {
28+
29+
syncStateLiveData.postValue(SyncStatus.PENDING)
30+
31+
try {
32+
val res = api.getRepositoriesData()
33+
34+
if (res.isSuccessful) {
35+
syncStateLiveData.postValue(SyncStatus.SUCCESS)
36+
val reposList: List<RepositoryModel> = res.body()!!
37+
38+
val cachedReposList = reposList.map { repositoryModel ->
39+
repoMapper.transform(repositoryModel)
40+
}
41+
42+
dao.clearRepositoriesCache()
43+
dao.insertRepositoriesCache(cachedReposList)
44+
} else {
45+
syncStateLiveData.postValue(SyncStatus.FAILED)
46+
}
47+
}
48+
49+
catch (e: Exception) {
50+
////Log.d(LOG_TAG, "exception ${e}")
51+
syncStateLiveData.postValue(SyncStatus.FAILED)
52+
}
53+
}
54+
55+
fun getSyncStatusLiveData(): MutableLiveData<SyncStatus> {
56+
return syncStateLiveData
57+
}
58+
59+
suspend fun fetchAllRepositoriesFromDb(): List<RepositoryEntity> {
60+
return dao.getRepositoriesCache()
61+
}
62+
63+
suspend fun fetchActiveRepositoriesFromDb(): List<RepositoryEntity> {
64+
return dao.getActiveRepositoriesCache()
65+
}
66+
67+
suspend fun fetchArchivedRepositoriesFromDb(): List<RepositoryEntity> {
68+
return dao.getArchivedRepositoriesCache()
69+
}
70+
71+
72+
// Filters
73+
fun getRepositoriesFilters(): ReposFilters {
74+
val filtersStr = appPreferences.repositoriesFilters
75+
76+
return if (filtersStr == null) {
77+
// New instance with default params
78+
// See filters class
79+
ReposFilters()
80+
} else {
81+
// Load from persistent storage
82+
gson.fromJson(filtersStr, ReposFilters::class.java)
83+
}
84+
}
85+
86+
87+
fun saveRepositoriesFilters(filters: ReposFilters) {
88+
appPreferences.repositoriesFilters = gson.toJson(filters)
89+
}
90+
91+
92+
// Prog languages
93+
fun getLanguagesForReposList(repos: List<RepositoryEntity>): List<Language> {
94+
return langManager.getLanguagesList(repos)
95+
}
96+
97+
}

app/src/main/java/com/alexandr7035/gitstat/data/Repository.kt

Lines changed: 0 additions & 135 deletions
This file was deleted.

0 commit comments

Comments
 (0)