Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ All notable changes to ComposeDex will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed
- asynchronous DAO queries no longer suspend indefinitely if table is empty.

## [1.0.0] - 2024-12-30

### Added
- Initial release of the ComposeDex Android app.
- Initial release of the ComposeDex Android app.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ android {
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
freeCompilerArgs.add("-Xannotation-default-target=param-property")
}
}
detekt {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -91,7 +91,8 @@ class PokemonLocalDataSourceTest: LocalDataSourceTest() {
database.pokemonDao()
.getWithSpeciesTypesAndVarietiesByName(expectedEntity.pokemon.pokemonName).test {
val actualEntity = awaitItem()
assertThat(actualEntity.sortTypesForComparison())
assertThat(actualEntity).isNotNull()
assertThat(actualEntity!!.sortTypesForComparison())
.isEqualTo(expectedEntity.sortTypesForComparison())
}
}
Expand Down Expand Up @@ -179,20 +180,23 @@ class PokemonLocalDataSourceTest: LocalDataSourceTest() {

localDataSource.insertPokemonWithSpeciesTypesAndVarieties(expectedEntity)
val actualEntity = awaitItem()
assertThat(actualEntity.sortTypesForComparison()).isEqualTo(expectedEntity.sortTypesForComparison())
assertThat(actualEntity).isNotNull()
assertThat(actualEntity!!.sortTypesForComparison()).isEqualTo(expectedEntity.sortTypesForComparison())
assertThat(actualEntity.pokemon.isFavourite).isFalse()

database.pokemonDao().updateFavourite(FavouriteUpdate(expectedEntity.pokemon.pokemonId, true))
val favouriteEntity = awaitItem()
assertThat(favouriteEntity.pokemon.isFavourite).isTrue()
assertThat(favouriteEntity).isNotNull()
assertThat(favouriteEntity!!.pokemon.isFavourite).isTrue()

val expectedEntitySprite = "newSprite"
localDataSource.updatePokemonSprite(
favouriteEntity.pokemon.pokemonId,
expectedEntitySprite
)
val spriteEntity = awaitItem()
assertThat(spriteEntity.pokemon.localSprite).isEqualTo(expectedEntitySprite)
assertThat(spriteEntity).isNotNull()
assertThat(spriteEntity!!.pokemon.localSprite).isEqualTo(expectedEntitySprite)
}
}

Expand All @@ -207,20 +211,23 @@ class PokemonLocalDataSourceTest: LocalDataSourceTest() {

localDataSource.insertPokemonWithSpeciesTypesAndVarieties(expectedEntity)
val actualEntity = awaitItem()
assertThat(actualEntity.sortTypesForComparison()).isEqualTo(expectedEntity.sortTypesForComparison())
assertThat(actualEntity).isNotNull()
assertThat(actualEntity!!.sortTypesForComparison()).isEqualTo(expectedEntity.sortTypesForComparison())
assertThat(actualEntity.pokemon.isFavourite).isFalse()

database.pokemonDao().updateFavourite(FavouriteUpdate(expectedEntity.pokemon.pokemonId, true))
val favouriteEntity = awaitItem()
assertThat(favouriteEntity.pokemon.isFavourite).isTrue()
assertThat(favouriteEntity).isNotNull()
assertThat(favouriteEntity!!.pokemon.isFavourite).isTrue()

val expectedEntitySprite = "newSprite"
localDataSource.updatePokemonSprite(
favouriteEntity.pokemon.pokemonId,
expectedEntitySprite
)
val spriteEntity = awaitItem()
assertThat(spriteEntity.pokemon.localSprite).isEqualTo(expectedEntitySprite)
assertThat(spriteEntity).isNotNull()
assertThat(spriteEntity!!.pokemon.localSprite).isEqualTo(expectedEntitySprite)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -37,7 +37,7 @@ interface GenerationDao : BaseDao<GenerationEntity> {

/** Load the [GenerationOverviewEntity]. */
@Query("SELECT * FROM generation_overview WHERE id = 0")
fun getOverview(): Flow<GenerationOverviewEntity>
fun getOverview(): Flow<GenerationOverviewEntity?>

/** Load all [GenerationEntity]. */
@Transaction
Expand All @@ -51,7 +51,7 @@ interface GenerationDao : BaseDao<GenerationEntity> {
*/
@Transaction
@Query("SELECT * FROM generation WHERE generationName = :name")
fun getByName(name: String): Flow<GenerationEntity>
fun getByName(name: String): Flow<GenerationEntity?>

/**
* Load a [GenerationEntity] with the given id.
Expand All @@ -60,7 +60,7 @@ interface GenerationDao : BaseDao<GenerationEntity> {
*/
@Transaction
@Query("SELECT * FROM generation WHERE generationId = :id")
fun getById(id: Int): Flow<GenerationEntity>
fun getById(id: Int): Flow<GenerationEntity?>

/**
* Load all [PokemonWithSpeciesTypesAndVarieties] belonging to the [GenerationEntity] with the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -123,7 +123,7 @@ interface PokemonDao : BaseDao<PokemonEntity> {
*/
@Transaction
@Query("SELECT * FROM pokemon WHERE pokemonId = :id")
fun getWithSpeciesTypesAndVarietiesById(id: Int): Flow<PokemonWithSpeciesTypesAndVarieties>
fun getWithSpeciesTypesAndVarietiesById(id: Int): Flow<PokemonWithSpeciesTypesAndVarieties?>

/**
* Load a [PokemonEntity] with the given name and all associated [SpeciesEntity], [TypeEntity]
Expand All @@ -135,7 +135,7 @@ interface PokemonDao : BaseDao<PokemonEntity> {
@Query("SELECT * FROM pokemon WHERE pokemonName = :name")
fun getWithSpeciesTypesAndVarietiesByName(
name: String
): Flow<PokemonWithSpeciesTypesAndVarieties>
): Flow<PokemonWithSpeciesTypesAndVarieties?>

/**
* Load a [PokemonEntity] with the given name and all associated [SpeciesEntity], [TypeEntity]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -37,7 +37,7 @@ interface TypeDao : BaseDao<TypeEntity> {

/** Load the [TypeOverviewEntity]. */
@Query("SELECT * FROM type_overview WHERE id = 0")
fun getOverview(): Flow<TypeOverviewEntity>
fun getOverview(): Flow<TypeOverviewEntity?>

/**
* Insert a relation object between [TypeEntity] and [PokemonEntity] into the database.
Expand Down Expand Up @@ -66,7 +66,7 @@ interface TypeDao : BaseDao<TypeEntity> {
* @param name of the type to load
*/
@Query("SELECT * FROM type WHERE typeName = :name")
fun getByName(name: String): Flow<TypeEntity>
fun getByName(name: String): Flow<TypeEntity?>

/**
* Load all [PokemonWithSpeciesTypesAndVarieties] belonging to the [TypeEntity] with the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -56,13 +56,13 @@ class GenerationLocalDataSource(
}
}

fun getGenerationOverview(): Flow<GenerationOverviewEntity> = generationDao.getOverview()
fun getGenerationOverview(): Flow<GenerationOverviewEntity?> = generationDao.getOverview()

fun getAllGenerations(): Flow<List<GenerationEntity>> = generationDao.getAll()

fun getGenerationByName(name: String): Flow<GenerationEntity> = generationDao.getByName(name)
fun getGenerationByName(name: String): Flow<GenerationEntity?> = generationDao.getByName(name)

fun getGenerationById(id: Int): Flow<GenerationEntity> = generationDao.getById(id)
fun getGenerationById(id: Int): Flow<GenerationEntity?> = generationDao.getById(id)

fun getPokemonOfGenerationByName(name: String): Flow<List<PokemonWithSpeciesTypesAndVarieties>> =
generationDao.getPokemonWithinGenerationByName(name)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -98,11 +98,11 @@ class PokemonLocalDataSource(

fun getPokemonWithSpeciesTypesAndVarietiesByName(
name: String
): Flow<PokemonWithSpeciesTypesAndVarieties> =
): Flow<PokemonWithSpeciesTypesAndVarieties?> =
pokemonDao.getWithSpeciesTypesAndVarietiesByName(name)

fun getPokemonWithSpeciesTypesAndVarietiesById(
id: Int
): Flow<PokemonWithSpeciesTypesAndVarieties> =
): Flow<PokemonWithSpeciesTypesAndVarieties?> =
pokemonDao.getWithSpeciesTypesAndVarietiesById(id)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -54,11 +54,11 @@ class TypeLocalDataSource(
}
}

fun getTypeOverview(): Flow<TypeOverviewEntity> = typeDao.getOverview()
fun getTypeOverview(): Flow<TypeOverviewEntity?> = typeDao.getOverview()

fun getAllTypes(): Flow<List<TypeEntity>> = typeDao.getAll()

fun getTypeByName(name: String): Flow<TypeEntity> = typeDao.getByName(name)
fun getTypeByName(name: String): Flow<TypeEntity?> = typeDao.getByName(name)

fun getPokemonOfType(name: String): Flow<List<PokemonWithSpeciesTypesAndVarieties>> =
typeDao.getPokemonWithType(name)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -52,7 +52,7 @@ class OfflineFirstGenerationRepository(
override fun getGenerations(): Flow<List<Generation>> =
localDataSource.getGenerationOverview()
.combine(localDataSource.getAllGenerations()) { overview, generations ->
if (generations.isNotEmpty() && generations.size == overview.names.size) {
if (generations.isNotEmpty() && generations.size == overview!!.names.size) {
generations.map { it.asExternalModel() }
} else {
throw LocalDataException("Not all generations in database")
Expand Down Expand Up @@ -96,7 +96,7 @@ class OfflineFirstGenerationRepository(
override fun getGenerationByName(name: String): Flow<Generation> {
return localDataSource
.getGenerationByName(name)
.map { generationEntity -> generationEntity.asExternalModel() }
.map { generationEntity -> generationEntity!!.asExternalModel() }
.retryWhen { cause, attempt ->
if (cause is NullPointerException && attempt < RETRY_COUNT) {
Timber.d(
Expand All @@ -119,7 +119,7 @@ class OfflineFirstGenerationRepository(
override fun getGenerationById(id: Int): Flow<Generation> {
return localDataSource
.getGenerationById(id)
.map { generationEntity -> generationEntity.asExternalModel() }
.map { generationEntity -> generationEntity!!.asExternalModel() }
.retryWhen { cause, attempt ->
if (cause is NullPointerException && attempt < RETRY_COUNT) {
Timber.d(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -42,7 +42,7 @@ class OfflineFirstPokemonRepository(
) : PokemonRepository {
override fun getPokemonByName(name: String): Flow<Pokemon> {
return localDataSource.getPokemonWithSpeciesTypesAndVarietiesByName(name).map {
it.asExternalModel()
it!!.asExternalModel()
}.retryWhen { cause, attempt ->
if (cause is NullPointerException && attempt < RETRY_COUNT) {
Timber.d("Attempt $attempt of $RETRY_COUNT to fetch pokemon $name, failed previously because: $cause")
Expand Down Expand Up @@ -73,7 +73,7 @@ class OfflineFirstPokemonRepository(

override fun getPokemonById(id: Int): Flow<Pokemon> {
return localDataSource.getPokemonWithSpeciesTypesAndVarietiesById(id).map {
it.asExternalModel()
it!!.asExternalModel()
}.retryWhen { cause, attempt ->
if (cause is NullPointerException && attempt < RETRY_COUNT) {
Timber.d(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -51,7 +51,7 @@ class OfflineFirstTypeRepository(
override fun getTypes(): Flow<List<Type>> =
localDataSource.getTypeOverview()
.combine(localDataSource.getAllTypes()) { overview, types ->
if (types.isNotEmpty() && types.size == overview.names.size) {
if (types.isNotEmpty() && types.size == overview?.names?.size) {
types.map { it.asExternalModel() }
} else {
throw LocalDataException("Not all types in database")
Expand Down Expand Up @@ -92,7 +92,7 @@ class OfflineFirstTypeRepository(
}

override fun getTypeByName(name: String): Flow<Type> =
localDataSource.getTypeByName(name).map { it.asExternalModel() }
localDataSource.getTypeByName(name).map { it!!.asExternalModel() }
.retryWhen { cause, attempt ->
if (cause is NullPointerException && attempt < RETRY_COUNT) {
Timber.d("Attempt $attempt of $RETRY_COUNT to fetch type $name, failed previously because: $cause")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -66,10 +66,10 @@ class SaveRemoteImageUseCase @Inject constructor(
try {
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
Timber.e("Unsuccessful response: ${response.code()} for $imageAddress")
Timber.e("Unsuccessful response: ${response.code} for $imageAddress")
return@withContext null
}
response.body()?.byteStream()?.use { inputStream ->
response.body?.byteStream()?.use { inputStream ->
BitmapFactory.decodeStream(inputStream)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -58,10 +58,10 @@ class SaveRemoteSoundUseCase @Inject constructor(
try {
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
Timber.e("Unsuccessful response: ${response.code()} for $soundAddress")
Timber.e("Unsuccessful response: ${response.code} for $soundAddress")
return@withContext null
}
response.body()?.byteStream()?.use { inputStream ->
response.body?.byteStream()?.use { inputStream ->
context.openFileOutput(dataName, Context.MODE_PRIVATE).use { outputStream ->
inputStream.copyTo(outputStream)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Entikore
* Copyright 2025 Entikore
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -88,7 +88,7 @@ class ArchitectureCheck {
val hasSingleConstructor = declaration.constructors.size == 1
val constructorParametersHavePrivateModifier = declaration.constructors.first().parameters.all {
it.modifiers.isEmpty() ||
(it.hasValModifier && it.hasModifier(KoModifier.PRIVATE))
(it.isVal && it.hasModifier(KoModifier.PRIVATE))
}
hasSingleConstructor && constructorParametersHavePrivateModifier
}
Expand Down
1 change: 1 addition & 0 deletions config/detekt.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
comments:
AbsentOrWrongFileLicense:
active: true
licenseTemplateIsRegex: true
licenseTemplateFile: 'license-header.txt'
EndOfSentenceFormat:
active: true
Expand Down
Loading