├── feature
├── base
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── ids.xml
│ │ │ │ ├── strings.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── color_palete.xml
│ │ │ └── drawable
│ │ │ │ ├── ic_search.xml
│ │ │ │ ├── image_placeholder_3.xml
│ │ │ │ ├── image_placeholder_2.xml
│ │ │ │ └── image_placeholder_1.xml
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── igorwojda
│ │ │ └── showcase
│ │ │ └── feature
│ │ │ └── base
│ │ │ ├── presentation
│ │ │ ├── viewmodel
│ │ │ │ ├── BaseState.kt
│ │ │ │ ├── BaseAction.kt
│ │ │ │ ├── BaseViewModel.kt
│ │ │ │ └── StateTimeTravelDebugger.kt
│ │ │ └── compose
│ │ │ │ └── composable
│ │ │ │ ├── UnderConstructionAnim.kt
│ │ │ │ ├── TextTitleLarge.kt
│ │ │ │ ├── TextTitleMedium.kt
│ │ │ │ ├── ErrorAnim.kt
│ │ │ │ ├── Loading.kt
│ │ │ │ ├── PlaceholderImage.kt
│ │ │ │ └── Lottie.kt
│ │ │ ├── domain
│ │ │ └── result
│ │ │ │ ├── ResultExt.kt
│ │ │ │ └── Result.kt
│ │ │ ├── common
│ │ │ ├── res
│ │ │ │ └── Dimen.kt
│ │ │ └── delegate
│ │ │ │ └── Observer.kt
│ │ │ ├── data
│ │ │ └── retrofit
│ │ │ │ ├── ApiResultCallAdapter.kt
│ │ │ │ ├── ApiResult.kt
│ │ │ │ ├── ApiResultAdapterFactory.kt
│ │ │ │ └── ApiResultCall.kt
│ │ │ └── util
│ │ │ └── TimberLogTags.kt
│ ├── build.gradle.kts
│ └── proguard-rules.pro
├── album
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── igorwojda
│ │ │ │ │ └── showcase
│ │ │ │ │ └── feature
│ │ │ │ │ └── album
│ │ │ │ │ ├── domain
│ │ │ │ │ ├── model
│ │ │ │ │ │ ├── Tag.kt
│ │ │ │ │ │ ├── Track.kt
│ │ │ │ │ │ ├── Image.kt
│ │ │ │ │ │ └── Album.kt
│ │ │ │ │ ├── enum
│ │ │ │ │ │ └── ImageSize.kt
│ │ │ │ │ ├── DomainModule.kt
│ │ │ │ │ ├── repository
│ │ │ │ │ │ └── AlbumRepository.kt
│ │ │ │ │ └── usecase
│ │ │ │ │ │ ├── GetAlbumUseCase.kt
│ │ │ │ │ │ └── GetAlbumListUseCase.kt
│ │ │ │ │ ├── data
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ ├── api
│ │ │ │ │ │ │ ├── model
│ │ │ │ │ │ │ │ ├── TagListApiModel.kt
│ │ │ │ │ │ │ │ ├── AlbumListApiModel.kt
│ │ │ │ │ │ │ │ ├── TrackListApiModel.kt
│ │ │ │ │ │ │ │ ├── SearchAlbumResultsApiModel.kt
│ │ │ │ │ │ │ │ ├── TagApiModel.kt
│ │ │ │ │ │ │ │ ├── ImageApiModel.kt
│ │ │ │ │ │ │ │ ├── TrackApiModel.kt
│ │ │ │ │ │ │ │ ├── ImageSizeApiModel.kt
│ │ │ │ │ │ │ │ └── AlbumApiModel.kt
│ │ │ │ │ │ │ ├── response
│ │ │ │ │ │ │ │ ├── GetAlbumInfoResponse.kt
│ │ │ │ │ │ │ │ └── SearchAlbumResponse.kt
│ │ │ │ │ │ │ └── service
│ │ │ │ │ │ │ │ └── AlbumRetrofitService.kt
│ │ │ │ │ │ └── database
│ │ │ │ │ │ │ ├── model
│ │ │ │ │ │ │ ├── TagRoomModel.kt
│ │ │ │ │ │ │ ├── ImageRoomModel.kt
│ │ │ │ │ │ │ ├── TrackRoomModel.kt
│ │ │ │ │ │ │ ├── ImageSizeRoomModel.kt
│ │ │ │ │ │ │ └── AlbumRoomModel.kt
│ │ │ │ │ │ │ ├── AlbumDatabase.kt
│ │ │ │ │ │ │ └── AlbumDao.kt
│ │ │ │ │ ├── mapper
│ │ │ │ │ │ ├── TagMapper.kt
│ │ │ │ │ │ ├── TrackMapper.kt
│ │ │ │ │ │ ├── ImageMapper.kt
│ │ │ │ │ │ ├── ImageSizeMapper.kt
│ │ │ │ │ │ └── AlbumMapper.kt
│ │ │ │ │ ├── DataModule.kt
│ │ │ │ │ └── repository
│ │ │ │ │ │ └── AlbumRepositoryImpl.kt
│ │ │ │ │ ├── AlbumKoinModule.kt
│ │ │ │ │ └── presentation
│ │ │ │ │ ├── util
│ │ │ │ │ └── TimeUtil.kt
│ │ │ │ │ ├── screen
│ │ │ │ │ ├── albumlist
│ │ │ │ │ │ ├── AlbumListUiState.kt
│ │ │ │ │ │ ├── AlbumListAction.kt
│ │ │ │ │ │ ├── AlbumListViewModel.kt
│ │ │ │ │ │ └── AlbumListScreen.kt
│ │ │ │ │ └── albumdetail
│ │ │ │ │ │ ├── AlbumDetailUiState.kt
│ │ │ │ │ │ ├── AlbumDetailAction.kt
│ │ │ │ │ │ └── AlbumDetailViewModel.kt
│ │ │ │ │ ├── PresentationModule.kt
│ │ │ │ │ └── composable
│ │ │ │ │ └── SearchBarComposable.kt
│ │ │ └── res
│ │ │ │ └── values
│ │ │ │ └── strings.xml
│ │ └── test
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── igorwojda
│ │ │ └── showcase
│ │ │ └── feature
│ │ │ └── album
│ │ │ ├── data
│ │ │ ├── datasource
│ │ │ │ └── api
│ │ │ │ │ └── model
│ │ │ │ │ ├── ImageSizeApiModelTest.kt
│ │ │ │ │ ├── ImageApiModelTest.kt
│ │ │ │ │ └── AlbumApiModelTest.kt
│ │ │ ├── mapper
│ │ │ │ ├── TagMapperTest.kt
│ │ │ │ ├── TrackMapperTest.kt
│ │ │ │ ├── ImageSizeMapperTest.kt
│ │ │ │ └── ImageMapperTest.kt
│ │ │ └── DataFixtures.kt
│ │ │ ├── domain
│ │ │ ├── model
│ │ │ │ └── AlbumTest.kt
│ │ │ ├── DomainFixtures.kt
│ │ │ └── usecase
│ │ │ │ ├── GetAlbumUseCaseTest.kt
│ │ │ │ └── GetAlbumListUseCaseTest.kt
│ │ │ └── presentation
│ │ │ └── screen
│ │ │ ├── albumlist
│ │ │ └── AlbumListViewModelTest.kt
│ │ │ └── albumdetail
│ │ │ └── AlbumDetailViewModelTest.kt
│ ├── build.gradle.kts
│ └── proguard-rules.pro
├── favourite
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── igorwojda
│ │ │ └── showcase
│ │ │ └── feature
│ │ │ └── favourite
│ │ │ ├── data
│ │ │ └── DataModule.kt
│ │ │ ├── domain
│ │ │ └── DomainModule.kt
│ │ │ ├── presentation
│ │ │ ├── PresentationModule.kt
│ │ │ └── screen
│ │ │ │ └── favourite
│ │ │ │ └── FavouriteScreen.kt
│ │ │ └── FavouriteKoinModule.kt
│ ├── build.gradle.kts
│ └── proguard-rules.pro
└── settings
│ ├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── igorwojda
│ │ │ │ └── showcase
│ │ │ │ └── feature
│ │ │ │ └── settings
│ │ │ │ ├── data
│ │ │ │ └── DataModule.kt
│ │ │ │ ├── domain
│ │ │ │ └── DomainModule.kt
│ │ │ │ ├── presentation
│ │ │ │ ├── screen
│ │ │ │ │ ├── settings
│ │ │ │ │ │ ├── SettingsAction.kt
│ │ │ │ │ │ ├── SettingsViewModel.kt
│ │ │ │ │ │ ├── SettingsUiState.kt
│ │ │ │ │ │ └── SettingsScreen.kt
│ │ │ │ │ └── aboutlibraries
│ │ │ │ │ │ ├── AboutLibrariesAction.kt
│ │ │ │ │ │ ├── AboutLibrariesViewModel.kt
│ │ │ │ │ │ ├── AboutLibrariesUiState.kt
│ │ │ │ │ │ └── AboutLibrariesScreen.kt
│ │ │ │ └── PresentationModule.kt
│ │ │ │ └── SettingsKoinModule.kt
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ └── test
│ │ └── kotlin
│ │ └── com
│ │ └── igorwojda
│ │ └── showcase
│ │ └── feature
│ │ └── settings
│ │ └── presentation
│ │ └── screen
│ │ ├── settings
│ │ └── SettingsViewModelTest.kt
│ │ └── aboutlibraries
│ │ └── AboutLibrariesViewModelTest.kt
│ ├── build.gradle.kts
│ └── proguard-rules.pro
├── library
└── test-utils
│ ├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ └── com
│ │ └── igorwojda
│ │ └── showcase
│ │ └── library
│ │ └── testutils
│ │ ├── CoroutinesTestDispatcherExtension.kt
│ │ └── InstantTaskExecutorExtension.kt
│ ├── build.gradle.kts
│ └── proguard-rules.pro
├── misc
└── image
│ ├── avatar.png
│ ├── app_data_flow.png
│ ├── logs_action.png
│ ├── logs_network.png
│ ├── module_layers.png
│ ├── logs_navigation.png
│ ├── screen_settings.png
│ ├── feature_structure.png
│ ├── screen_album_list.png
│ ├── screen_favorites.png
│ ├── module_dependencies.png
│ ├── screen_album_detail.png
│ ├── application_icon_label.png
│ ├── application_themed_icon.png
│ ├── module_layers_details.png
│ └── screen_open_source_libraries.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── app
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── values
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-anydpi
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-anydpi-v33
│ │ │ │ ├── ic_launcher_round.xml
│ │ │ │ └── ic_launcher.xml
│ │ │ ├── drawable
│ │ │ │ ├── ic_launcher_foreground_themed.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ ├── ic_music_library.xml
│ │ │ │ ├── ic_favorite.xml
│ │ │ │ └── ic_settings.xml
│ │ │ └── xml
│ │ │ │ └── data_extraction_rules.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── igorwojda
│ │ │ │ └── showcase
│ │ │ │ └── app
│ │ │ │ ├── presentation
│ │ │ │ ├── NavigationRoute.kt
│ │ │ │ ├── MainShowcaseActivity.kt
│ │ │ │ ├── util
│ │ │ │ │ └── NavigationDestinationLogger.kt
│ │ │ │ ├── BottomNavigationBar.kt
│ │ │ │ └── MainShowcaseScreen.kt
│ │ │ │ ├── data
│ │ │ │ └── api
│ │ │ │ │ ├── AuthenticationInterceptor.kt
│ │ │ │ │ └── UserAgentInterceptor.kt
│ │ │ │ ├── ShowcaseApplication.kt
│ │ │ │ └── AppKoinModule.kt
│ │ └── AndroidManifest.xml
│ └── debug
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ └── xml
│ │ └── network_security_config.xml
├── proguard-rules.pro
└── build.gradle.kts
├── .editorconfig
├── konsist-test
├── build.gradle.kts
└── src
│ └── test
│ └── kotlin
│ └── com
│ └── igorwojda
│ └── showcase
│ └── konsisttest
│ ├── TestKonsistTest.kt
│ ├── AndroidKonsistTest.kt
│ ├── ModuleKonsistTest.kt
│ ├── CleanArchitectureKonsistTest.kt
│ ├── GeneralKonsistTest.kt
│ ├── ViewModelKonsistTest.kt
│ └── UseCaseKonsistTest.kt
├── .github
├── workflows
│ ├── auto-approve.yml
│ ├── claude.yml
│ └── claude-code-review.yml
└── stale.yml
├── .idea
└── icon.svg
├── .gitignore
├── settings.gradle.kts
├── renovate.json
├── CONTRIBUTING.md
├── DeveloperReadme.md
├── gradle.properties
├── gradlew.bat
└── CODE_OF_CONDUCT.md
/feature/base/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library/test-utils/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/feature/album/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/feature/base/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/feature/favourite/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/feature/settings/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/misc/image/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/avatar.png
--------------------------------------------------------------------------------
/misc/image/app_data_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/app_data_flow.png
--------------------------------------------------------------------------------
/misc/image/logs_action.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/logs_action.png
--------------------------------------------------------------------------------
/misc/image/logs_network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/logs_network.png
--------------------------------------------------------------------------------
/misc/image/module_layers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/module_layers.png
--------------------------------------------------------------------------------
/misc/image/logs_navigation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/logs_navigation.png
--------------------------------------------------------------------------------
/misc/image/screen_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/screen_settings.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/misc/image/feature_structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/feature_structure.png
--------------------------------------------------------------------------------
/misc/image/screen_album_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/screen_album_list.png
--------------------------------------------------------------------------------
/misc/image/screen_favorites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/screen_favorites.png
--------------------------------------------------------------------------------
/misc/image/module_dependencies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/module_dependencies.png
--------------------------------------------------------------------------------
/misc/image/screen_album_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/screen_album_detail.png
--------------------------------------------------------------------------------
/misc/image/application_icon_label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/application_icon_label.png
--------------------------------------------------------------------------------
/misc/image/application_themed_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/application_themed_icon.png
--------------------------------------------------------------------------------
/misc/image/module_layers_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/module_layers_details.png
--------------------------------------------------------------------------------
/misc/image/screen_open_source_libraries.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/misc/image/screen_open_source_libraries.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorwojda/android-showcase/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 |
--------------------------------------------------------------------------------
/feature/base/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/feature/base/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.igorwojda.showcase.convention.feature")
3 | }
4 |
5 | android {
6 | namespace = "com.igorwojda.showcase.feature.base"
7 | }
8 |
--------------------------------------------------------------------------------
/feature/album/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.igorwojda.showcase.convention.feature")
3 | }
4 |
5 | android {
6 | namespace = "com.igorwojda.showcase.feature.album"
7 | }
8 |
--------------------------------------------------------------------------------
/feature/favourite/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.igorwojda.showcase.convention.feature")
3 | }
4 |
5 | android {
6 | namespace = "com.igorwojda.showcase.feature.favourite"
7 | }
8 |
--------------------------------------------------------------------------------
/feature/base/src/main/kotlin/com/igorwojda/showcase/feature/base/presentation/viewmodel/BaseState.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.base.presentation.viewmodel
2 |
3 | interface BaseState
4 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/domain/model/Tag.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.domain.model
2 |
3 | internal data class Tag(
4 | val name: String,
5 | )
6 |
--------------------------------------------------------------------------------
/feature/base/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Under construction
3 | Data not found
4 |
5 |
--------------------------------------------------------------------------------
/feature/favourite/src/main/kotlin/com/igorwojda/showcase/feature/favourite/data/DataModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.favourite.data
2 |
3 | import org.koin.dsl.module
4 |
5 | internal val dataModule = module { }
6 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/data/DataModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings.data
2 |
3 | import org.koin.dsl.module
4 |
5 | internal val dataModule = module { }
6 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/domain/DomainModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings.domain
2 |
3 | import org.koin.dsl.module
4 |
5 | internal val domainModule = module { }
6 |
--------------------------------------------------------------------------------
/feature/favourite/src/main/kotlin/com/igorwojda/showcase/feature/favourite/domain/DomainModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.favourite.domain
2 |
3 | import org.koin.dsl.module
4 |
5 | internal val domainModule = module { }
6 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/domain/model/Track.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.domain.model
2 |
3 | internal data class Track(
4 | val name: String,
5 | val duration: Int? = null,
6 | )
7 |
--------------------------------------------------------------------------------
/feature/base/src/main/kotlin/com/igorwojda/showcase/feature/base/presentation/viewmodel/BaseAction.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.base.presentation.viewmodel
2 |
3 | interface BaseAction {
4 | fun reduce(state: State): State
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #FFFFFF
5 | #3DDC84
6 |
--------------------------------------------------------------------------------
/feature/favourite/src/main/kotlin/com/igorwojda/showcase/feature/favourite/presentation/PresentationModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.favourite.presentation
2 |
3 | import org.koin.dsl.module
4 |
5 | internal val presentationModule = module { }
6 |
--------------------------------------------------------------------------------
/feature/base/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/domain/enum/ImageSize.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.domain.enum
2 |
3 | internal enum class ImageSize {
4 | SMALL,
5 | MEDIUM,
6 | LARGE,
7 | EXTRA_LARGE,
8 | MEGA,
9 | }
10 |
--------------------------------------------------------------------------------
/feature/settings/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.igorwojda.showcase.convention.feature")
3 | }
4 |
5 | android {
6 | namespace = "com.igorwojda.showcase.feature.settings"
7 | }
8 |
9 | dependencies {
10 | implementation(libs.aboutlibraries.compose)
11 | }
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{kt,kts}]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | max_line_length = 140
7 | ktlint_function_naming_ignore_when_annotated_with=Composable
8 |
9 | # Detekt orders imports correctly, while ktlint does not
10 | ktlint_standard_import-ordering = disabled
11 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/domain/model/Image.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.domain.model
2 |
3 | import com.igorwojda.showcase.feature.album.domain.enum.ImageSize
4 |
5 | internal data class Image(
6 | val url: String,
7 | val size: ImageSize,
8 | )
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Showcase
3 |
4 | Albums
5 | Favorites
6 | Settings
7 |
8 |
--------------------------------------------------------------------------------
/feature/base/src/main/res/values/color_palete.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2B2E30
4 | #1A1E21
5 | #3D3D3D
6 | #FB3430
7 | #FFFFFF
8 |
9 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/presentation/screen/settings/SettingsAction.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings.presentation.screen.settings
2 |
3 | import com.igorwojda.showcase.feature.base.presentation.viewmodel.BaseAction
4 |
5 | internal sealed class SettingsAction : BaseAction
6 |
--------------------------------------------------------------------------------
/feature/base/src/main/kotlin/com/igorwojda/showcase/feature/base/domain/result/ResultExt.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.base.domain.result
2 |
3 | inline fun Result.mapSuccess(crossinline onResult: Result.Success.() -> Result): Result {
4 | if (this is Result.Success) {
5 | return onResult(this)
6 | }
7 | return this
8 | }
9 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/presentation/screen/aboutlibraries/AboutLibrariesAction.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings.presentation.screen.aboutlibraries
2 |
3 | import com.igorwojda.showcase.feature.base.presentation.viewmodel.BaseAction
4 |
5 | internal sealed class AboutLibrariesAction : BaseAction
6 |
--------------------------------------------------------------------------------
/feature/base/src/main/kotlin/com/igorwojda/showcase/feature/base/domain/result/Result.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.base.domain.result
2 |
3 | sealed interface Result {
4 | data class Success(
5 | val value: T,
6 | ) : Result
7 |
8 | data class Failure(
9 | val throwable: Throwable? = null,
10 | ) : Result
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/presentation/screen/settings/SettingsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings.presentation.screen.settings
2 |
3 | import com.igorwojda.showcase.feature.base.presentation.viewmodel.BaseViewModel
4 |
5 | internal class SettingsViewModel : BaseViewModel(SettingsUiState.Content)
6 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/api/model/TagListApiModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.api.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class TagListApiModel(
8 | @SerialName("tag") val tag: List,
9 | )
10 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/api/model/AlbumListApiModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.api.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class AlbumListApiModel(
8 | @SerialName("album") val album: List,
9 | )
10 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/api/model/TrackListApiModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.api.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class TrackListApiModel(
8 | @SerialName("track") val track: List,
9 | )
10 |
--------------------------------------------------------------------------------
/feature/base/src/main/kotlin/com/igorwojda/showcase/feature/base/common/res/Dimen.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.base.common.res
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | object Dimen {
6 | val spaceS = 4.dp
7 | val spaceM = 8.dp
8 | val spaceL = 16.dp
9 | val spaceXL = 32.dp
10 | val spaceXXL = 64.dp
11 | val screenContentPadding = spaceL
12 | val imageSize = 100.dp
13 | }
14 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/presentation/screen/aboutlibraries/AboutLibrariesViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings.presentation.screen.aboutlibraries
2 |
3 | import com.igorwojda.showcase.feature.base.presentation.viewmodel.BaseViewModel
4 |
5 | internal class AboutLibrariesViewModel : BaseViewModel(AboutLibrariesUiState.Content)
6 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/api/model/SearchAlbumResultsApiModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.api.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class SearchAlbumResultsApiModel(
8 | @SerialName("albummatches") val albumMatches: AlbumListApiModel,
9 | )
10 |
--------------------------------------------------------------------------------
/konsist-test/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.igorwojda.showcase.convention.test.library")
3 | }
4 |
5 | android {
6 | namespace = "com.igorwojda.showcase.konsist.test"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.feature.base)
11 |
12 | testImplementation(projects.library.testUtils)
13 | testImplementation(libs.bundles.test)
14 | testImplementation(libs.konsist)
15 | testImplementation(libs.viewmodel.ktx)
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v33/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/presentation/screen/settings/SettingsUiState.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings.presentation.screen.settings
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.igorwojda.showcase.feature.base.presentation.viewmodel.BaseState
5 |
6 | @Immutable
7 | internal sealed interface SettingsUiState : BaseState {
8 | @Immutable
9 | data object Content : SettingsUiState
10 | }
11 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/AlbumKoinModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album
2 |
3 | import com.igorwojda.showcase.feature.album.data.dataModule
4 | import com.igorwojda.showcase.feature.album.domain.domainModule
5 | import com.igorwojda.showcase.feature.album.presentation.presentationModule
6 |
7 | val featureAlbumModules =
8 | listOf(
9 | presentationModule,
10 | domainModule,
11 | dataModule,
12 | )
13 |
--------------------------------------------------------------------------------
/.github/workflows/auto-approve.yml:
--------------------------------------------------------------------------------
1 | name: Auto Approve
2 |
3 | on: pull_request_target
4 |
5 | jobs:
6 | auto-approve:
7 | runs-on: ubuntu-latest
8 | permissions:
9 | pull-requests: write
10 | if: |
11 | github.event.pull_request.head.repo.full_name == github.repository &&
12 | github.actor == github.event.pull_request.user.login &&
13 | contains(fromJson('["renovate[bot]", "igorwojda"]'), github.actor)
14 | steps:
15 | - uses: hmarr/auto-approve-action@v4
16 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/api/response/GetAlbumInfoResponse.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.api.response
2 |
3 | import com.igorwojda.showcase.feature.album.data.datasource.api.model.AlbumApiModel
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | internal data class GetAlbumInfoResponse(
9 | @SerialName("album") val album: AlbumApiModel,
10 | )
11 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/database/model/TagRoomModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.database.model
2 |
3 | import com.igorwojda.showcase.feature.album.domain.model.Tag
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class TagRoomModel(
8 | val name: String,
9 | )
10 |
11 | internal fun TagRoomModel.toDomainModel() =
12 | Tag(
13 | name = this.name,
14 | )
15 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/presentation/screen/aboutlibraries/AboutLibrariesUiState.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings.presentation.screen.aboutlibraries
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.igorwojda.showcase.feature.base.presentation.viewmodel.BaseState
5 |
6 | @Immutable
7 | internal sealed interface AboutLibrariesUiState : BaseState {
8 | @Immutable
9 | data object Content : AboutLibrariesUiState
10 | }
11 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/SettingsKoinModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings
2 |
3 | import com.igorwojda.showcase.feature.settings.data.dataModule
4 | import com.igorwojda.showcase.feature.settings.domain.domainModule
5 | import com.igorwojda.showcase.feature.settings.presentation.presentationModule
6 |
7 | val featureSettingsModules =
8 | listOf(
9 | presentationModule,
10 | domainModule,
11 | dataModule,
12 | )
13 |
--------------------------------------------------------------------------------
/feature/album/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Search Album
4 | Albums
5 | Search albums…
6 |
7 | Album Cover
8 | Tracks
9 | Back
10 |
11 |
--------------------------------------------------------------------------------
/feature/favourite/src/main/kotlin/com/igorwojda/showcase/feature/favourite/FavouriteKoinModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.favourite
2 |
3 | import com.igorwojda.showcase.feature.favourite.data.dataModule
4 | import com.igorwojda.showcase.feature.favourite.domain.domainModule
5 | import com.igorwojda.showcase.feature.favourite.presentation.presentationModule
6 |
7 | val featureFavouriteModules =
8 | listOf(
9 | presentationModule,
10 | domainModule,
11 | dataModule,
12 | )
13 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/api/response/SearchAlbumResponse.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.api.response
2 |
3 | import com.igorwojda.showcase.feature.album.data.datasource.api.model.SearchAlbumResultsApiModel
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | internal data class SearchAlbumResponse(
9 | @SerialName("results") val results: SearchAlbumResultsApiModel,
10 | )
11 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/database/AlbumDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.igorwojda.showcase.feature.album.data.datasource.database.model.AlbumRoomModel
6 |
7 | @Database(entities = [AlbumRoomModel::class], version = 1, exportSchema = false)
8 | internal abstract class AlbumDatabase : RoomDatabase() {
9 | abstract fun albums(): AlbumDao
10 | }
11 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/domain/DomainModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.domain
2 |
3 | import com.igorwojda.showcase.feature.album.domain.usecase.GetAlbumListUseCase
4 | import com.igorwojda.showcase.feature.album.domain.usecase.GetAlbumUseCase
5 | import org.koin.core.module.dsl.singleOf
6 | import org.koin.dsl.module
7 |
8 | internal val domainModule =
9 | module {
10 |
11 | singleOf(::GetAlbumListUseCase)
12 |
13 | singleOf(::GetAlbumUseCase)
14 | }
15 |
--------------------------------------------------------------------------------
/feature/base/src/main/kotlin/com/igorwojda/showcase/feature/base/data/retrofit/ApiResultCallAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.base.data.retrofit
2 |
3 | import retrofit2.Call
4 | import retrofit2.CallAdapter
5 | import java.lang.reflect.Type
6 |
7 | internal class ApiResultCallAdapter(
8 | private val successType: Type,
9 | ) : CallAdapter>> {
10 | override fun responseType(): Type = successType
11 |
12 | override fun adapt(call: Call): Call> = ApiResultCall(call)
13 | }
14 |
--------------------------------------------------------------------------------
/.idea/icon.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/database/model/ImageRoomModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.database.model
2 |
3 | import com.igorwojda.showcase.feature.album.domain.model.Image
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class ImageRoomModel(
8 | val url: String,
9 | val size: ImageSizeRoomModel,
10 | )
11 |
12 | internal fun ImageRoomModel.toDomainModel() = this.size.toDomainModel()?.let { Image(this.url, it) }
13 |
--------------------------------------------------------------------------------
/feature/base/src/main/res/drawable/ic_search.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/domain/repository/AlbumRepository.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.domain.repository
2 |
3 | import com.igorwojda.showcase.feature.album.domain.model.Album
4 | import com.igorwojda.showcase.feature.base.domain.result.Result
5 |
6 | internal interface AlbumRepository {
7 | suspend fun getAlbumInfo(
8 | artistName: String,
9 | albumName: String,
10 | mbId: String?,
11 | ): Result
12 |
13 | suspend fun searchAlbum(phrase: String?): Result>
14 | }
15 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/database/model/TrackRoomModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.database.model
2 |
3 | import com.igorwojda.showcase.feature.album.domain.model.Track
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class TrackRoomModel(
8 | val name: String,
9 | val duration: Int? = null,
10 | )
11 |
12 | internal fun TrackRoomModel.toDomainModel() =
13 | Track(
14 | name = this.name,
15 | duration = this.duration,
16 | )
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground_themed.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/com/igorwojda/showcase/feature/settings/presentation/PresentationModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.settings.presentation
2 |
3 | import com.igorwojda.showcase.feature.settings.presentation.screen.aboutlibraries.AboutLibrariesViewModel
4 | import com.igorwojda.showcase.feature.settings.presentation.screen.settings.SettingsViewModel
5 | import org.koin.core.module.dsl.viewModelOf
6 | import org.koin.dsl.module
7 |
8 | internal val presentationModule =
9 | module {
10 | viewModelOf(::SettingsViewModel)
11 | viewModelOf(::AboutLibrariesViewModel)
12 | }
13 |
--------------------------------------------------------------------------------
/feature/base/src/main/kotlin/com/igorwojda/showcase/feature/base/presentation/compose/composable/UnderConstructionAnim.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.base.presentation.compose.composable
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.tooling.preview.Preview
5 | import com.igorwojda.showcase.feature.base.R
6 |
7 | @Composable
8 | fun UnderConstructionAnim() {
9 | LabeledAnimation(R.string.common_under_construction, R.raw.lottie_building_screen)
10 | }
11 |
12 | @Preview
13 | @Composable
14 | private fun UnderConstructionAnimPreview() {
15 | UnderConstructionAnim()
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/presentation/util/TimeUtil.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.presentation.util
2 |
3 | object TimeUtil {
4 | /**
5 | * provides a String representation of the given time.
6 | * @return `seconds` in mm:ss format
7 | */
8 | internal fun formatTime(seconds: Int): String {
9 | val secondsInMinute = 60
10 | val secondsInHour = 3600
11 |
12 | @Suppress("detekt.ImplicitDefaultLocale")
13 | return String.format("%02d:%02d", seconds % secondsInHour / secondsInMinute, seconds % secondsInMinute)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/library/test-utils/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.igorwojda.showcase.convention.library")
3 | }
4 |
5 | android {
6 | namespace = "com.igorwojda.showcase.library.testutils"
7 | }
8 |
9 | dependencies {
10 | // implementation configuration is used here (instead of testImplementation) because this module is added as
11 | // testImplementation dependency inside other modules. Using implementation allows to write tests for test utilities.
12 | implementation(libs.kotlin.reflect)
13 | implementation(libs.bundles.test)
14 | implementation(libs.bundles.compose)
15 |
16 | runtimeOnly(libs.junit.jupiter.engine)
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/debug/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ws.audioscrobbler.com
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/feature/base/src/main/kotlin/com/igorwojda/showcase/feature/base/common/delegate/Observer.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.base.common.delegate
2 |
3 | import kotlin.properties.ObservableProperty
4 | import kotlin.properties.ReadWriteProperty
5 | import kotlin.reflect.KProperty
6 |
7 | inline fun observer(
8 | initialValue: T,
9 | crossinline onChange: (newValue: T) -> Unit,
10 | ): ReadWriteProperty =
11 | object : ObservableProperty(initialValue) {
12 | override fun afterChange(
13 | property: KProperty<*>,
14 | oldValue: T,
15 | newValue: T,
16 | ) = onChange(newValue)
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_music_library.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/domain/usecase/GetAlbumUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.domain.usecase
2 |
3 | import com.igorwojda.showcase.feature.album.domain.model.Album
4 | import com.igorwojda.showcase.feature.album.domain.repository.AlbumRepository
5 | import com.igorwojda.showcase.feature.base.domain.result.Result
6 |
7 | internal class GetAlbumUseCase(
8 | private val albumRepository: AlbumRepository,
9 | ) {
10 | suspend operator fun invoke(
11 | artistName: String,
12 | albumName: String,
13 | mbId: String?,
14 | ): Result = albumRepository.getAlbumInfo(artistName, albumName, mbId)
15 | }
16 |
--------------------------------------------------------------------------------
/konsist-test/src/test/kotlin/com/igorwojda/showcase/konsisttest/TestKonsistTest.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.konsisttest
2 |
3 | import com.lemonappdev.konsist.api.Konsist
4 | import com.lemonappdev.konsist.api.ext.list.functions
5 | import com.lemonappdev.konsist.api.verify.assertFalse
6 | import org.junit.jupiter.api.Test
7 |
8 | // Check test coding rules.
9 | class TestKonsistTest {
10 | @Test
11 | fun `don't use JUnit4 Test annotation`() {
12 | Konsist
13 | .scopeFromProject()
14 | .classes()
15 | .functions()
16 | .assertFalse { it.hasAnnotationsWithAllNames("org.junit.Test") } // should be only org.junit.jupiter.api.Test
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/feature/settings/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Settings
4 | About
5 | Open Source Licenses
6 | View licenses of third-party libraries
7 | Licenses
8 | Navigate
9 |
10 | Open Source Licenses
11 | Back
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/igorwojda/showcase/app/presentation/NavigationRoute.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.app.presentation
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | sealed interface NavigationRoute {
6 | @Serializable
7 | data object AlbumList : NavigationRoute
8 |
9 | @Serializable
10 | data class AlbumDetail(
11 | val albumName: String,
12 | val artistName: String,
13 | val albumMbId: String?,
14 | ) : NavigationRoute
15 |
16 | @Serializable
17 | data object Favourites : NavigationRoute
18 |
19 | @Serializable
20 | data object Settings : NavigationRoute
21 |
22 | @Serializable
23 | data object AboutLibraries : NavigationRoute
24 | }
25 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/presentation/screen/albumlist/AlbumListUiState.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.presentation.screen.albumlist
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.igorwojda.showcase.feature.album.domain.model.Album
5 | import com.igorwojda.showcase.feature.base.presentation.viewmodel.BaseState
6 |
7 | @Immutable
8 | internal sealed interface AlbumListUiState : BaseState {
9 | @Immutable
10 | data class Content(
11 | val albums: List,
12 | ) : AlbumListUiState
13 |
14 | @Immutable
15 | data object Loading : AlbumListUiState
16 |
17 | @Immutable
18 | data object Error : AlbumListUiState
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_favorite.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/data/datasource/api/model/TagApiModel.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.api.model
2 |
3 | import com.igorwojda.showcase.feature.album.data.datasource.database.model.TagRoomModel
4 | import com.igorwojda.showcase.feature.album.domain.model.Tag
5 | import kotlinx.serialization.SerialName
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | internal data class TagApiModel(
10 | @SerialName("name") val name: String,
11 | )
12 |
13 | internal fun TagApiModel.toDomainModel() =
14 | Tag(
15 | name = this.name,
16 | )
17 |
18 | internal fun TagApiModel.toRoomModel() =
19 | TagRoomModel(
20 | name = this.name,
21 | )
22 |
--------------------------------------------------------------------------------
/konsist-test/src/test/kotlin/com/igorwojda/showcase/konsisttest/AndroidKonsistTest.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.konsisttest
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.lemonappdev.konsist.api.Konsist
5 | import com.lemonappdev.konsist.api.ext.list.withParentClassOf
6 | import com.lemonappdev.konsist.api.verify.assertTrue
7 | import org.junit.jupiter.api.Test
8 |
9 | // Check Android specific coding rules.
10 | class AndroidKonsistTest {
11 | @Test
12 | fun `classes extending 'ViewModel' should have 'ViewModel' suffix`() {
13 | Konsist
14 | .scopeFromProject()
15 | .classes()
16 | .withParentClassOf(ViewModel::class)
17 | .assertTrue { it.name.endsWith("ViewModel") }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | - enhancement
10 | # Label to use when marking an issue as stale
11 | staleLabel: wontfix
12 | # Comment to post when marking an issue as stale. Set to `false` to disable
13 | markComment: >
14 | This issue has been automatically marked as stale because it has not had
15 | recent activity. It will be closed if no further activity occurs. Thank you
16 | for your contributions.
17 | # Comment to post when closing a stale issue. Set to `false` to disable
18 | closeComment: true
19 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/presentation/PresentationModule.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.presentation
2 |
3 | import coil.ImageLoader
4 | import com.igorwojda.showcase.feature.album.presentation.screen.albumdetail.AlbumDetailViewModel
5 | import com.igorwojda.showcase.feature.album.presentation.screen.albumlist.AlbumListViewModel
6 | import org.koin.core.module.dsl.singleOf
7 | import org.koin.core.module.dsl.viewModelOf
8 | import org.koin.dsl.module
9 |
10 | internal val presentationModule =
11 | module {
12 |
13 | // AlbumList
14 | viewModelOf(::AlbumListViewModel)
15 |
16 | singleOf(::ImageLoader)
17 |
18 | // AlbumDetails
19 | viewModelOf(::AlbumDetailViewModel)
20 | }
21 |
--------------------------------------------------------------------------------
/feature/album/src/test/kotlin/com/igorwojda/showcase/feature/album/data/datasource/api/model/ImageSizeApiModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.data.datasource.api.model
2 |
3 | import org.junit.jupiter.api.Test
4 |
5 | class ImageSizeApiModelTest {
6 | @Test
7 | fun `maps to AlbumDomainImageSize`() {
8 | // given
9 | val dataEnums =
10 | ImageSizeApiModel
11 | .entries
12 | .filterNot { it == ImageSizeApiModel.UNKNOWN }
13 |
14 | // when
15 | dataEnums.forEach { it.toDomainModel() }
16 |
17 | // then
18 | // no explicit check is required, because test will crash if any of
19 | // the costs in the enums can't be mapped to a domain enum
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/feature/album/src/main/kotlin/com/igorwojda/showcase/feature/album/domain/model/Album.kt:
--------------------------------------------------------------------------------
1 | package com.igorwojda.showcase.feature.album.domain.model
2 |
3 | import com.igorwojda.showcase.feature.album.domain.enum.ImageSize
4 |
5 | // Images are loaded for both album list and album detail instance
6 | // Tracks and Tags are only loaded for album detail instance (not album list instance)
7 | internal data class Album(
8 | val name: String,
9 | val artist: String,
10 | val mbId: String? = null,
11 | val images: List = emptyList(),
12 | val tracks: List