Skip to content

Commit

Permalink
Merge pull request #82 from DenisGithuku/72-fix-app-state-refresh-status
Browse files Browse the repository at this point in the history
Move keystore secrets to environment variables
  • Loading branch information
DenisGithuku authored Oct 9, 2024
2 parents 105cb3b + 6cc6f9e commit 4222dc5
Show file tree
Hide file tree
Showing 17 changed files with 266 additions and 225 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,26 @@ jobs:
cache: 'gradle'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Decode Keystore
env:
ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
RELEASE_KEYSTORE_ALIAS: ${{ secrets.RELEASE_KEYSTORE_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
run: |
echo $ENCODED_STRING > keystore-b64.txt
base64 -d keystore-b64.txt > keystore.jks
- name: Run build with Gradle Wrapper
env:
RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
RELEASE_KEYSTORE_ALIAS: ${{ secrets.RELEASE_KEYSTORE_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
run: ./gradlew build

- name: Run tests with Gradle Wrapper
env:
RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
RELEASE_KEYSTORE_ALIAS: ${{ secrets.RELEASE_KEYSTORE_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
run: ./gradlew check
10 changes: 5 additions & 5 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ android {
}

signingConfigs {
getByName("debug") {
storeFile = file("../keystore/comlibdebug.keystore")
keyAlias = "comlib"
keyPassword = "comlibdroid"
storePassword = "04uth50ft!5f4"
create("release") {
storeFile = file("../keystore.jks")
storePassword = System.getenv("RELEASE_KEYSTORE_PASSWORD")
keyAlias = System.getenv("RELEASE_KEYSTORE_ALIAS")
keyPassword = System.getenv("RELEASE_KEY_PASSWORD")
}
}
buildTypes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,49 +20,51 @@ import com.githukudenis.comlib.core.data.repository.UserPrefsRepository
import com.githukudenis.comlib.core.model.ThemeConfig
import com.githukudenis.comlib.core.model.UserPrefs
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow

class FakeUserPrefsRepository : UserPrefsRepository {

private var prefs =
UserPrefs(
readBooks = setOf("1", "2", "3"),
themeConfig = ThemeConfig.SYSTEM,
bookmarkedBooks = setOf("1", "2", "3"),
isSetup = false,
preferredGenres = setOf("genre1", "genre2"),
token = "token",
userId = "owner@5"
private var _prefs =
MutableStateFlow(
UserPrefs(
readBooks = setOf("1", "2", "3"),
themeConfig = ThemeConfig.SYSTEM,
bookmarkedBooks = setOf("1", "2", "3"),
isSetup = false,
preferredGenres = setOf("genre1", "genre2"),
token = "token",
userId = "owner@5"
)
)

override val userPrefs: Flow<UserPrefs>
get() = flowOf(prefs)
get() = _prefs

override suspend fun setThemeConfig(themeConfig: ThemeConfig) {
prefs = prefs.copy(themeConfig = themeConfig)
_prefs.value = _prefs.value.copy(themeConfig = themeConfig)
}

override suspend fun setBookMarks(bookMarks: Set<String>) {
prefs = prefs.copy(bookmarkedBooks = bookMarks)
_prefs.value = _prefs.value.copy(bookmarkedBooks = bookMarks)
}

override suspend fun setSetupStatus(isComplete: Boolean) {
prefs = prefs.copy(isSetup = isComplete)
_prefs.value = _prefs.value.copy(isSetup = isComplete)
}

override suspend fun setPreferredGenres(genres: Set<String>) {
prefs = prefs.copy(preferredGenres = genres)
_prefs.value = _prefs.value.copy(preferredGenres = genres)
}

override suspend fun clearSession() {
prefs = prefs.copy(token = null, themeConfig = ThemeConfig.SYSTEM)
_prefs.value = _prefs.value.copy(token = null, themeConfig = ThemeConfig.SYSTEM)
}

override suspend fun setToken(token: String) {
prefs = prefs.copy(token = token)
_prefs.value = _prefs.value.copy(token = token)
}

override suspend fun setUserId(userId: String) {
prefs = prefs.copy(userId = userId)
_prefs.value = _prefs.value.copy(userId = userId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,20 @@ import com.githukudenis.comlib.core.designsystem.ui.components.loading_indicator
@Composable
fun BookDetailRoute(viewModel: BookDetailViewModel = hiltViewModel(), onBackPressed: () -> Unit) {
val state by viewModel.state.collectAsStateWithLifecycle()
val isFavourite by viewModel.isFavourite.collectAsStateWithLifecycle()

BookDetailScreen(
state = state,
isFavourite = isFavourite,
onToggleFavourite = { bookId -> viewModel.toggleBookmark(bookId) },
onRetry = { viewModel.onRetry() },
onRetry = {
// viewModel.onRetry()
},
onBackPressed = onBackPressed
)
}

@Composable
fun BookDetailScreen(
state: BookDetailUiState,
isFavourite: Boolean,
onToggleFavourite: (String) -> Unit,
onRetry: () -> Unit,
onBackPressed: () -> Unit
Expand All @@ -90,7 +89,7 @@ fun BookDetailScreen(
}
is BookDetailUiState.Success -> {
LoadedScreen(
bookUiModel = state.bookUiModel.copy(isFavourite = isFavourite),
bookUiModel = state.bookUiModel,
onBackPressed = onBackPressed,
onToggleFavourite = onToggleFavourite
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ data class BookUiModel(
val isFavourite: Boolean = false
)

suspend fun Book.toBookUiModel(isRead: Boolean, getGenre: suspend (String) -> Genre): BookUiModel {
suspend fun Book.toBookUiModel(
isRead: Boolean,
isFavourite: Boolean,
getGenre: suspend (String) -> Genre
): BookUiModel {
return BookUiModel(
id = id,
title = title,
Expand All @@ -49,6 +53,7 @@ suspend fun Book.toBookUiModel(isRead: Boolean, getGenre: suspend (String) -> Ge
description = description,
imageUrl = image,
isRead = isRead,
isFavourite = isFavourite,
reservedBy = reserved,
pages = pages
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,12 @@ import com.githukudenis.comlib.core.data.repository.UserPrefsRepository
import com.githukudenis.comlib.core.model.genre.Genre
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

@HiltViewModel
Expand All @@ -47,39 +44,33 @@ constructor(
savedStateHandle: SavedStateHandle
) : ViewModel() {

private val _state: MutableStateFlow<BookDetailUiState> =
MutableStateFlow(BookDetailUiState.Loading)
val state: StateFlow<BookDetailUiState> = _state.asStateFlow()

private val bookId: String? = savedStateHandle.get<String>("bookId")

val isFavourite: StateFlow<Boolean> =
userPreferencesRepository.userPrefs
.distinctUntilChanged()
.mapLatest { userPrefs -> userPrefs.bookmarkedBooks.contains(bookId) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = false
)

init {
savedStateHandle.get<String>("bookId")?.let { bookId -> getBook(bookId) }
private suspend fun fetchGenreById(genreId: String): Genre? {
return when (val result = genresRepository.getGenreById(genreId)) {
is ResponseResult.Failure -> {
null
}
is ResponseResult.Success -> {
result.data.data.genre
}
}
}

private fun getBook(bookId: String) {
viewModelScope.launch {
when (val result = booksRepository.getBookById(bookId)) {
is ResponseResult.Failure -> {
_state.update { BookDetailUiState.Error(result.error.message) }
val state: StateFlow<BookDetailUiState> =
combine(
savedStateHandle.getStateFlow(key = "bookId", initialValue = ""),
userPreferencesRepository.userPrefs.mapLatest { prefs ->
prefs.readBooks to prefs.bookmarkedBooks
}
is ResponseResult.Success -> {
val isRead =
userPreferencesRepository.userPrefs.first().readBooks.contains(result.data.data.book.id)
_state.update {
) { bookId, (read, bookmarked) ->
when (val result = booksRepository.getBookById(bookId)) {
is ResponseResult.Failure -> {
BookDetailUiState.Error(result.error.message)
}
is ResponseResult.Success -> {
val bookUiModel =
result.data.data.book.toBookUiModel(
isRead = isRead,
isRead = read.contains(bookId),
isFavourite = bookmarked.contains(bookId),
getGenre = { genreId ->
fetchGenreById(genreId) ?: Genre(_id = genreId, id = genreId, name = "Unknown")
}
Expand All @@ -88,25 +79,11 @@ constructor(
}
}
}
}
}

fun onRetry() {
if (bookId != null) {
getBook(bookId)
}
}

private suspend fun fetchGenreById(genreId: String): Genre? {
return when (val result = genresRepository.getGenreById(genreId)) {
is ResponseResult.Failure -> {
null
}
is ResponseResult.Success -> {
result.data.data.genre
}
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = BookDetailUiState.Loading
)

fun toggleBookmark(bookId: String) {
viewModelScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import com.githukudenis.comlib.core.data.repository.fake.FakeGenresRepository
import com.githukudenis.comlib.core.data.repository.fake.FakeUserPrefsRepository
import com.githukudenis.comlib.core.testing.util.MainCoroutineRule
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
Expand Down Expand Up @@ -54,13 +58,16 @@ class BookDetailViewModelTest {

@Test
fun testOnInitializeUpdatesState() = runTest {
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { viewModel.state.collect() }
val state = viewModel.state.value as BookDetailUiState.Success
assert(state.bookUiModel.authors.contains("Peter"))
}

@Test
fun toggleBookmark() = runTest {
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { viewModel.state.collect() }
viewModel.toggleBookmark("1")
advanceUntilIdle()
val state = viewModel.state.value as BookDetailUiState.Success

assertTrue(!state.bookUiModel.isFavourite)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,12 @@ fun HomeRoute(
HomeRouteContent(
state = state,
onOpenProfile = onOpenProfile,
onClickRetryGetReads = viewModel::onClickRetryGetReads,
onClickRetryGetAvailableBooks = viewModel::onRefreshAvailableBooks,
onClickRetryGetReads = {
// viewModel::onClickRetryGetReads
},
onClickRetryGetAvailableBooks = {
// viewModel::onRefreshAvailableBooks
},
onOpenAllBooks = onOpenAllBooks,
onOpenBookDetails = onOpenBookDetails,
onToggleFavourite = viewModel::onToggleFavourite,
Expand Down Expand Up @@ -207,13 +211,16 @@ fun HomeRouteContent(
}
}
is FetchItemState.Success -> {
val list = state.availableState.data
if (list.isNotEmpty()) {
val books =
state.availableState.data.map { model ->
model.copy(isFavourite = model.isFavourite == model.book.id in state.bookmarks)
}
if (books.isNotEmpty()) {
LazyRow(
contentPadding = PaddingValues(horizontal = LocalDimens.current.extraLarge),
horizontalArrangement = Arrangement.spacedBy(LocalDimens.current.large)
) {
items(list, key = { bookUiModel -> bookUiModel.book._id }) { bookUiModel ->
items(books, key = { bookUiModel -> bookUiModel.book._id }) { bookUiModel ->
BookCard(
bookUiModel = bookUiModel,
onClick = onOpenBookDetails,
Expand Down
Loading

0 comments on commit 4222dc5

Please sign in to comment.