├── app ├── .gitignore └── src │ ├── main │ ├── ic_launcher-web.png │ ├── res │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ ├── values │ │ │ └── ic_launcher_background.xml │ │ └── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ └── java │ │ └── com │ │ └── chesire │ │ └── nekome │ │ ├── ui │ │ └── UIState.kt │ │ ├── injection │ │ ├── LogoutExecutorModule.kt │ │ ├── WorkerModule.kt │ │ ├── AppModule.kt │ │ ├── DatabaseModule.kt │ │ └── SeriesModule.kt │ │ ├── services │ │ ├── WidgetDataWorker.kt │ │ └── DataRefreshNotifier.kt │ │ ├── binders │ │ └── UserProviderBinder.kt │ │ └── LogoutHandler.kt │ ├── androidTest │ └── java │ │ └── com │ │ └── chesire │ │ └── nekome │ │ ├── helpers │ │ ├── IntExtensions.kt │ │ ├── AuthProviderExtensions.kt │ │ ├── creation │ │ │ ├── UserDomain.kt │ │ │ └── SearchDomain.kt │ │ ├── UserDaoExtensions.kt │ │ └── PreferencesExtensions.kt │ │ ├── injection │ │ ├── MockAuthModule.kt │ │ ├── MockUserModule.kt │ │ ├── MockLibraryModule.kt │ │ ├── MockSearchModule.kt │ │ └── MemoryDatabaseModule.kt │ │ └── TestRunner.kt │ └── test │ └── java │ └── com │ └── chesire │ └── nekome │ └── LogoutHandlerTests.kt ├── core ├── compose │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── core │ │ │ └── compose │ │ │ ├── theme │ │ │ ├── Type.kt │ │ │ └── Shape.kt │ │ │ └── LazyListStateExtensions.kt │ └── build.gradle.kts ├── preferences │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── keys.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── core │ │ │ │ └── preferences │ │ │ │ ├── flags │ │ │ │ ├── ImageQuality.kt │ │ │ │ ├── SortOption.kt │ │ │ │ ├── TitleLanguage.kt │ │ │ │ └── HomeScreenOptions.kt │ │ │ │ └── ext │ │ │ │ └── SharedPreferenceExtensions.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── core │ │ │ └── preferences │ │ │ ├── ApplicationPreferencesTests.kt │ │ │ ├── SeriesPreferencesTests.kt │ │ │ └── flags │ │ │ ├── ImageQualityTests.kt │ │ │ └── TitleLanguageTests.kt │ └── build.gradle.kts └── resources │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── chesire │ │ └── nekome │ │ └── resources │ │ └── Alias.kt │ └── build.gradle.kts ├── testing ├── .gitignore ├── consumer-rules.pro └── build.gradle.kts ├── features ├── login │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── chesire │ │ │ │ │ └── nekome │ │ │ │ │ └── app │ │ │ │ │ └── login │ │ │ │ │ ├── syncing │ │ │ │ │ ├── ui │ │ │ │ │ │ └── UIState.kt │ │ │ │ │ └── core │ │ │ │ │ │ ├── SyncSeriesUseCase.kt │ │ │ │ │ │ └── RetrieveAvatarUseCase.kt │ │ │ │ │ └── credentials │ │ │ │ │ ├── core │ │ │ │ │ ├── ClearCredentialsUseCase.kt │ │ │ │ │ └── PopulateUserDetailsUseCase.kt │ │ │ │ │ └── ui │ │ │ │ │ ├── ViewAction.kt │ │ │ │ │ └── UIState.kt │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ │ └── drawable │ │ │ │ ├── ic_lock.xml │ │ │ │ └── ic_account_circle.xml │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── app │ │ │ └── login │ │ │ ├── credentials │ │ │ └── core │ │ │ │ └── ClearCredentialsUseCaseTest.kt │ │ │ └── syncing │ │ │ └── core │ │ │ └── SyncSeriesUseCaseTest.kt │ └── consumer-rules.pro ├── search │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── app │ │ │ │ └── search │ │ │ │ └── search │ │ │ │ ├── core │ │ │ │ ├── model │ │ │ │ │ └── SearchGroup.kt │ │ │ │ ├── RememberSearchGroupUseCase.kt │ │ │ │ ├── RetrieveUserSeriesIdsUseCase.kt │ │ │ │ └── SearchInitializeUseCase.kt │ │ │ │ ├── ui │ │ │ │ └── ViewAction.kt │ │ │ │ └── data │ │ │ │ └── SearchPreferences.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── app │ │ │ └── search │ │ │ └── search │ │ │ └── core │ │ │ └── RememberSearchGroupUseCaseTest.kt │ └── consumer-rules.pro ├── series │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── app │ │ │ │ └── series │ │ │ │ ├── collection │ │ │ │ ├── core │ │ │ │ │ ├── CurrentSortUseCase.kt │ │ │ │ │ ├── CollectSeriesUseCase.kt │ │ │ │ │ ├── UpdateSortUseCase.kt │ │ │ │ │ ├── UpdateFiltersUseCase.kt │ │ │ │ │ ├── CurrentFiltersUseCase.kt │ │ │ │ │ ├── ShouldRateSeriesUseCase.kt │ │ │ │ │ ├── RefreshSeriesUseCase.kt │ │ │ │ │ └── IncrementSeriesUseCase.kt │ │ │ │ └── ui │ │ │ │ │ └── ViewAction.kt │ │ │ │ └── item │ │ │ │ ├── core │ │ │ │ ├── RetrieveItemUseCase.kt │ │ │ │ ├── GetImageUseCase.kt │ │ │ │ ├── DeleteItemUseCase.kt │ │ │ │ ├── BuildTitleUseCase.kt │ │ │ │ └── UpdateItemUseCase.kt │ │ │ │ └── ui │ │ │ │ └── ViewAction.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── app │ │ │ └── series │ │ │ ├── collection │ │ │ └── core │ │ │ │ ├── UpdateSortUseCaseTest.kt │ │ │ │ ├── CurrentSortUseCaseTest.kt │ │ │ │ └── CollectSeriesUseCaseTest.kt │ │ │ └── item │ │ │ └── core │ │ │ └── RetrieveItemUseCaseTest.kt │ └── consumer-rules.pro ├── serieswidget │ ├── .gitignore │ ├── consumer-rules.pro │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── feature │ │ │ └── serieswidget │ │ │ ├── ui │ │ │ ├── ViewAction.kt │ │ │ ├── UIState.kt │ │ │ └── DomainMapper.kt │ │ │ ├── GlanceDataReceiver.kt │ │ │ ├── SeriesWidgetEntryPoint.kt │ │ │ └── core │ │ │ └── UpdateSeriesUseCase.kt │ │ ├── res │ │ └── xml │ │ │ └── series_widget_info.xml │ │ └── AndroidManifest.xml └── settings │ ├── .gitignore │ ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── app │ │ │ └── settings │ │ │ └── config │ │ │ ├── core │ │ │ ├── RetrieveUserUseCase.kt │ │ │ ├── UpdateRateSeriesUseCase.kt │ │ │ ├── UpdateThemeUseCase.kt │ │ │ ├── UpdateImageQualityUseCase.kt │ │ │ ├── UpdateTitleLanguageUseCase.kt │ │ │ ├── UpdateDefaultHomeScreenUseCase.kt │ │ │ └── UpdateDefaultSeriesStateUseCase.kt │ │ │ └── LogoutExecutor.kt │ └── test │ │ └── java │ │ └── com │ │ └── chesire │ │ └── nekome │ │ └── app │ │ └── settings │ │ └── config │ │ └── core │ │ ├── UpdateRateSeriesUseCaseTest.kt │ │ ├── UpdateThemeUseCaseTest.kt │ │ ├── UpdateImageQualityUseCaseTest.kt │ │ ├── UpdateTitleLanguageUseCaseTest.kt │ │ ├── UpdateDefaultSeriesStateUseCaseTest.kt │ │ ├── UpdateDefaultHomeScreenUseCaseTest.kt │ │ └── RetrieveUserUseCaseTest.kt │ └── consumer-rules.pro ├── libraries ├── core │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── drawable-hdpi │ │ │ │ │ └── blackcat.webp │ │ │ │ ├── drawable-mdpi │ │ │ │ │ └── blackcat.webp │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ └── blackcat.webp │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ └── blackcat.webp │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ └── blackcat.webp │ │ │ │ ├── anim │ │ │ │ │ ├── fragment_fade_enter.xml │ │ │ │ │ └── fragment_fade_exit.xml │ │ │ │ ├── values-v21 │ │ │ │ │ └── styles.xml │ │ │ │ ├── values │ │ │ │ │ ├── colors.xml │ │ │ │ │ ├── dimens.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── values-night │ │ │ │ │ └── colors.xml │ │ │ │ └── drawable │ │ │ │ │ ├── splash_screen.xml │ │ │ │ │ └── ic_blackcat.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── core │ │ │ │ ├── flags │ │ │ │ ├── Service.kt │ │ │ │ ├── RatingSystem.kt │ │ │ │ ├── SeriesStatus.kt │ │ │ │ ├── Subtype.kt │ │ │ │ └── SeriesType.kt │ │ │ │ ├── models │ │ │ │ └── ErrorDomain.kt │ │ │ │ └── AuthCaster.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── core │ │ │ └── flags │ │ │ └── SeriesTypeTests.kt │ ├── consumer-rules.pro │ └── build.gradle.kts ├── kitsu │ ├── .gitignore │ ├── activity │ │ ├── consumer-rules.pro │ │ ├── .gitignore │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── kitsu │ │ │ │ └── activity │ │ │ │ ├── dto │ │ │ │ ├── ChangedData.kt │ │ │ │ └── RetrieveActivityDto.kt │ │ │ │ ├── KitsuActivityService.kt │ │ │ │ └── RetrieveActivityDtoMapper.kt │ │ └── build.gradle.kts │ ├── auth │ │ ├── .gitignore │ │ ├── consumer-rules.pro │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── kitsu │ │ │ │ └── auth │ │ │ │ ├── dto │ │ │ │ ├── AuthResponseDto.kt │ │ │ │ ├── RefreshTokenRequestDto.kt │ │ │ │ └── LoginRequestDto.kt │ │ │ │ └── KitsuAuthService.kt │ │ └── build.gradle.kts │ ├── library │ │ ├── consumer-rules.pro │ │ ├── .gitignore │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── kitsu │ │ │ │ └── library │ │ │ │ └── dto │ │ │ │ ├── AddResponseDto.kt │ │ │ │ └── RetrieveResponseDto.kt │ │ └── build.gradle.kts │ ├── search │ │ ├── consumer-rules.pro │ │ ├── .gitignore │ │ ├── src │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── chesire │ │ │ │ │ └── nekome │ │ │ │ │ └── kitsu │ │ │ │ │ └── search │ │ │ │ │ ├── dto │ │ │ │ │ ├── SearchResponseDto.kt │ │ │ │ │ └── SearchItemDto.kt │ │ │ │ │ └── SearchItemDtoMapper.kt │ │ │ └── test │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── kitsu │ │ │ │ └── search │ │ │ │ └── SearchCreation.kt │ │ └── build.gradle.kts │ ├── trending │ │ ├── consumer-rules.pro │ │ ├── .gitignore │ │ ├── src │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── chesire │ │ │ │ │ └── nekome │ │ │ │ │ └── kitsu │ │ │ │ │ └── trending │ │ │ │ │ ├── dto │ │ │ │ │ ├── TrendingResponseDto.kt │ │ │ │ │ └── TrendingItemDto.kt │ │ │ │ │ ├── KitsuTrendingService.kt │ │ │ │ │ └── TrendingItemDtoMapper.kt │ │ │ └── test │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── kitsu │ │ │ │ └── trending │ │ │ │ └── TrendingCreation.kt │ │ └── build.gradle.kts │ ├── user │ │ ├── .gitignore │ │ ├── consumer-rules.pro │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── kitsu │ │ │ │ └── user │ │ │ │ ├── dto │ │ │ │ ├── UserResponseDto.kt │ │ │ │ └── UserItemDto.kt │ │ │ │ ├── KitsuUserService.kt │ │ │ │ └── UserItemDtoMapper.kt │ │ └── build.gradle.kts │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── kitsu │ │ │ ├── KitsuConstants.kt │ │ │ ├── api │ │ │ └── intermediaries │ │ │ │ └── Links.kt │ │ │ ├── ResponseParsing.kt │ │ │ └── adapters │ │ │ └── SeriesTypeAdapter.kt │ ├── consumer-rules.pro │ └── build.gradle.kts ├── database │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── chesire │ │ │ │ └── nekome │ │ │ │ └── database │ │ │ │ ├── entity │ │ │ │ ├── UserEntity.kt │ │ │ │ └── SeriesEntity.kt │ │ │ │ └── converters │ │ │ │ ├── SubtypeConverter.kt │ │ │ │ ├── ServiceConverter.kt │ │ │ │ ├── SeriesTypeConverter.kt │ │ │ │ ├── SeriesStatusConverter.kt │ │ │ │ ├── UserSeriesStatusConverter.kt │ │ │ │ ├── ImageModelConverter.kt │ │ │ │ └── MapConverter.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── database │ │ │ └── converters │ │ │ ├── ServiceConverterTests.kt │ │ │ ├── SubtypeConverterTests.kt │ │ │ ├── SeriesTypeConverterTests.kt │ │ │ ├── SeriesStatusConverterTests.kt │ │ │ ├── UserSeriesStatusConverterTests.kt │ │ │ ├── ImageModelConverterTests.kt │ │ │ └── MapConverterTests.kt │ └── consumer-rules.pro └── datasource │ ├── activity │ ├── consumer-rules.pro │ ├── .gitignore │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── datasource │ │ │ └── activity │ │ │ ├── ActivityDomain.kt │ │ │ ├── Event.kt │ │ │ ├── remote │ │ │ └── ActivityApi.kt │ │ │ └── local │ │ │ └── ActivityLocalDataStorage.kt │ └── build.gradle.kts │ ├── auth │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── datasource │ │ │ └── auth │ │ │ ├── AuthException.kt │ │ │ ├── remote │ │ │ ├── AuthFailure.kt │ │ │ ├── AuthApi.kt │ │ │ └── AuthInjectionInterceptor.kt │ │ │ └── local │ │ │ └── AuthProvider.kt │ └── build.gradle.kts │ ├── search │ ├── consumer-rules.pro │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── chesire │ │ └── nekome │ │ └── datasource │ │ └── search │ │ ├── SearchDomain.kt │ │ └── remote │ │ └── SearchApi.kt │ ├── series │ ├── consumer-rules.pro │ ├── .gitignore │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── datasource │ │ │ └── series │ │ │ ├── UserProvider.kt │ │ │ └── SeriesDomain.kt │ └── build.gradle.kts │ ├── trending │ ├── consumer-rules.pro │ ├── .gitignore │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── chesire │ │ │ └── nekome │ │ │ └── datasource │ │ │ └── trending │ │ │ ├── TrendingDomain.kt │ │ │ └── remote │ │ │ └── TrendingApi.kt │ └── build.gradle.kts │ └── user │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ └── main │ │ └── java │ │ └── com │ │ └── chesire │ │ └── nekome │ │ └── datasource │ │ └── user │ │ ├── UserDomain.kt │ │ ├── User.kt │ │ ├── remote │ │ └── UserApi.kt │ │ └── UserMapper.kt │ └── build.gradle.kts ├── fastlane ├── metadata │ └── android │ │ ├── en-GB │ │ ├── video.txt │ │ ├── title.txt │ │ ├── changelogs │ │ │ ├── 24081508.txt │ │ │ ├── 151.txt │ │ │ ├── 23071421.txt │ │ │ ├── 24010720.txt │ │ │ ├── 23042320.txt │ │ │ ├── 23101020.txt │ │ │ ├── 23052116.txt │ │ │ ├── 23062419.txt │ │ │ ├── 21091122.txt │ │ │ ├── 23050122.txt │ │ │ ├── 21091120.txt │ │ │ ├── 23100818.txt │ │ │ ├── 24012317.txt │ │ │ ├── 24010710.txt │ │ │ ├── 23062323.txt │ │ │ └── 201.txt │ │ ├── short_description.txt │ │ ├── images │ │ │ ├── icon.png │ │ │ ├── featureGraphic.png │ │ │ └── phoneScreenshots │ │ │ │ ├── 1_en-GB.png │ │ │ │ ├── 2_en-GB.png │ │ │ │ ├── 3_en-GB.png │ │ │ │ ├── 4_en-GB.png │ │ │ │ ├── 5_en-GB.png │ │ │ │ └── 6_en-GB.png │ │ └── full_description.txt │ │ └── en-US │ │ ├── video.txt │ │ ├── title.txt │ │ ├── changelogs │ │ ├── 24081508.txt │ │ ├── 151.txt │ │ ├── 23071421.txt │ │ ├── 24010720.txt │ │ ├── 23042320.txt │ │ ├── 23101020.txt │ │ ├── 23052116.txt │ │ ├── 23062419.txt │ │ ├── 21091122.txt │ │ ├── 23050122.txt │ │ ├── 21091120.txt │ │ ├── 23100818.txt │ │ ├── 24012317.txt │ │ ├── 24010710.txt │ │ ├── 23062323.txt │ │ └── 201.txt │ │ ├── short_description.txt │ │ ├── images │ │ ├── icon.png │ │ ├── featureGraphic.png │ │ └── phoneScreenshots │ │ │ ├── 1_en-US.png │ │ │ ├── 2_en-US.png │ │ │ ├── 3_en-US.png │ │ │ ├── 4_en-US.png │ │ │ ├── 5_en-US.png │ │ │ └── 6_en-US.png │ │ └── full_description.txt ├── Appfile ├── Fastfile └── README.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── Gemfile ├── .idea ├── codeStyles │ └── codeStyleConfig.xml └── kotlinc.xml ├── codecov.yml ├── .github ├── renovate.json └── workflows │ └── master.yml ├── privacypolicy.md ├── gradle.properties ├── Dangerfile └── settings.gradle.kts /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/compose/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/compose/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/preferences/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/resources/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/resources/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testing/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/preferences/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/login/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /features/search/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /features/series/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /features/serieswidget/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/kitsu/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/video.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/video.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/serieswidget/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/settings/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/database/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/kitsu/activity/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/kitsu/auth/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/kitsu/auth/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/kitsu/library/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/kitsu/search/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/kitsu/trending/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/kitsu/user/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/kitsu/user/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/title.txt: -------------------------------------------------------------------------------- 1 | Nekome -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Nekome -------------------------------------------------------------------------------- /libraries/datasource/activity/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/datasource/auth/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/datasource/auth/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/datasource/search/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/datasource/series/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/datasource/trending/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/datasource/user/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/datasource/user/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/kitsu/activity/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/kitsu/library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/kitsu/search/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/kitsu/trending/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/datasource/activity/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/datasource/search/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/datasource/series/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/datasource/trending/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/24081508.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/24081508.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/151.txt: -------------------------------------------------------------------------------- 1 | Initial release -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/151.txt: -------------------------------------------------------------------------------- 1 | Initial release -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/23071421.txt: -------------------------------------------------------------------------------- 1 | * Update the series detail view 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/24010720.txt: -------------------------------------------------------------------------------- 1 | * Use updated logo for the Nekome 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23071421.txt: -------------------------------------------------------------------------------- 1 | * Update the series detail view 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/24010720.txt: -------------------------------------------------------------------------------- 1 | * Use updated logo for the Nekome 2 | -------------------------------------------------------------------------------- /core/resources/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/23042320.txt: -------------------------------------------------------------------------------- 1 | * Big rewrite to change how the UI looks. 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/23101020.txt: -------------------------------------------------------------------------------- 1 | * Fix issue with R8 causing api failures 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23042320.txt: -------------------------------------------------------------------------------- 1 | Big rewrite to change how the UI looks. 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23101020.txt: -------------------------------------------------------------------------------- 1 | * Fix issue with R8 causing api failures 2 | -------------------------------------------------------------------------------- /core/preferences/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/23052116.txt: -------------------------------------------------------------------------------- 1 | * Fix issue with api calls to Kitsu not working 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/23062419.txt: -------------------------------------------------------------------------------- 1 | * Add option to set the title display language 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23052116.txt: -------------------------------------------------------------------------------- 1 | * Fix issue with api calls to Kitsu not working 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23062419.txt: -------------------------------------------------------------------------------- 1 | * Add option to set the title display language 2 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/21091122.txt: -------------------------------------------------------------------------------- 1 | * Fix issue with background services not starting up correctly -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/23050122.txt: -------------------------------------------------------------------------------- 1 | * Fix potential crash when loading the settings screen. 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/21091122.txt: -------------------------------------------------------------------------------- 1 | * Fix issue with background services not starting up correctly -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23050122.txt: -------------------------------------------------------------------------------- 1 | * Fix potential crash when loading the settings screen. 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/21091120.txt: -------------------------------------------------------------------------------- 1 | * Fix crash that would sometimes occur due to invalid persisted data -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/short_description.txt: -------------------------------------------------------------------------------- 1 | Keep track of your anime and manga, through the use of the Kitsu API. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/21091120.txt: -------------------------------------------------------------------------------- 1 | * Fix crash that would sometimes occur due to invalid persisted data -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Keep track of your anime and manga, through the use of the Kitsu API. -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/23100818.txt: -------------------------------------------------------------------------------- 1 | * Add a widget that allows user to update their anime series 2 | * UI improvements 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/24012317.txt: -------------------------------------------------------------------------------- 1 | * Fix crash when navigating to series detail view, if series has a / in the name 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23100818.txt: -------------------------------------------------------------------------------- 1 | * Add a widget that allows user to update their anime series 2 | * UI improvements 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/24012317.txt: -------------------------------------------------------------------------------- 1 | * Fix crash when navigating to series detail view, if series has a / in the name 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/24010710.txt: -------------------------------------------------------------------------------- 1 | * Add monochrome icon 2 | * Move search from bottom navigation to the series list as a FAB -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-GB/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/24010710.txt: -------------------------------------------------------------------------------- 1 | * Add monochrome icon 2 | * Move search from bottom navigation to the series list as a FAB -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /libraries/core/src/main/res/drawable-hdpi/blackcat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/libraries/core/src/main/res/drawable-hdpi/blackcat.webp -------------------------------------------------------------------------------- /libraries/core/src/main/res/drawable-mdpi/blackcat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/libraries/core/src/main/res/drawable-mdpi/blackcat.webp -------------------------------------------------------------------------------- /libraries/core/src/main/res/drawable-xhdpi/blackcat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/libraries/core/src/main/res/drawable-xhdpi/blackcat.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/images/featureGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-GB/images/featureGraphic.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/featureGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-US/images/featureGraphic.png -------------------------------------------------------------------------------- /libraries/core/src/main/res/drawable-xxhdpi/blackcat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/libraries/core/src/main/res/drawable-xxhdpi/blackcat.webp -------------------------------------------------------------------------------- /libraries/core/src/main/res/drawable-xxxhdpi/blackcat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/libraries/core/src/main/res/drawable-xxxhdpi/blackcat.webp -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/23062323.txt: -------------------------------------------------------------------------------- 1 | * Add option to change displayed image quality 2 | * Fix potential crash occurring while app is in background 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23062323.txt: -------------------------------------------------------------------------------- 1 | * Add option to change displayed image quality 2 | * Fix potential crash occurring while app is in background 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane", "> 0" 4 | gem "danger", "> 0" 5 | gem "danger-android_lint", "> 0" 6 | gem "danger-checkstyle_format", "> 0" 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #74909E 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/images/phoneScreenshots/1_en-GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-GB/images/phoneScreenshots/1_en-GB.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/images/phoneScreenshots/2_en-GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-GB/images/phoneScreenshots/2_en-GB.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/images/phoneScreenshots/3_en-GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-GB/images/phoneScreenshots/3_en-GB.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/images/phoneScreenshots/4_en-GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-GB/images/phoneScreenshots/4_en-GB.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/images/phoneScreenshots/5_en-GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-GB/images/phoneScreenshots/5_en-GB.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/images/phoneScreenshots/6_en-GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-GB/images/phoneScreenshots/6_en-GB.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chesire/Nekome/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /libraries/kitsu/auth/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/kitsu/library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/kitsu/search/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/kitsu/src/main/java/com/chesire/nekome/kitsu/KitsuConstants.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu 2 | 3 | /** 4 | * Base URL for all Kitsu requests. 5 | */ 6 | const val KITSU_URL = "https://kitsu.app/" 7 | -------------------------------------------------------------------------------- /libraries/kitsu/user/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /features/search/src/main/java/com/chesire/nekome/app/search/search/core/model/SearchGroup.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.search.search.core.model 2 | 3 | enum class SearchGroup { 4 | Anime, 5 | Manga 6 | } 7 | -------------------------------------------------------------------------------- /libraries/kitsu/activity/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/kitsu/trending/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | json_key_file("fastlane/googleservice.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one 2 | package_name("com.chesire.nekome") # e.g. com.krausefx.app 3 | -------------------------------------------------------------------------------- /features/login/src/main/java/com/chesire/nekome/app/login/syncing/ui/UIState.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.login.syncing.ui 2 | 3 | data class UIState( 4 | val avatar: String, 5 | val finishedSyncing: Boolean? 6 | ) 7 | -------------------------------------------------------------------------------- /libraries/datasource/auth/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/resources/src/main/java/com/chesire/nekome/resources/Alias.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.resources 2 | 3 | typealias Resources = com.chesire.nekome.core.resources.R 4 | typealias StringResource = com.chesire.nekome.core.resources.R.string 5 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/changelogs/201.txt: -------------------------------------------------------------------------------- 1 | * Add a link to the open source repo on GitHub from the settings. 2 | * Update the results screen to show a bit more information about each series. 3 | * Update the login and syncing screens UI. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/201.txt: -------------------------------------------------------------------------------- 1 | * Add a link to the open source repo on GitHub from the settings. 2 | * Update the results screen to show a bit more information about each series. 3 | * Update the login and syncing screens UI. -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/chesire/nekome/core/flags/Service.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.flags 2 | 3 | /** 4 | * All possible supported services. 5 | */ 6 | enum class Service { 7 | Kitsu, 8 | Unknown 9 | } 10 | -------------------------------------------------------------------------------- /features/login/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /features/serieswidget/src/main/java/com/chesire/nekome/feature/serieswidget/ui/ViewAction.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.feature.serieswidget.ui 2 | 3 | sealed interface ViewAction { 4 | 5 | data class UpdateSeries(val id: Int) : ViewAction 6 | } 7 | -------------------------------------------------------------------------------- /core/preferences/src/test/java/com/chesire/nekome/core/preferences/ApplicationPreferencesTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.preferences 2 | 3 | class ApplicationPreferencesTests { 4 | // TODO: Add unit tests for the application preferences 5 | } 6 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "app/src/main/java/com/chesire/nekome/App.kt" 3 | - "app/src/main/java/com/chesire/nekome/injection/*" 4 | - "app/src/main/java/com/chesire/nekome/injection/**/*" 5 | - "testing/src/main/java/**/*" 6 | - "**/*Fragment*" 7 | - "**/build/**" -------------------------------------------------------------------------------- /core/compose/src/main/java/com/chesire/nekome/core/compose/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.compose.theme 2 | 3 | import androidx.compose.material3.Typography 4 | 5 | // Set of Material typography styles to start with 6 | internal val NekomeTypography = Typography() 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/chesire/nekome/core/flags/RatingSystem.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.flags 2 | 3 | /** 4 | * Wrapper around the possible configurations for the ratings. 5 | */ 6 | enum class RatingSystem { 7 | Unknown, 8 | Advanced, 9 | Regular, 10 | Simple 11 | } 12 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/chesire/nekome/core/flags/SeriesStatus.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.flags 2 | 3 | /** 4 | * All possible states of a series. 5 | */ 6 | enum class SeriesStatus { 7 | Unknown, 8 | Current, 9 | Finished, 10 | TBA, 11 | Unreleased, 12 | Upcoming 13 | } 14 | -------------------------------------------------------------------------------- /libraries/core/src/main/res/anim/fragment_fade_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /libraries/core/src/main/res/anim/fragment_fade_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /libraries/core/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /libraries/core/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #607D8B 4 | #455A64 5 | #42A5F5 6 | 7 | #AAAAAA 8 | 9 | -------------------------------------------------------------------------------- /libraries/core/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #333F48 4 | #252E34 5 | #2196F3 6 | 7 | #AAAAAA 8 | 9 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/helpers/IntExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.helpers 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | 5 | /** 6 | * Gets a string based on what the value of [this] is. 7 | */ 8 | fun Int.getResource() = InstrumentationRegistry.getInstrumentation().targetContext.getString(this) 9 | -------------------------------------------------------------------------------- /libraries/datasource/activity/src/main/java/com/chesire/nekome/datasource/activity/ActivityDomain.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.activity 2 | 3 | /** 4 | * Provides a domain for a single piece of activity that has occurred. 5 | */ 6 | data class ActivityDomain( 7 | val id: Int, 8 | val timestamp: String, 9 | val events: List 10 | ) 11 | -------------------------------------------------------------------------------- /features/serieswidget/src/main/java/com/chesire/nekome/feature/serieswidget/ui/UIState.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.feature.serieswidget.ui 2 | 3 | data class UIState( 4 | val series: List = emptyList() 5 | ) 6 | 7 | data class Series( 8 | val userId: Int, 9 | val title: String, 10 | val progress: String, 11 | val isUpdating: Boolean 12 | ) 13 | -------------------------------------------------------------------------------- /libraries/core/src/main/res/drawable/splash_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /libraries/datasource/auth/src/main/java/com/chesire/nekome/datasource/auth/AuthException.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.auth 2 | 3 | import java.io.IOException 4 | 5 | /** 6 | * Exception thrown when the auth token is unable to be refreshed. 7 | */ 8 | class AuthException : IOException() 9 | // IOException must be extended or the Retrofit interceptors will crash 10 | -------------------------------------------------------------------------------- /core/preferences/src/test/java/com/chesire/nekome/core/preferences/SeriesPreferencesTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.preferences 2 | 3 | class SeriesPreferencesTests { 4 | 5 | private val defaultFilter = 6 | """ 7 | {"0":true,"1":false,"2":false,"3":false,"4":false} 8 | """.trimIndent() 9 | 10 | // TODO: Add unit tests for the series preferences 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "assignees": [ 6 | "Chesire" 7 | ], 8 | "branchPrefix": "deps/", 9 | "semanticCommits": "enabled", 10 | "semanticCommitType": "chore", 11 | "semanticCommitScope": "deps", 12 | "labels": [ 13 | "dependencies" 14 | ], 15 | "bundler": { 16 | "rangeStrategy": "update-lockfile" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/resources/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.chesire.nekome.core.resources" 8 | compileSdk = libs.versions.sdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /features/login/src/main/java/com/chesire/nekome/app/login/credentials/core/ClearCredentialsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.login.credentials.core 2 | 3 | import com.chesire.nekome.datasource.auth.AccessTokenRepository 4 | import javax.inject.Inject 5 | 6 | class ClearCredentialsUseCase @Inject constructor(private val auth: AccessTokenRepository) { 7 | 8 | operator fun invoke() = auth.clear() 9 | } 10 | -------------------------------------------------------------------------------- /libraries/kitsu/user/src/main/java/com/chesire/nekome/kitsu/user/dto/UserResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.user.dto 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | /** 7 | * DTO for responses from the Kitsu user endpoint. 8 | */ 9 | @JsonClass(generateAdapter = true) 10 | data class UserResponseDto( 11 | @Json(name = "data") 12 | val data: List 13 | ) 14 | -------------------------------------------------------------------------------- /privacypolicy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Nekome takes your privacy seriously. To better protect your privacy we provide this privacy policy notice explaining the way your personal information is collected and used. 4 | 5 | ## Collection of Information 6 | 7 | Nekome collects the email address and password of the user for their Kitsu account to facilitate logging in and retrieve/updating their lists. 8 | No other data is obtained or stored. 9 | -------------------------------------------------------------------------------- /libraries/kitsu/search/src/main/java/com/chesire/nekome/kitsu/search/dto/SearchResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.search.dto 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | /** 7 | * DTO for responses from the Kitsu search endpoint. 8 | */ 9 | @JsonClass(generateAdapter = true) 10 | data class SearchResponseDto( 11 | @Json(name = "data") 12 | val data: List 13 | ) 14 | -------------------------------------------------------------------------------- /core/compose/src/main/java/com/chesire/nekome/core/compose/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.compose.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | internal val NekomeShapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) 12 | -------------------------------------------------------------------------------- /libraries/kitsu/trending/src/main/java/com/chesire/nekome/kitsu/trending/dto/TrendingResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.trending.dto 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | /** 7 | * DTO for responses from the Kitsu trending endpoint. 8 | */ 9 | @JsonClass(generateAdapter = true) 10 | data class TrendingResponseDto( 11 | @Json(name = "data") 12 | val data: List 13 | ) 14 | -------------------------------------------------------------------------------- /core/preferences/src/main/res/values/keys.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | keyDefaultSeriesState 4 | keyDefaultHomeScreen 5 | keyTheme 6 | keyRateOnCompletion 7 | 8 | -------------------------------------------------------------------------------- /features/login/src/main/java/com/chesire/nekome/app/login/credentials/ui/ViewAction.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.login.credentials.ui 2 | 3 | sealed interface ViewAction { 4 | data class UsernameChanged(val newUsername: String) : ViewAction 5 | data class PasswordChanged(val newPassword: String) : ViewAction 6 | object LoginPressed : ViewAction 7 | object ErrorSnackbarObserved : ViewAction 8 | object NavigationObserved : ViewAction 9 | } 10 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/chesire/nekome/core/flags/Subtype.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.flags 2 | 3 | /** 4 | * List of all possible series sub types. 5 | */ 6 | enum class Subtype { 7 | Unknown, 8 | 9 | // Anime 10 | ONA, 11 | OVA, 12 | TV, 13 | Movie, 14 | Music, 15 | Special, 16 | 17 | // Manga 18 | Doujin, 19 | Manga, 20 | Manhua, 21 | Manhwa, 22 | Novel, 23 | OEL, 24 | Oneshot 25 | } 26 | -------------------------------------------------------------------------------- /libraries/datasource/trending/src/main/java/com/chesire/nekome/datasource/trending/TrendingDomain.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.trending 2 | 3 | import com.chesire.nekome.core.flags.SeriesType 4 | import com.chesire.nekome.core.models.ImageModel 5 | 6 | /** 7 | * Domain class for a trending item. 8 | */ 9 | data class TrendingDomain( 10 | val id: Int, 11 | val type: SeriesType, 12 | val canonicalTitle: String, 13 | val posterImage: ImageModel 14 | ) 15 | -------------------------------------------------------------------------------- /libraries/datasource/auth/src/main/java/com/chesire/nekome/datasource/auth/remote/AuthFailure.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.auth.remote 2 | 3 | data class AuthDomain( 4 | val accessToken: String, 5 | val refreshToken: String 6 | ) 7 | 8 | sealed class AuthFailure { 9 | object CouldNotReachServer : AuthFailure() 10 | object InvalidCredentials : AuthFailure() 11 | object CouldNotRefresh : AuthFailure() 12 | object BadRequest : AuthFailure() 13 | } 14 | -------------------------------------------------------------------------------- /features/serieswidget/src/main/java/com/chesire/nekome/feature/serieswidget/GlanceDataReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.feature.serieswidget 2 | 3 | import androidx.glance.appwidget.GlanceAppWidget 4 | import androidx.glance.appwidget.GlanceAppWidgetReceiver 5 | import com.chesire.nekome.feature.serieswidget.ui.SeriesWidget 6 | 7 | class GlanceDataReceiver : GlanceAppWidgetReceiver() { 8 | override val glanceAppWidget: GlanceAppWidget 9 | get() = SeriesWidget() 10 | } 11 | -------------------------------------------------------------------------------- /libraries/kitsu/activity/src/main/java/com/chesire/nekome/kitsu/activity/dto/ChangedData.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.activity.dto 2 | 3 | /** 4 | * Container for the [ChangedData] events from the API. 5 | */ 6 | data class ChangedDataContainer( 7 | val changedData: List 8 | ) 9 | 10 | /** 11 | * Data for an event of "activity" for a user. 12 | */ 13 | data class ChangedData( 14 | val type: String, 15 | val from: String, 16 | val to: String 17 | ) 18 | -------------------------------------------------------------------------------- /libraries/datasource/user/src/main/java/com/chesire/nekome/datasource/user/UserDomain.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.user 2 | 3 | import com.chesire.nekome.core.flags.Service 4 | import com.chesire.nekome.core.models.ImageModel 5 | 6 | /** 7 | * Domain class for user related information. 8 | */ 9 | data class UserDomain( 10 | val userId: Int, 11 | val name: String, 12 | val avatar: ImageModel, 13 | val coverImage: ImageModel, 14 | val service: Service 15 | ) 16 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/chesire/nekome/core/flags/SeriesType.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.flags 2 | 3 | /** 4 | * All possible types of a series. 5 | */ 6 | enum class SeriesType(val id: Int) { 7 | Unknown(-1), 8 | Anime(0), 9 | Manga(1); 10 | 11 | companion object { 12 | /** 13 | * Gets the series typed based on its id. 14 | */ 15 | fun forId(typeId: Int): SeriesType = values().find { it.id == typeId } ?: Unknown 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libraries/datasource/activity/src/main/java/com/chesire/nekome/datasource/activity/Event.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.activity 2 | 3 | /** 4 | * Represents a single activity event. 5 | * [from] represents the value before the activity event. 6 | * [to] represents the value after the activity event. 7 | * [eventType] represents the type of event that has occurred. 8 | */ 9 | data class Event( 10 | val from: String, 11 | val to: String, 12 | val eventType: String 13 | ) 14 | -------------------------------------------------------------------------------- /libraries/datasource/user/src/main/java/com/chesire/nekome/datasource/user/User.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.user 2 | 3 | /** 4 | * Possible results for accessing the user object from the [UserRepository]. 5 | */ 6 | sealed class User { 7 | 8 | /** 9 | * User has been found with a valid model. 10 | */ 11 | data class Found(val domain: UserDomain) : User() 12 | 13 | /** 14 | * No user has been found. 15 | */ 16 | object NotFound : User() 17 | } 18 | -------------------------------------------------------------------------------- /libraries/kitsu/library/src/main/java/com/chesire/nekome/kitsu/library/dto/AddResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.library.dto 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | /** 7 | * DTO from the Kitsu library add/update endpoint. 8 | */ 9 | @JsonClass(generateAdapter = true) 10 | data class AddResponseDto( 11 | @Json(name = "data") 12 | val data: DataDto, 13 | @Json(name = "included") 14 | val included: List 15 | ) 16 | -------------------------------------------------------------------------------- /libraries/kitsu/auth/src/main/java/com/chesire/nekome/kitsu/auth/dto/AuthResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.auth.dto 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | /** 7 | * DTO for responses from the Kitsu auth endpoints. 8 | */ 9 | @JsonClass(generateAdapter = true) 10 | data class AuthResponseDto( 11 | @Json(name = "access_token") 12 | val accessToken: String, 13 | @Json(name = "refresh_token") 14 | val refreshToken: String 15 | ) 16 | -------------------------------------------------------------------------------- /features/settings/src/main/java/com/chesire/nekome/app/settings/config/core/RetrieveUserUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.datasource.user.User 4 | import com.chesire.nekome.datasource.user.UserRepository 5 | import javax.inject.Inject 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | class RetrieveUserUseCase @Inject constructor(private val userRepository: UserRepository) { 9 | 10 | operator fun invoke(): Flow = userRepository.user 11 | } 12 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-GB/full_description.txt: -------------------------------------------------------------------------------- 1 | An ad free and open source application that uses Kitsu to keep your current anime and manga series progress up to date. 2 | 3 | Application features: 4 | * Add new anime/manga series to track 5 | * Update current series episode/chapter number 6 | * Change series status 7 | 8 | This app does NOT allow you to read or watch anything, this merely allows you to track your current progress. 9 | 10 | Note: A free account with Kitsu is required to use this application. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | An ad free and open source application that uses Kitsu to keep your current anime and manga series progress up to date. 2 | 3 | Application features: 4 | * Add new anime/manga series to track 5 | * Update current series episode/chapter number 6 | * Change series status 7 | 8 | This app does NOT allow you to read or watch anything, this merely allows you to track your current progress. 9 | 10 | Note: A free account with Kitsu is required to use this application. -------------------------------------------------------------------------------- /features/serieswidget/src/main/java/com/chesire/nekome/feature/serieswidget/SeriesWidgetEntryPoint.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.feature.serieswidget 2 | 3 | import com.chesire.nekome.feature.serieswidget.ui.SeriesWidgetViewModel 4 | import dagger.hilt.EntryPoint 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | 8 | @EntryPoint 9 | @InstallIn(SingletonComponent::class) 10 | interface SeriesWidgetEntryPoint { 11 | 12 | fun seriesWidgetViewModel(): SeriesWidgetViewModel 13 | } 14 | -------------------------------------------------------------------------------- /features/serieswidget/src/main/res/xml/series_widget_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/helpers/AuthProviderExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.helpers 2 | 3 | import com.chesire.nekome.datasource.auth.local.AuthProvider 4 | 5 | /** 6 | * Tells the [AuthProvider] that a user is logged in, used to skip login. 7 | */ 8 | fun AuthProvider.login() { 9 | accessToken = "fakeAccessToken" 10 | } 11 | 12 | /** 13 | * Tells the [AuthProvider] that a user is logged out, used to enter login. 14 | */ 15 | fun AuthProvider.logout() { 16 | accessToken = "" 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/ui/UIState.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.ui 2 | 3 | data class UIState( 4 | val isInitialized: Boolean, 5 | val userLoggedIn: Boolean, 6 | val defaultHomeScreen: String, 7 | val kickUserToLogin: Unit? 8 | ) { 9 | companion object { 10 | val empty = UIState( 11 | isInitialized = false, 12 | userLoggedIn = false, 13 | defaultHomeScreen = Screen.Anime.route, 14 | kickUserToLogin = null 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/collection/core/CurrentSortUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.core.preferences.flags.SortOption 5 | import javax.inject.Inject 6 | import kotlinx.coroutines.flow.first 7 | 8 | class CurrentSortUseCase @Inject constructor(private val pref: SeriesPreferences) { 9 | 10 | suspend operator fun invoke(): SortOption = pref.sort.first() 11 | } 12 | -------------------------------------------------------------------------------- /libraries/kitsu/auth/src/main/java/com/chesire/nekome/kitsu/auth/dto/RefreshTokenRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.auth.dto 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | /** 7 | * DTO to use when requesting to refresh the auth token. 8 | */ 9 | @JsonClass(generateAdapter = true) 10 | data class RefreshTokenRequestDto( 11 | @Json(name = "refresh_token") 12 | val refreshToken: String, 13 | @Json(name = "grant_type") 14 | val grantType: String = "refresh_token" 15 | ) 16 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/collection/core/CollectSeriesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesDomain 4 | import com.chesire.nekome.datasource.series.SeriesRepository 5 | import javax.inject.Inject 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | class CollectSeriesUseCase @Inject constructor(private val seriesRepo: SeriesRepository) { 9 | 10 | operator fun invoke(): Flow> = seriesRepo.getSeries() 11 | } 12 | -------------------------------------------------------------------------------- /libraries/core/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 8dp 7 | 16dp 8 | 32dp 9 | 10 | 8dp 11 | 12 | 8dp 13 | 14 | -------------------------------------------------------------------------------- /libraries/datasource/search/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.chesire.nekome.datasource.search" 8 | compileSdk = libs.versions.sdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation(project(":libraries:core")) 19 | 20 | implementation(libs.kotlin.result) 21 | } 22 | -------------------------------------------------------------------------------- /libraries/datasource/trending/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.chesire.nekome.datasource.trending" 8 | compileSdk = libs.versions.sdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation(project(":libraries:core")) 19 | 20 | implementation(libs.kotlin.result) 21 | } 22 | -------------------------------------------------------------------------------- /features/login/src/main/res/drawable/ic_lock.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /libraries/kitsu/src/main/java/com/chesire/nekome/kitsu/api/intermediaries/Links.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.api.intermediaries 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | /** 7 | * Class used as intermediary when parsing out response json. 8 | */ 9 | @JsonClass(generateAdapter = true) 10 | data class Links( 11 | @Json(name = "first") 12 | val first: String = "", 13 | @Json(name = "next") 14 | val next: String = "", 15 | @Json(name = "last") 16 | val last: String = "" 17 | ) 18 | -------------------------------------------------------------------------------- /features/login/src/main/res/drawable/ic_account_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/helpers/creation/UserDomain.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.helpers.creation 2 | 3 | import com.chesire.nekome.core.flags.Service 4 | import com.chesire.nekome.core.models.ImageModel 5 | import com.chesire.nekome.datasource.user.UserDomain 6 | 7 | /** 8 | * Creates a new [UserDomain]. 9 | */ 10 | fun createUserDomain() = 11 | UserDomain( 12 | userId = 1, 13 | name = "name", 14 | avatar = ImageModel.empty, 15 | coverImage = ImageModel.empty, 16 | service = Service.Kitsu 17 | ) 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/injection/LogoutExecutorModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import com.chesire.nekome.LogoutHandler 4 | import com.chesire.nekome.app.settings.config.LogoutExecutor 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.ViewModelComponent 9 | 10 | @Module 11 | @InstallIn(ViewModelComponent::class) 12 | abstract class LogoutExecutorModule { 13 | 14 | @Binds 15 | abstract fun bindExecutor(concrete: LogoutHandler): LogoutExecutor 16 | } 17 | -------------------------------------------------------------------------------- /features/search/src/main/java/com/chesire/nekome/app/search/search/ui/ViewAction.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.search.search.ui 2 | 3 | import com.chesire.nekome.app.search.search.core.model.SearchGroup 4 | 5 | sealed interface ViewAction { 6 | data class SearchGroupChanged(val newGroup: SearchGroup) : ViewAction 7 | data class SearchTextUpdated(val newSearchText: String) : ViewAction 8 | data object ExecuteSearch : ViewAction 9 | data class TrackSeries(val model: ResultModel) : ViewAction 10 | data object ErrorSnackbarObserved : ViewAction 11 | } 12 | -------------------------------------------------------------------------------- /libraries/kitsu/auth/src/main/java/com/chesire/nekome/kitsu/auth/dto/LoginRequestDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.auth.dto 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | /** 7 | * DTO to use when attempting to authorize as a user. 8 | */ 9 | @JsonClass(generateAdapter = true) 10 | data class LoginRequestDto( 11 | @Json(name = "username") 12 | val username: String, 13 | @Json(name = "password") 14 | val password: String, 15 | @Json(name = "grant_type") 16 | val grantType: String = "password" 17 | ) 18 | -------------------------------------------------------------------------------- /features/settings/src/main/java/com/chesire/nekome/app/settings/config/LogoutExecutor.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config 2 | 3 | /** 4 | * Interface to populate with a concrete to handle performing log out operations. 5 | */ 6 | interface LogoutExecutor { 7 | 8 | /** 9 | * Execute log out, clearing anything left over and resetting the application state. 10 | * 11 | * This needs to be called using a suspend function so clearing any data such as from databases 12 | * can be done safely. 13 | */ 14 | suspend fun executeLogout() 15 | } 16 | -------------------------------------------------------------------------------- /libraries/datasource/user/src/main/java/com/chesire/nekome/datasource/user/remote/UserApi.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.user.remote 2 | 3 | import com.chesire.nekome.core.models.ErrorDomain 4 | import com.chesire.nekome.datasource.user.UserDomain 5 | import com.github.michaelbull.result.Result 6 | 7 | /** 8 | * Methods relating to getting information about a user from the api. 9 | */ 10 | interface UserApi { 11 | 12 | /** 13 | * Executes request to get the user details. 14 | */ 15 | suspend fun getUserDetails(): Result 16 | } 17 | -------------------------------------------------------------------------------- /libraries/datasource/activity/src/main/java/com/chesire/nekome/datasource/activity/remote/ActivityApi.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.activity.remote 2 | 3 | import com.chesire.nekome.core.models.ErrorDomain 4 | import com.chesire.nekome.datasource.activity.ActivityDomain 5 | import com.github.michaelbull.result.Result 6 | 7 | /** 8 | * Methods relating to retrieving the users activity. 9 | */ 10 | interface ActivityApi { 11 | 12 | /** 13 | * Retrieve the users latest activity. 14 | */ 15 | suspend fun retrieveActivity(): Result, ErrorDomain> 16 | } 17 | -------------------------------------------------------------------------------- /features/search/src/main/java/com/chesire/nekome/app/search/search/core/RememberSearchGroupUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.search.search.core 2 | 3 | import com.chesire.nekome.app.search.search.core.model.SearchGroup 4 | import com.chesire.nekome.app.search.search.data.SearchPreferences 5 | import javax.inject.Inject 6 | 7 | class RememberSearchGroupUseCase @Inject constructor( 8 | private val searchPreferences: SearchPreferences 9 | ) { 10 | 11 | operator fun invoke(newSearchGroup: SearchGroup) { 12 | searchPreferences.lastSearchGroup = newSearchGroup.name 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /features/settings/src/main/java/com/chesire/nekome/app/settings/config/core/UpdateRateSeriesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import javax.inject.Inject 5 | import timber.log.Timber 6 | 7 | class UpdateRateSeriesUseCase @Inject constructor(private val pref: SeriesPreferences) { 8 | 9 | suspend operator fun invoke(newRateSeriesValue: Boolean) { 10 | Timber.i("Updating rate series to [$newRateSeriesValue]") 11 | pref.updateRateSeriesOnCompletion(newRateSeriesValue) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.useAndroidX=true 10 | org.gradle.configuration-cache=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | org.gradle.parallel=true 13 | -------------------------------------------------------------------------------- /features/settings/src/main/java/com/chesire/nekome/app/settings/config/core/UpdateThemeUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.ApplicationPreferences 4 | import com.chesire.nekome.core.preferences.flags.Theme 5 | import javax.inject.Inject 6 | import timber.log.Timber 7 | 8 | class UpdateThemeUseCase @Inject constructor(private val pref: ApplicationPreferences) { 9 | 10 | suspend operator fun invoke(newTheme: Theme) { 11 | Timber.i("Updating theme to [$newTheme]") 12 | pref.updateTheme(newTheme) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libraries/kitsu/trending/src/test/java/com/chesire/nekome/kitsu/trending/TrendingCreation.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.trending 2 | 3 | import com.chesire.nekome.core.flags.SeriesType 4 | import com.chesire.nekome.core.models.ImageModel 5 | import com.chesire.nekome.kitsu.trending.dto.TrendingItemDto 6 | 7 | /** 8 | * Create a [TrendingItemDto] for tests. 9 | */ 10 | fun createTrendingItemDto(type: SeriesType) = 11 | TrendingItemDto( 12 | 0, 13 | type, 14 | TrendingItemDto.Attributes( 15 | "canonicalTitle", 16 | ImageModel.empty 17 | ) 18 | ) 19 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/collection/core/UpdateSortUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.core.preferences.flags.SortOption 5 | import javax.inject.Inject 6 | import timber.log.Timber 7 | 8 | class UpdateSortUseCase @Inject constructor(private val pref: SeriesPreferences) { 9 | 10 | suspend operator fun invoke(newSortOption: SortOption) { 11 | Timber.d("Updating sorting to [$newSortOption]") 12 | pref.updateSort(newSortOption) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/injection/MockAuthModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import com.chesire.nekome.datasource.auth.remote.AuthApi 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.Reusable 7 | import dagger.hilt.components.SingletonComponent 8 | import dagger.hilt.testing.TestInstallIn 9 | import io.mockk.mockk 10 | 11 | @Module 12 | @TestInstallIn( 13 | components = [SingletonComponent::class], 14 | replaces = [AuthModule::class] 15 | ) 16 | class MockAuthModule { 17 | 18 | @Provides 19 | @Reusable 20 | fun provideApi() = mockk() 21 | } 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/injection/MockUserModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import com.chesire.nekome.datasource.user.remote.UserApi 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.Reusable 7 | import dagger.hilt.components.SingletonComponent 8 | import dagger.hilt.testing.TestInstallIn 9 | import io.mockk.mockk 10 | 11 | @Module 12 | @TestInstallIn( 13 | components = [SingletonComponent::class], 14 | replaces = [UserModule::class] 15 | ) 16 | class MockUserModule { 17 | 18 | @Provides 19 | @Reusable 20 | fun provideApi() = mockk() 21 | } 22 | -------------------------------------------------------------------------------- /libraries/kitsu/library/src/main/java/com/chesire/nekome/kitsu/library/dto/RetrieveResponseDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.library.dto 2 | 3 | import com.chesire.nekome.kitsu.api.intermediaries.Links 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | /** 8 | * DTO from the Kitsu library retrieve endpoint. 9 | */ 10 | @JsonClass(generateAdapter = true) 11 | data class RetrieveResponseDto( 12 | @Json(name = "data") 13 | val data: List, 14 | @Json(name = "included") 15 | val included: List?, 16 | @Json(name = "links") 17 | val links: Links 18 | ) 19 | -------------------------------------------------------------------------------- /libraries/datasource/search/src/main/java/com/chesire/nekome/datasource/search/SearchDomain.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.search 2 | 3 | import com.chesire.nekome.core.flags.SeriesType 4 | import com.chesire.nekome.core.flags.Subtype 5 | import com.chesire.nekome.core.models.ImageModel 6 | 7 | /** 8 | * Domain related to a singular searched item. 9 | */ 10 | data class SearchDomain( 11 | val id: Int, 12 | val type: SeriesType, 13 | val synopsis: String, 14 | val canonicalTitle: String, 15 | val otherTitles: Map, 16 | val subtype: Subtype, 17 | val posterImage: ImageModel 18 | ) 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/services/WidgetDataWorker.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.services 2 | 3 | import android.content.Context 4 | import androidx.glance.appwidget.updateAll 5 | import androidx.work.CoroutineWorker 6 | import androidx.work.WorkerParameters 7 | import com.chesire.nekome.feature.serieswidget.ui.SeriesWidget 8 | 9 | class WidgetDataWorker( 10 | private val context: Context, 11 | params: WorkerParameters 12 | ) : CoroutineWorker(context, params) { 13 | 14 | override suspend fun doWork(): Result { 15 | SeriesWidget().updateAll(context) 16 | return Result.success() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/injection/MockLibraryModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import com.chesire.nekome.datasource.series.remote.SeriesApi 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.Reusable 7 | import dagger.hilt.components.SingletonComponent 8 | import dagger.hilt.testing.TestInstallIn 9 | import io.mockk.mockk 10 | 11 | @Module 12 | @TestInstallIn( 13 | components = [SingletonComponent::class], 14 | replaces = [LibraryModule::class] 15 | ) 16 | class MockLibraryModule { 17 | 18 | @Provides 19 | @Reusable 20 | fun provideApi() = mockk() 21 | } 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/injection/MockSearchModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import com.chesire.nekome.datasource.search.remote.SearchApi 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.Reusable 7 | import dagger.hilt.components.SingletonComponent 8 | import dagger.hilt.testing.TestInstallIn 9 | import io.mockk.mockk 10 | 11 | @Module 12 | @TestInstallIn( 13 | components = [SingletonComponent::class], 14 | replaces = [SearchModule::class] 15 | ) 16 | class MockSearchModule { 17 | 18 | @Provides 19 | @Reusable 20 | fun provideApi() = mockk() 21 | } 22 | -------------------------------------------------------------------------------- /libraries/kitsu/activity/src/main/java/com/chesire/nekome/kitsu/activity/KitsuActivityService.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.activity 2 | 3 | import com.chesire.nekome.kitsu.activity.dto.RetrieveActivityDto 4 | import retrofit2.Response 5 | import retrofit2.http.GET 6 | 7 | /** 8 | * Constructed with Retrofit to interface with the Kitsu API for queries related to user activity. 9 | */ 10 | interface KitsuActivityService { 11 | 12 | /** 13 | * Retrieves the recent library events for a user. 14 | */ 15 | @GET("api/edge/library-events") 16 | suspend fun retrieveLibraryEvents(): Response 17 | } 18 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/item/core/RetrieveItemUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.item.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesDomain 4 | import com.chesire.nekome.datasource.series.SeriesRepository 5 | import javax.inject.Inject 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | 9 | class RetrieveItemUseCase @Inject constructor(private val seriesRepo: SeriesRepository) { 10 | 11 | suspend operator fun invoke(userSeriesId: Int): SeriesDomain = withContext(Dispatchers.IO) { 12 | seriesRepo.getSeries(userSeriesId) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libraries/database/src/main/java/com/chesire/nekome/database/entity/UserEntity.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.chesire.nekome.core.flags.Service 6 | import com.chesire.nekome.core.models.ImageModel 7 | import com.squareup.moshi.JsonClass 8 | 9 | /** 10 | * Data for a singular user entity. 11 | */ 12 | @Entity 13 | @JsonClass(generateAdapter = true) 14 | data class UserEntity( 15 | val userId: Int, 16 | val name: String, 17 | val avatar: ImageModel, 18 | val coverImage: ImageModel, 19 | @PrimaryKey 20 | val service: Service 21 | ) 22 | -------------------------------------------------------------------------------- /features/settings/src/main/java/com/chesire/nekome/app/settings/config/core/UpdateImageQualityUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.core.preferences.flags.ImageQuality 5 | import javax.inject.Inject 6 | import timber.log.Timber 7 | 8 | class UpdateImageQualityUseCase @Inject constructor(private val pref: SeriesPreferences) { 9 | 10 | suspend operator fun invoke(newImageQuality: ImageQuality) { 11 | Timber.i("Updating image quality to [$newImageQuality]") 12 | pref.updateImageQuality(newImageQuality) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /features/login/src/main/java/com/chesire/nekome/app/login/credentials/core/PopulateUserDetailsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.login.credentials.core 2 | 3 | import com.chesire.nekome.datasource.user.UserRepository 4 | import com.github.michaelbull.result.Result 5 | import javax.inject.Inject 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | 9 | class PopulateUserDetailsUseCase @Inject constructor(private val user: UserRepository) { 10 | 11 | suspend operator fun invoke(): Result { 12 | return withContext(Dispatchers.IO) { 13 | user.refreshUser() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /features/settings/src/main/java/com/chesire/nekome/app/settings/config/core/UpdateTitleLanguageUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.core.preferences.flags.TitleLanguage 5 | import javax.inject.Inject 6 | import timber.log.Timber 7 | 8 | class UpdateTitleLanguageUseCase @Inject constructor(private val pref: SeriesPreferences) { 9 | 10 | suspend operator fun invoke(newTitleLanguage: TitleLanguage) { 11 | Timber.i("Updating title language to [$newTitleLanguage]") 12 | pref.updateTitleLanguage(newTitleLanguage) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libraries/core/src/test/java/com/chesire/nekome/core/flags/SeriesTypeTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.flags 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class SeriesTypeTests { 7 | @Test 8 | fun `forId '-1' returns SeriesType#Unknown`() { 9 | assertEquals(SeriesType.Unknown, SeriesType.forId(-1)) 10 | } 11 | 12 | @Test 13 | fun `forId '0' returns SeriesType#Anime`() { 14 | assertEquals(SeriesType.Anime, SeriesType.forId(0)) 15 | } 16 | 17 | @Test 18 | fun `forId '1' returns SeriesType#Manga`() { 19 | assertEquals(SeriesType.Manga, SeriesType.forId(1)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/helpers/UserDaoExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.helpers 2 | 3 | import com.chesire.nekome.core.models.ImageModel 4 | import com.chesire.nekome.database.dao.UserDao 5 | import com.chesire.nekome.testing.createUserEntity 6 | import kotlinx.coroutines.runBlocking 7 | 8 | /** 9 | * Creates and pushes a new user into the [UserDao]. 10 | */ 11 | fun UserDao.createTestUser( 12 | userId: Int = 0, 13 | name: String = "Nekome", 14 | avatar: ImageModel = ImageModel.empty, 15 | coverImage: ImageModel = ImageModel.empty 16 | ) = runBlocking { 17 | insert(createUserEntity(userId, name, avatar, coverImage)) 18 | } 19 | -------------------------------------------------------------------------------- /features/serieswidget/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/TestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.test.runner.AndroidJUnitRunner 6 | import dagger.hilt.android.testing.HiltTestApplication 7 | 8 | /** 9 | * Overridden runner that forces the application object to be [HiltTestApplication]. 10 | */ 11 | @Suppress("unused") 12 | class TestRunner : AndroidJUnitRunner() { 13 | override fun newApplication( 14 | cl: ClassLoader?, 15 | className: String?, 16 | context: Context? 17 | ): Application = super.newApplication(cl, HiltTestApplication::class.java.name, context) 18 | } 19 | -------------------------------------------------------------------------------- /libraries/datasource/auth/src/main/java/com/chesire/nekome/datasource/auth/remote/AuthApi.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.auth.remote 2 | 3 | import com.github.michaelbull.result.Result 4 | 5 | /** 6 | * Methods relating to authorizing as a user. 7 | */ 8 | interface AuthApi { 9 | 10 | /** 11 | * Logs into the service using a [username] and [password], returning the success state. 12 | */ 13 | suspend fun login(username: String, password: String): Result 14 | 15 | /** 16 | * Refreshes any current auth credentials. 17 | */ 18 | suspend fun refresh(refreshToken: String): Result 19 | } 20 | -------------------------------------------------------------------------------- /features/search/src/main/java/com/chesire/nekome/app/search/search/core/RetrieveUserSeriesIdsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.search.search.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesRepository 4 | import javax.inject.Inject 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.mapLatest 7 | 8 | class RetrieveUserSeriesIdsUseCase @Inject constructor(private val seriesRepo: SeriesRepository) { 9 | 10 | operator fun invoke(): Flow> { 11 | return seriesRepo 12 | .getSeries() 13 | .mapLatest { seriesDomains -> 14 | seriesDomains.map { it.id } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /features/settings/src/main/java/com/chesire/nekome/app/settings/config/core/UpdateDefaultHomeScreenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.ApplicationPreferences 4 | import com.chesire.nekome.core.preferences.flags.HomeScreenOptions 5 | import javax.inject.Inject 6 | import timber.log.Timber 7 | 8 | class UpdateDefaultHomeScreenUseCase @Inject constructor(private val pref: ApplicationPreferences) { 9 | 10 | suspend operator fun invoke(newHomeScreen: HomeScreenOptions) { 11 | Timber.i("Updating default home screen to [$newHomeScreen]") 12 | pref.updateDefaultHomeScreen(newHomeScreen) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /features/settings/src/main/java/com/chesire/nekome/app/settings/config/core/UpdateDefaultSeriesStateUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.flags.UserSeriesStatus 4 | import com.chesire.nekome.core.preferences.ApplicationPreferences 5 | import javax.inject.Inject 6 | import timber.log.Timber 7 | 8 | class UpdateDefaultSeriesStateUseCase @Inject constructor(private val pref: ApplicationPreferences) { 9 | 10 | suspend operator fun invoke(newDefaultState: UserSeriesStatus) { 11 | Timber.i("Updating default series status to [$newDefaultState]") 12 | pref.updateDefaultSeriesState(newDefaultState) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libraries/datasource/activity/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.chesire.nekome.datasource.activity" 8 | compileSdk = libs.versions.sdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation(project(":libraries:core")) 19 | 20 | implementation(libs.google.hilt.android) 21 | implementation(libs.kotlin.coroutines.android) 22 | implementation(libs.kotlin.coroutines.core) 23 | implementation(libs.kotlin.result) 24 | } 25 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/item/ui/ViewAction.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.item.ui 2 | 3 | import com.chesire.nekome.core.flags.UserSeriesStatus 4 | 5 | sealed interface ViewAction { 6 | data class OnDeleteResult(val result: Boolean) : ViewAction 7 | data class ProgressChanged(val newProgress: String) : ViewAction 8 | data class RatingChanged(val newRating: Float) : ViewAction 9 | data class SeriesStatusChanged(val newSeriesStatus: UserSeriesStatus) : ViewAction 10 | object ConfirmPressed : ViewAction 11 | object DeletePressed : ViewAction 12 | object SnackbarObserved : ViewAction 13 | object FinishScreenObserved : ViewAction 14 | } 15 | -------------------------------------------------------------------------------- /libraries/database/src/main/java/com/chesire/nekome/database/converters/SubtypeConverter.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import androidx.room.TypeConverter 4 | import com.chesire.nekome.core.flags.Subtype 5 | 6 | /** 7 | * Converter for [Subtype] -> [String]. 8 | * 9 | * For saving an [Subtype] into the database. 10 | */ 11 | class SubtypeConverter { 12 | /** 13 | * Converts a [Subtype] into a [String]. 14 | */ 15 | @TypeConverter 16 | fun fromSubtype(type: Subtype): String = type.name 17 | 18 | /** 19 | * Converts a [String] into a [Subtype]. 20 | */ 21 | @TypeConverter 22 | fun toSubtype(type: String): Subtype = Subtype.valueOf(type) 23 | } 24 | -------------------------------------------------------------------------------- /libraries/kitsu/user/src/main/java/com/chesire/nekome/kitsu/user/KitsuUserService.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.user 2 | 3 | import com.chesire.nekome.kitsu.user.dto.UserResponseDto 4 | import retrofit2.Response 5 | import retrofit2.http.GET 6 | 7 | /** 8 | * Constructed using Retrofit to interface with the Kitsu API for queries related to a user. 9 | */ 10 | interface KitsuUserService { 11 | 12 | /** 13 | * Gets the details about a user. 14 | * This requires an auth token set in the header to get the correct user. 15 | */ 16 | @GET("api/edge/users?filter[self]=true&fields[users]=id,name,avatar,coverImage") 17 | suspend fun getUserDetailsAsync(): Response 18 | } 19 | -------------------------------------------------------------------------------- /libraries/database/src/main/java/com/chesire/nekome/database/converters/ServiceConverter.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import androidx.room.TypeConverter 4 | import com.chesire.nekome.core.flags.Service 5 | 6 | /** 7 | * Converter for [Service] -> [String]. 8 | * 9 | * For saving an [Service] into the database. 10 | */ 11 | class ServiceConverter { 12 | /** 13 | * Converts a [Service] into a [String]. 14 | */ 15 | @TypeConverter 16 | fun fromService(service: Service): String = service.name 17 | 18 | /** 19 | * Converts a [String] into a [Service]. 20 | */ 21 | @TypeConverter 22 | fun toService(service: String): Service = Service.valueOf(service) 23 | } 24 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | 9 | # Uncomment the line if you want fastlane to automatically update itself 10 | # update_fastlane 11 | 12 | default_platform(:android) 13 | 14 | platform :android do 15 | desc "Deploy a new Alpha Build to the Play Store" 16 | lane :alpha do 17 | gradle( 18 | task: 'clean assemble', 19 | build_type: 'release' 20 | ) 21 | upload_to_play_store( 22 | track: 'alpha', 23 | #mapping: '' - Will add this later 24 | ) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /libraries/kitsu/search/src/test/java/com/chesire/nekome/kitsu/search/SearchCreation.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.search 2 | 3 | import com.chesire.nekome.core.flags.SeriesType 4 | import com.chesire.nekome.core.flags.Subtype 5 | import com.chesire.nekome.core.models.ImageModel 6 | import com.chesire.nekome.kitsu.search.dto.SearchItemDto 7 | 8 | /** 9 | * Create a [SearchItemDto] for tests. 10 | */ 11 | fun createSearchItemDto(type: SeriesType) = 12 | SearchItemDto( 13 | 0, 14 | type, 15 | SearchItemDto.Attributes( 16 | "synopsis", 17 | mapOf("en" to "en"), 18 | "canonicalTitle", 19 | Subtype.Unknown, 20 | ImageModel.empty 21 | ) 22 | ) 23 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/collection/core/UpdateFiltersUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.core.flags.UserSeriesStatus 4 | import com.chesire.nekome.core.preferences.SeriesPreferences 5 | import javax.inject.Inject 6 | import timber.log.Timber 7 | 8 | class UpdateFiltersUseCase @Inject constructor(private val pref: SeriesPreferences) { 9 | 10 | suspend operator fun invoke(newFilter: Map) { 11 | Timber.d("Updating filters to [$newFilter]") 12 | pref.updateFilter( 13 | newFilter 14 | .map { it.key.index to it.value } 15 | .toMap() 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /libraries/database/src/main/java/com/chesire/nekome/database/converters/SeriesTypeConverter.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import androidx.room.TypeConverter 4 | import com.chesire.nekome.core.flags.SeriesType 5 | 6 | /** 7 | * Converter for [SeriesType] -> [String]. 8 | * 9 | * For saving an [SeriesType] into the database. 10 | */ 11 | class SeriesTypeConverter { 12 | /** 13 | * Converts a [SeriesType] into a [String]. 14 | */ 15 | @TypeConverter 16 | fun fromSeriesType(type: SeriesType): String = type.name 17 | 18 | /** 19 | * Converts a [String] into a [SeriesType]. 20 | */ 21 | @TypeConverter 22 | fun toSeriesType(type: String): SeriesType = SeriesType.valueOf(type) 23 | } 24 | -------------------------------------------------------------------------------- /libraries/datasource/search/src/main/java/com/chesire/nekome/datasource/search/remote/SearchApi.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.search.remote 2 | 3 | import com.chesire.nekome.core.models.ErrorDomain 4 | import com.chesire.nekome.datasource.search.SearchDomain 5 | import com.github.michaelbull.result.Result 6 | 7 | /** 8 | * Methods relating to searching for series. 9 | */ 10 | interface SearchApi { 11 | 12 | /** 13 | * Search for the anime series [title]. 14 | */ 15 | suspend fun searchForAnime(title: String): Result, ErrorDomain> 16 | 17 | /** 18 | * Search for the manga series [title]. 19 | */ 20 | suspend fun searchForManga(title: String): Result, ErrorDomain> 21 | } 22 | -------------------------------------------------------------------------------- /libraries/core/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/injection/WorkerModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import android.content.Context 4 | import androidx.work.WorkManager 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | 11 | /** 12 | * Dagger [Module] to provide the systems [WorkManager]. 13 | */ 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object WorkerModule { 17 | 18 | /** 19 | * Provides a [WorkManager] instance to the dependency graph. 20 | */ 21 | @Provides 22 | fun providesWorkManager(@ApplicationContext context: Context) = WorkManager.getInstance(context) 23 | } 24 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/collection/core/CurrentFiltersUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.core.flags.UserSeriesStatus 4 | import com.chesire.nekome.core.preferences.SeriesPreferences 5 | import javax.inject.Inject 6 | import kotlinx.coroutines.flow.first 7 | 8 | class CurrentFiltersUseCase @Inject constructor(private val pref: SeriesPreferences) { 9 | 10 | suspend operator fun invoke(): Map = 11 | pref 12 | .filter 13 | .first() 14 | .map { UserSeriesStatus.getFromIndex(it.key.toString()) to it.value } 15 | .filterNot { it.first == UserSeriesStatus.Unknown } 16 | .toMap() 17 | } 18 | -------------------------------------------------------------------------------- /libraries/datasource/trending/src/main/java/com/chesire/nekome/datasource/trending/remote/TrendingApi.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.trending.remote 2 | 3 | import com.chesire.nekome.core.models.ErrorDomain 4 | import com.chesire.nekome.datasource.trending.TrendingDomain 5 | import com.github.michaelbull.result.Result 6 | 7 | /** 8 | * Methods relating to getting information about trending topics. 9 | */ 10 | interface TrendingApi { 11 | 12 | /** 13 | * Retrieves the current trending anime. 14 | */ 15 | suspend fun getTrendingAnime(): Result, ErrorDomain> 16 | 17 | /** 18 | * Retrieves the current trending manga. 19 | */ 20 | suspend fun getTrendingManga(): Result, ErrorDomain> 21 | } 22 | -------------------------------------------------------------------------------- /libraries/database/src/main/java/com/chesire/nekome/database/converters/SeriesStatusConverter.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import androidx.room.TypeConverter 4 | import com.chesire.nekome.core.flags.SeriesStatus 5 | 6 | /** 7 | * Converter for [SeriesStatus] -> [String]. 8 | * 9 | * For saving an [SeriesStatus] into the database. 10 | */ 11 | class SeriesStatusConverter { 12 | /** 13 | * Converts a [SeriesStatus] into a [String]. 14 | */ 15 | @TypeConverter 16 | fun fromSeriesStatus(status: SeriesStatus): String = status.name 17 | 18 | /** 19 | * Converts a [String] into a [SeriesStatus]. 20 | */ 21 | @TypeConverter 22 | fun toSeriesStatus(status: String): SeriesStatus = SeriesStatus.valueOf(status) 23 | } 24 | -------------------------------------------------------------------------------- /core/preferences/src/main/java/com/chesire/nekome/core/preferences/flags/ImageQuality.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.preferences.flags 2 | 3 | import androidx.annotation.StringRes 4 | import com.chesire.nekome.resources.StringResource 5 | 6 | /** 7 | * Options available for the quality of the images. 8 | */ 9 | enum class ImageQuality(val index: Int, @StringRes val stringId: Int) { 10 | Low(0, StringResource.image_quality_low), 11 | Medium(1, StringResource.image_quality_medium), 12 | High(2, StringResource.image_quality_high); 13 | 14 | companion object { 15 | 16 | /** 17 | * Get [ImageQuality] for its given [index]. 18 | */ 19 | fun forIndex(index: Int): ImageQuality = values().find { it.index == index } ?: Low 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /features/login/src/main/java/com/chesire/nekome/app/login/syncing/core/SyncSeriesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.login.syncing.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesRepository 4 | import javax.inject.Inject 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.async 7 | import kotlinx.coroutines.awaitAll 8 | import kotlinx.coroutines.withContext 9 | 10 | class SyncSeriesUseCase @Inject constructor(private val seriesRepo: SeriesRepository) { 11 | 12 | suspend operator fun invoke() = withContext(Dispatchers.IO) { 13 | val animeJob = async(Dispatchers.IO) { seriesRepo.refreshAnime() } 14 | val mangaJob = async(Dispatchers.IO) { seriesRepo.refreshManga() } 15 | 16 | awaitAll(animeJob, mangaJob) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/binders/UserProviderBinder.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.binders 2 | 3 | import com.chesire.nekome.datasource.series.UserProvider 4 | import com.chesire.nekome.datasource.user.UserRepository 5 | import javax.inject.Inject 6 | 7 | /** 8 | * Provides a concrete class to bind the [UserProvider] interface. 9 | */ 10 | class UserProviderBinder @Inject constructor( 11 | private val userRepository: UserRepository 12 | ) : UserProvider { 13 | 14 | override suspend fun provideUserId(): UserProvider.UserIdResult { 15 | val id = userRepository.retrieveUserId() 16 | return if (id == null) { 17 | UserProvider.UserIdResult.Failure 18 | } else { 19 | UserProvider.UserIdResult.Success(id) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /features/search/src/main/java/com/chesire/nekome/app/search/search/data/SearchPreferences.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.search.search.data 2 | 3 | import android.content.SharedPreferences 4 | import androidx.core.content.edit 5 | import javax.inject.Inject 6 | 7 | /** 8 | * Wrapper around [SharedPreferences] to store settings or items related to Search. 9 | */ 10 | data class SearchPreferences @Inject constructor(private val sharedPreferences: SharedPreferences) { 11 | 12 | var lastSearchGroup: String 13 | get() = sharedPreferences.getString(LAST_SEARCH_GROUP, "") ?: "" 14 | set(value) = sharedPreferences.edit { putString(LAST_SEARCH_GROUP, value) } 15 | 16 | companion object { 17 | private const val LAST_SEARCH_GROUP = "preference.last_search_group" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libraries/database/src/test/java/com/chesire/nekome/database/converters/ServiceConverterTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import com.chesire.nekome.core.flags.Service 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class ServiceConverterTests { 8 | @Test 9 | fun `fromService converts to enum name from Service`() { 10 | val converter = ServiceConverter() 11 | Service.values().forEach { 12 | assertEquals(it.name, converter.fromService(it)) 13 | } 14 | } 15 | 16 | @Test 17 | fun `toService converts to Service from name`() { 18 | val converter = ServiceConverter() 19 | Service.values().forEach { 20 | assertEquals(it, converter.toService(it.name)) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libraries/database/src/test/java/com/chesire/nekome/database/converters/SubtypeConverterTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import com.chesire.nekome.core.flags.Subtype 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class SubtypeConverterTests { 8 | @Test 9 | fun `fromSubtype converts to enum name from Subtype`() { 10 | val converter = SubtypeConverter() 11 | Subtype.values().forEach { 12 | assertEquals(it.name, converter.fromSubtype(it)) 13 | } 14 | } 15 | 16 | @Test 17 | fun `toSubtype converts to Subtype from name`() { 18 | val converter = SubtypeConverter() 19 | Subtype.values().forEach { 20 | assertEquals(it, converter.toSubtype(it.name)) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/item/core/GetImageUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.item.core 2 | 3 | import com.chesire.nekome.core.models.ImageModel 4 | import com.chesire.nekome.core.preferences.SeriesPreferences 5 | import com.chesire.nekome.core.preferences.flags.ImageQuality 6 | import javax.inject.Inject 7 | import kotlinx.coroutines.flow.first 8 | 9 | class GetImageUseCase @Inject constructor(private val pref: SeriesPreferences) { 10 | 11 | suspend operator fun invoke(images: ImageModel): String { 12 | return when (pref.imageQuality.first()) { 13 | ImageQuality.Low -> images.smallest?.url 14 | ImageQuality.Medium -> images.middlest?.url 15 | ImageQuality.High -> images.largest?.url 16 | } ?: "" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /features/serieswidget/src/main/java/com/chesire/nekome/feature/serieswidget/ui/DomainMapper.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.feature.serieswidget.ui 2 | 3 | import com.chesire.nekome.datasource.series.SeriesDomain 4 | import javax.inject.Inject 5 | 6 | class DomainMapper @Inject constructor() { 7 | 8 | fun toSeries(domain: SeriesDomain): Series { 9 | return Series( 10 | userId = domain.userId, 11 | title = domain.title, 12 | progress = buildProgress(domain.progress, domain.totalLength), 13 | isUpdating = false 14 | ) 15 | } 16 | 17 | private fun buildProgress(progress: Int, totalLength: Int): String { 18 | val maxLengthString = if (totalLength == 0) "-" else totalLength 19 | return "$progress / $maxLengthString" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /features/login/src/main/java/com/chesire/nekome/app/login/syncing/core/RetrieveAvatarUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.login.syncing.core 2 | 3 | import com.chesire.nekome.datasource.user.User 4 | import com.chesire.nekome.datasource.user.UserRepository 5 | import javax.inject.Inject 6 | import kotlinx.coroutines.flow.filterIsInstance 7 | import kotlinx.coroutines.flow.firstOrNull 8 | import kotlinx.coroutines.flow.map 9 | 10 | class RetrieveAvatarUseCase @Inject constructor(private val userRepository: UserRepository) { 11 | 12 | suspend operator fun invoke(): String { 13 | return userRepository 14 | .user 15 | .filterIsInstance() 16 | .map { user -> user.domain.avatar.largest?.url } 17 | .firstOrNull() 18 | ?: "" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libraries/kitsu/trending/src/main/java/com/chesire/nekome/kitsu/trending/KitsuTrendingService.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.trending 2 | 3 | import com.chesire.nekome.kitsu.trending.dto.TrendingResponseDto 4 | import retrofit2.Response 5 | import retrofit2.http.GET 6 | 7 | /** 8 | * Constructed using Retrofit to interface with the Kitsu API for queries related to trending. 9 | */ 10 | interface KitsuTrendingService { 11 | 12 | /** 13 | * Gets the top trending Anime from Kitsu. 14 | */ 15 | @GET("api/edge/trending/anime") 16 | suspend fun getTrendingAnimeAsync(): Response 17 | 18 | /** 19 | * Gets the top trending Manga from Kitsu. 20 | */ 21 | @GET("api/edge/trending/manga") 22 | suspend fun getTrendingMangaAsync(): Response 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: Master 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: set up JDK 17 13 | uses: actions/setup-java@v4 14 | with: 15 | distribution: 'adopt' 16 | java-version: 17 17 | - name: setup gradle cache 18 | uses: gradle/actions/setup-gradle@v3 19 | with: 20 | cache-read-only: false 21 | - name: run assemble 22 | run: ./gradlew assemble 23 | - name: run kover 24 | run: ./gradlew koverXmlReport 25 | - uses: codecov/codecov-action@v4 26 | - name: run lint 27 | run: ./gradlew :app:lint 28 | - name: run detektCheck 29 | run: ./gradlew detektCheck 30 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/item/core/DeleteItemUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.item.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesRepository 4 | import com.github.michaelbull.result.Result 5 | import com.github.michaelbull.result.mapError 6 | import javax.inject.Inject 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.withContext 9 | 10 | class DeleteItemUseCase @Inject constructor(private val seriesRepo: SeriesRepository) { 11 | 12 | suspend operator fun invoke(userSeriesId: Int): Result { 13 | return withContext(Dispatchers.IO) { 14 | val series = seriesRepo.getSeries(userSeriesId) 15 | 16 | seriesRepo.deleteSeries(series) 17 | .mapError { } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libraries/database/src/main/java/com/chesire/nekome/database/converters/UserSeriesStatusConverter.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import androidx.room.TypeConverter 4 | import com.chesire.nekome.core.flags.UserSeriesStatus 5 | 6 | /** 7 | * Converter for [UserSeriesStatus] -> [String]. 8 | * 9 | * For saving an [UserSeriesStatus] into the database. 10 | */ 11 | class UserSeriesStatusConverter { 12 | /** 13 | * Converts a [UserSeriesStatus] into a [String]. 14 | */ 15 | @TypeConverter 16 | fun fromUserSeriesStatus(status: UserSeriesStatus): String = status.name 17 | 18 | /** 19 | * Converts a [String] into a [UserSeriesStatus]. 20 | */ 21 | @TypeConverter 22 | fun toUserSeriesStatus(status: String): UserSeriesStatus = UserSeriesStatus.valueOf(status) 23 | } 24 | -------------------------------------------------------------------------------- /libraries/datasource/series/src/main/java/com/chesire/nekome/datasource/series/UserProvider.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.series 2 | 3 | /** 4 | * Provider for accessing the information for a user. 5 | */ 6 | interface UserProvider { 7 | /** 8 | * Executes an async command to acquire the users id. 9 | */ 10 | suspend fun provideUserId(): UserIdResult 11 | 12 | /** 13 | * Represents different result states that [provideUserId] can provide. 14 | */ 15 | sealed class UserIdResult { 16 | /** 17 | * Success state containing the user id. 18 | */ 19 | data class Success(val id: Int) : UserIdResult() 20 | 21 | /** 22 | * Failure state when id could not be retrieved. 23 | */ 24 | object Failure : UserIdResult() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## Android 19 | ### android alpha 20 | ``` 21 | fastlane android alpha 22 | ``` 23 | Deploy a new Alpha Build to the Play Store 24 | 25 | ---- 26 | 27 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 28 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 29 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 30 | -------------------------------------------------------------------------------- /libraries/database/src/test/java/com/chesire/nekome/database/converters/SeriesTypeConverterTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import com.chesire.nekome.core.flags.SeriesType 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class SeriesTypeConverterTests { 8 | @Test 9 | fun `fromSeriesType converts to enum name from SeriesType`() { 10 | val converter = SeriesTypeConverter() 11 | SeriesType.values().forEach { 12 | assertEquals(it.name, converter.fromSeriesType(it)) 13 | } 14 | } 15 | 16 | @Test 17 | fun `toSeriesType converts to SeriesType from name`() { 18 | val converter = SeriesTypeConverter() 19 | SeriesType.values().forEach { 20 | assertEquals(it, converter.toSeriesType(it.name)) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /testing/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /libraries/kitsu/src/main/java/com/chesire/nekome/kitsu/ResponseParsing.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu 2 | 3 | import com.chesire.nekome.core.models.ErrorDomain 4 | import com.chesire.nekome.datasource.auth.AuthException 5 | import java.net.UnknownHostException 6 | import retrofit2.Response 7 | 8 | /** 9 | * Converts the current [Response] into an [ErrorDomain]. 10 | */ 11 | fun Response.asError(): ErrorDomain = ErrorDomain(errorBody()?.string() ?: message(), code()) 12 | 13 | /** 14 | * Parses out the [Exception] providing an [ErrorDomain] for use elsewhere. 15 | */ 16 | fun Exception.parse(): ErrorDomain { 17 | return when (this) { 18 | is UnknownHostException -> ErrorDomain.couldNotReach 19 | is AuthException -> ErrorDomain.couldNotRefresh 20 | else -> ErrorDomain.badRequest.copy(message = toString()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /features/login/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /features/search/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /features/series/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/collection/ui/ViewAction.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.ui 2 | 3 | import com.chesire.nekome.core.preferences.flags.SortOption 4 | 5 | sealed interface ViewAction { 6 | object PerformSeriesRefresh : ViewAction 7 | data class SeriesPressed(val series: Series) : ViewAction 8 | object SeriesNavigationObserved : ViewAction 9 | data class IncrementSeriesPressed(val series: Series) : ViewAction 10 | data class IncrementSeriesWithRating(val series: Series, val rating: Int?) : ViewAction 11 | object SortPressed : ViewAction 12 | data class PerformSort(val option: SortOption?) : ViewAction 13 | object FilterPressed : ViewAction 14 | data class PerformFilter(val filters: List?) : ViewAction 15 | object ErrorSnackbarObserved : ViewAction 16 | } 17 | -------------------------------------------------------------------------------- /features/settings/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /libraries/database/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /libraries/datasource/activity/src/main/java/com/chesire/nekome/datasource/activity/local/ActivityLocalDataStorage.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.activity.local 2 | 3 | import com.chesire.nekome.datasource.activity.ActivityDomain 4 | import javax.inject.Inject 5 | import javax.inject.Singleton 6 | 7 | /** 8 | * Local data storage for the users activity. 9 | * This will cache the data in memory, as it doesn't need to have long term storage. 10 | */ 11 | @Singleton // Keep this file as a singleton so its memory persistent. 12 | class ActivityLocalDataStorage @Inject constructor() { 13 | var cachedActivityItems: List = emptyList() 14 | 15 | /** 16 | * Sets a new cache of data into the local cache. 17 | */ 18 | fun setNewCache(newCache: List) { 19 | cachedActivityItems = newCache 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libraries/kitsu/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /libraries/datasource/auth/src/main/java/com/chesire/nekome/datasource/auth/remote/AuthInjectionInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.auth.remote 2 | 3 | import com.chesire.nekome.datasource.auth.AccessTokenRepository 4 | import javax.inject.Inject 5 | import okhttp3.Interceptor 6 | import okhttp3.Response 7 | 8 | /** 9 | * Interceptor to push the authorization header into api requests. 10 | */ 11 | class AuthInjectionInterceptor @Inject constructor( 12 | private val repo: AccessTokenRepository 13 | ) : Interceptor { 14 | 15 | override fun intercept(chain: Interceptor.Chain): Response { 16 | val authenticatedRequest = chain.request() 17 | .newBuilder() 18 | .header("Authorization", "Bearer ${repo.accessToken}") 19 | .build() 20 | 21 | return chain.proceed(authenticatedRequest) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/item/core/BuildTitleUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.item.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.core.preferences.flags.TitleLanguage 5 | import com.chesire.nekome.datasource.series.SeriesDomain 6 | import javax.inject.Inject 7 | import kotlinx.coroutines.flow.first 8 | 9 | class BuildTitleUseCase @Inject constructor(private val pref: SeriesPreferences) { 10 | 11 | suspend operator fun invoke(series: SeriesDomain): String { 12 | return when (val titleLanguage = pref.titleLanguage.first()) { 13 | TitleLanguage.Canonical -> series.title 14 | else -> series.otherTitles[titleLanguage.key] 15 | .takeIf { !it.isNullOrBlank() } 16 | ?: series.title 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libraries/database/src/test/java/com/chesire/nekome/database/converters/SeriesStatusConverterTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import com.chesire.nekome.core.flags.SeriesStatus 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class SeriesStatusConverterTests { 8 | @Test 9 | fun `fromSeriesStatus converts to enum name from SeriesStatus`() { 10 | val converter = SeriesStatusConverter() 11 | SeriesStatus.values().forEach { 12 | assertEquals(it.name, converter.fromSeriesStatus(it)) 13 | } 14 | } 15 | 16 | @Test 17 | fun `toSeriesStatus converts to SeriesStatus from name`() { 18 | val converter = SeriesStatusConverter() 19 | SeriesStatus.values().forEach { 20 | assertEquals(it, converter.toSeriesStatus(it.name)) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /features/login/src/test/java/com/chesire/nekome/app/login/credentials/core/ClearCredentialsUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.login.credentials.core 2 | 3 | import com.chesire.nekome.datasource.auth.AccessTokenRepository 4 | import io.mockk.clearAllMocks 5 | import io.mockk.mockk 6 | import io.mockk.verify 7 | import org.junit.Before 8 | import org.junit.Test 9 | 10 | class ClearCredentialsUseCaseTest { 11 | 12 | private val authRepo = mockk(relaxed = true) 13 | private lateinit var clearCredentials: ClearCredentialsUseCase 14 | 15 | @Before 16 | fun setup() { 17 | clearAllMocks() 18 | clearCredentials = ClearCredentialsUseCase(authRepo) 19 | } 20 | 21 | @Test 22 | fun `UseCase invoke clears the auth repo`() { 23 | clearCredentials() 24 | 25 | verify { authRepo.clear() } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libraries/kitsu/user/src/main/java/com/chesire/nekome/kitsu/user/dto/UserItemDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.user.dto 2 | 3 | import com.chesire.nekome.core.models.ImageModel 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | /** 8 | * DTO from the Kitsu user endpoint. 9 | */ 10 | @JsonClass(generateAdapter = true) 11 | data class UserItemDto( 12 | @Json(name = "id") 13 | val id: Int, 14 | @Json(name = "attributes") 15 | val attributes: Attributes 16 | ) { 17 | /** 18 | * Attributes of the user item. 19 | */ 20 | @JsonClass(generateAdapter = true) 21 | data class Attributes( 22 | @Json(name = "name") 23 | val name: String, 24 | @Json(name = "avatar") 25 | val avatar: ImageModel?, 26 | @Json(name = "coverImage") 27 | val coverImage: ImageModel? 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /core/preferences/src/main/java/com/chesire/nekome/core/preferences/flags/SortOption.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.preferences.flags 2 | 3 | import androidx.annotation.StringRes 4 | import com.chesire.nekome.resources.StringResource 5 | 6 | /** 7 | * Options available for when sorting the series list is performed. 8 | */ 9 | enum class SortOption(val index: Int, @StringRes val stringId: Int) { 10 | Default(0, StringResource.sort_by_default), 11 | Title(1, StringResource.sort_by_title), 12 | StartDate(2, StringResource.sort_by_start_date), 13 | EndDate(3, StringResource.sort_by_end_date), 14 | Rating(4, StringResource.sort_by_rating); 15 | 16 | companion object { 17 | /** 18 | * Get [SortOption] for its given [index]. 19 | */ 20 | fun forIndex(index: Int): SortOption = values().find { it.index == index } ?: Default 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libraries/kitsu/trending/src/main/java/com/chesire/nekome/kitsu/trending/TrendingItemDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.trending 2 | 3 | import com.chesire.nekome.core.models.ImageModel 4 | import com.chesire.nekome.datasource.trending.TrendingDomain 5 | import com.chesire.nekome.kitsu.trending.dto.TrendingItemDto 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Provides ability to map instances of [TrendingItemDto] into [TrendingDomain]. 10 | */ 11 | class TrendingItemDtoMapper @Inject constructor() { 12 | 13 | /** 14 | * Converts an instance of [TrendingItemDto] into a [TrendingDomain]. 15 | */ 16 | fun toTrendingDomain(input: TrendingItemDto) = 17 | TrendingDomain( 18 | input.id, 19 | input.type, 20 | input.attributes.canonicalTitle, 21 | input.attributes.posterImage ?: ImageModel.empty 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /core/preferences/src/test/java/com/chesire/nekome/core/preferences/flags/ImageQualityTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.preferences.flags 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class ImageQualityTests { 7 | 8 | @Test 9 | fun `forIndex ImageQuality#Low returns expected value`() { 10 | assertEquals( 11 | ImageQuality.Low, 12 | ImageQuality.forIndex(0) 13 | ) 14 | } 15 | 16 | @Test 17 | fun `forIndex ImageQuality#Medium returns expected value`() { 18 | assertEquals( 19 | ImageQuality.Medium, 20 | ImageQuality.forIndex(1) 21 | ) 22 | } 23 | 24 | @Test 25 | fun `forIndex ImageQuality#High returns expected value`() { 26 | assertEquals( 27 | ImageQuality.High, 28 | ImageQuality.forIndex(2) 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/collection/core/ShouldRateSeriesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.datasource.series.SeriesRepository 5 | import javax.inject.Inject 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.flow.first 8 | import kotlinx.coroutines.withContext 9 | 10 | class ShouldRateSeriesUseCase @Inject constructor( 11 | private val repo: SeriesRepository, 12 | private val pref: SeriesPreferences 13 | ) { 14 | 15 | suspend operator fun invoke(userSeriesId: Int): Boolean { 16 | return withContext(Dispatchers.IO) { 17 | val domain = repo.getSeries(userSeriesId) 18 | pref.rateSeriesOnCompletion.first() && domain.progress + 1 == domain.totalLength 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /testing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.chesire.nekome.testing" 8 | compileSdk = libs.versions.sdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | packaging { 16 | resources { 17 | excludes += listOf("META-INF/AL2.0", "META-INF/LGPL2.1", "META-INF/*.kotlin_module") 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation(project(":libraries:core")) 24 | implementation(project(":libraries:database")) 25 | implementation(project(":libraries:datasource:series")) 26 | implementation(project(":libraries:datasource:user")) 27 | 28 | implementation(libs.junit) 29 | implementation(libs.kotlin.coroutines.test) 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/injection/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import androidx.preference.PreferenceManager 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.Reusable 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | 13 | /** 14 | * Dagger [Module] for generic application items. 15 | */ 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object AppModule { 19 | 20 | /** 21 | * Provides the default [SharedPreferences] for the application. 22 | */ 23 | @Provides 24 | @Reusable 25 | fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences = 26 | PreferenceManager.getDefaultSharedPreferences(context) 27 | } 28 | -------------------------------------------------------------------------------- /libraries/datasource/user/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.chesire.nekome.datasource.user" 8 | compileSdk = libs.versions.sdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation(project(":libraries:core")) 19 | implementation(project(":libraries:database")) 20 | 21 | implementation(libs.google.hilt.android) 22 | implementation(libs.kotlin.coroutines.android) 23 | implementation(libs.kotlin.coroutines.core) 24 | implementation(libs.kotlin.result) 25 | implementation(libs.timber) 26 | 27 | testImplementation(project(":testing")) 28 | testImplementation(libs.junit) 29 | testImplementation(libs.mockk) 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/LogoutHandler.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome 2 | 3 | import com.chesire.nekome.app.settings.config.LogoutExecutor 4 | import com.chesire.nekome.database.RoomDB 5 | import com.chesire.nekome.datasource.auth.AccessTokenRepository 6 | import javax.inject.Inject 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.withContext 9 | import timber.log.Timber 10 | 11 | /** 12 | * Handles clearing out resources for when a log out occurs. 13 | */ 14 | class LogoutHandler @Inject constructor( 15 | private val repo: AccessTokenRepository, 16 | private val db: RoomDB 17 | ) : LogoutExecutor { 18 | 19 | override suspend fun executeLogout() { 20 | withContext(Dispatchers.IO) { 21 | Timber.d("Clearing database tables") 22 | db.clearAllTables() 23 | } 24 | Timber.d("Clearing auth") 25 | repo.clear() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/chesire/nekome/core/models/ErrorDomain.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.models 2 | 3 | import java.net.HttpURLConnection.HTTP_BAD_REQUEST 4 | import java.net.HttpURLConnection.HTTP_FORBIDDEN 5 | import java.net.HttpURLConnection.HTTP_NO_CONTENT 6 | import java.net.HttpURLConnection.HTTP_UNAUTHORIZED 7 | import java.net.HttpURLConnection.HTTP_UNAVAILABLE 8 | 9 | data class ErrorDomain( 10 | val message: String, 11 | val code: Int 12 | ) { 13 | companion object { 14 | val badRequest = ErrorDomain("", HTTP_BAD_REQUEST) 15 | val couldNotReach = ErrorDomain("Could not reach service", HTTP_UNAVAILABLE) 16 | val couldNotRefresh = ErrorDomain("Could not refresh auth", HTTP_FORBIDDEN) 17 | val emptyResponse = ErrorDomain("Response body is null", HTTP_NO_CONTENT) 18 | val invalidCredentials = ErrorDomain("", HTTP_UNAUTHORIZED) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libraries/database/src/test/java/com/chesire/nekome/database/converters/UserSeriesStatusConverterTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import com.chesire.nekome.core.flags.UserSeriesStatus 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class UserSeriesStatusConverterTests { 8 | @Test 9 | fun `fromUserSeriesStatus converts to enum name from UserSeriesStatus`() { 10 | val converter = UserSeriesStatusConverter() 11 | UserSeriesStatus.values().forEach { 12 | assertEquals(it.name, converter.fromUserSeriesStatus(it)) 13 | } 14 | } 15 | 16 | @Test 17 | fun `toUserSeriesStatus converts to UserSeriesStatus from name`() { 18 | val converter = UserSeriesStatusConverter() 19 | UserSeriesStatus.values().forEach { 20 | assertEquals(it, converter.toUserSeriesStatus(it.name)) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/preferences/src/main/java/com/chesire/nekome/core/preferences/flags/TitleLanguage.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.preferences.flags 2 | 3 | import androidx.annotation.StringRes 4 | import com.chesire.nekome.resources.StringResource 5 | 6 | /** 7 | * Options available for the language used to display the titles. 8 | */ 9 | enum class TitleLanguage(val index: Int, @StringRes val stringId: Int, val key: String) { 10 | Canonical(0, StringResource.title_language_canonical, ""), 11 | English(1, StringResource.title_language_english, "en"), 12 | Romaji(2, StringResource.title_language_romaji, "en_jp"), 13 | Japanese(3, StringResource.title_language_japanese, "ja_jp"); 14 | 15 | companion object { 16 | 17 | /** 18 | * Get [TitleLanguage] for its given [index]. 19 | */ 20 | fun forIndex(index: Int): TitleLanguage = values().find { it.index == index } ?: Canonical 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libraries/kitsu/user/src/main/java/com/chesire/nekome/kitsu/user/UserItemDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.user 2 | 3 | import com.chesire.nekome.core.flags.Service 4 | import com.chesire.nekome.core.models.ImageModel 5 | import com.chesire.nekome.datasource.user.UserDomain 6 | import com.chesire.nekome.kitsu.user.dto.UserItemDto 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Provides ability to map instances of [UserItemDto] into [UserDomain]. 11 | */ 12 | class UserItemDtoMapper @Inject constructor() { 13 | 14 | /** 15 | * Converts an instance of [UserItemDto] into a [UserDomain]. 16 | */ 17 | fun toUserDomain(input: UserItemDto) = 18 | UserDomain( 19 | input.id, 20 | input.attributes.name, 21 | input.attributes.avatar ?: ImageModel.empty, 22 | input.attributes.coverImage ?: ImageModel.empty, 23 | Service.Kitsu 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /core/preferences/src/main/java/com/chesire/nekome/core/preferences/ext/SharedPreferenceExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.preferences.ext 2 | 3 | import android.content.SharedPreferences 4 | import androidx.core.content.edit 5 | 6 | /** 7 | * Migrates keys to a new [SharedPreferences] instance. 8 | * Will only migrate keys that have a value of type [String]. 9 | */ 10 | fun SharedPreferences.migrateTo(encryptedPreferences: SharedPreferences) { 11 | // If the current list is empty, no need to migrate 12 | if (all.isEmpty()) { 13 | return 14 | } 15 | 16 | val migrated = mutableListOf() 17 | all.forEach { (key, value) -> 18 | if (value is String) { 19 | migrated.add(key) 20 | encryptedPreferences.edit { 21 | putString(key, value) 22 | } 23 | } 24 | } 25 | edit { 26 | migrated.forEach(::remove) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /libraries/datasource/auth/src/main/java/com/chesire/nekome/datasource/auth/local/AuthProvider.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.auth.local 2 | 3 | import javax.inject.Inject 4 | 5 | /** 6 | * Provides authorization for Kitsu access. 7 | */ 8 | class AuthProvider @Inject constructor(private val auth: LocalAuth) { 9 | 10 | /** 11 | * Retrieve or set the current access token. 12 | */ 13 | var accessToken: String 14 | get() = auth.accessToken 15 | set(value) { 16 | auth.accessToken = value 17 | } 18 | 19 | /** 20 | * Retrieve or set the refresh token used to get a new access token. 21 | */ 22 | var refreshToken: String 23 | get() = auth.refreshToken 24 | set(value) { 25 | auth.refreshToken = value 26 | } 27 | 28 | /** 29 | * Clears out the current auth credentials. 30 | */ 31 | fun clearAuth() = auth.clear() 32 | } 33 | -------------------------------------------------------------------------------- /core/preferences/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.google.dagger.hilt.android) 4 | alias(libs.plugins.google.devtools.ksp) 5 | alias(libs.plugins.kotlin.android) 6 | } 7 | 8 | android { 9 | namespace = "com.chesire.nekome.core.preferences" 10 | compileSdk = libs.versions.sdk.get().toInt() 11 | 12 | defaultConfig { 13 | minSdk = 21 14 | 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | } 18 | 19 | dependencies { 20 | implementation(project(":core:resources")) 21 | implementation(project(":libraries:core")) 22 | 23 | implementation(libs.androidx.appcompat) 24 | implementation(libs.androidx.datastore.preferences) 25 | implementation(libs.google.hilt.android) 26 | implementation(libs.squareup.moshi) 27 | ksp(libs.google.hilt.android.compiler) 28 | 29 | testImplementation(libs.junit) 30 | testImplementation(libs.mockk) 31 | } 32 | -------------------------------------------------------------------------------- /libraries/kitsu/trending/src/main/java/com/chesire/nekome/kitsu/trending/dto/TrendingItemDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.trending.dto 2 | 3 | import com.chesire.nekome.core.flags.SeriesType 4 | import com.chesire.nekome.core.models.ImageModel 5 | import com.squareup.moshi.Json 6 | import com.squareup.moshi.JsonClass 7 | 8 | /** 9 | * DTO from the Kitsu trending endpoint. 10 | */ 11 | @JsonClass(generateAdapter = true) 12 | data class TrendingItemDto( 13 | @Json(name = "id") 14 | val id: Int, 15 | @Json(name = "type") 16 | val type: SeriesType, 17 | @Json(name = "attributes") 18 | val attributes: Attributes 19 | ) { 20 | /** 21 | * Attributes of the trending item. 22 | */ 23 | @JsonClass(generateAdapter = true) 24 | data class Attributes( 25 | @Json(name = "canonicalTitle") 26 | val canonicalTitle: String, 27 | @Json(name = "posterImage") 28 | val posterImage: ImageModel? 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /libraries/datasource/series/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.chesire.nekome.datasource.series" 8 | compileSdk = libs.versions.sdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation(project(":libraries:core")) 19 | implementation(project(":libraries:database")) 20 | 21 | implementation(libs.google.hilt.android) 22 | implementation(libs.kotlin.coroutines.android) 23 | implementation(libs.kotlin.coroutines.core) 24 | implementation(libs.kotlin.result) 25 | implementation(libs.timber) 26 | 27 | testImplementation(project(":testing")) 28 | testImplementation(libs.androidx.arch.core.testing) 29 | testImplementation(libs.junit) 30 | testImplementation(libs.mockk) 31 | } 32 | -------------------------------------------------------------------------------- /libraries/datasource/series/src/main/java/com/chesire/nekome/datasource/series/SeriesDomain.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.series 2 | 3 | import com.chesire.nekome.core.flags.SeriesStatus 4 | import com.chesire.nekome.core.flags.SeriesType 5 | import com.chesire.nekome.core.flags.Subtype 6 | import com.chesire.nekome.core.flags.UserSeriesStatus 7 | import com.chesire.nekome.core.models.ImageModel 8 | 9 | /** 10 | * Domain object for a single Series item. 11 | */ 12 | data class SeriesDomain( 13 | val id: Int, 14 | val userId: Int, 15 | val type: SeriesType, 16 | val subtype: Subtype, 17 | val slug: String, 18 | val title: String, 19 | val otherTitles: Map, 20 | val seriesStatus: SeriesStatus, 21 | val userSeriesStatus: UserSeriesStatus, 22 | val progress: Int, 23 | val totalLength: Int, 24 | val rating: Int, 25 | val posterImage: ImageModel, 26 | val startDate: String, 27 | val endDate: String 28 | ) 29 | -------------------------------------------------------------------------------- /libraries/kitsu/search/src/main/java/com/chesire/nekome/kitsu/search/SearchItemDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.search 2 | 3 | import com.chesire.nekome.core.models.ImageModel 4 | import com.chesire.nekome.datasource.search.SearchDomain 5 | import com.chesire.nekome.kitsu.search.dto.SearchItemDto 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Provides ability to map instances of [SearchItemDto] into [SearchDomain]. 10 | */ 11 | class SearchItemDtoMapper @Inject constructor() { 12 | 13 | /** 14 | * Converts an instance of [SearchItemDto] into a [SearchDomain]. 15 | */ 16 | fun toSearchDomain(input: SearchItemDto) = 17 | SearchDomain( 18 | input.id, 19 | input.type, 20 | input.attributes.synopsis, 21 | input.attributes.canonicalTitle, 22 | input.attributes.titles, 23 | input.attributes.subtype, 24 | input.attributes.posterImage ?: ImageModel.empty 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /features/login/src/main/java/com/chesire/nekome/app/login/credentials/ui/UIState.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.login.credentials.ui 2 | 3 | import androidx.annotation.StringRes 4 | 5 | data class UIState( 6 | val username: String, 7 | val hasUsernameError: Boolean, 8 | val password: String, 9 | val hasPasswordError: Boolean, 10 | val isPerformingLogin: Boolean, 11 | val loginButtonEnabled: Boolean, 12 | @StringRes val errorSnackbarMessage: Int?, 13 | val navigateScreenEvent: Boolean? 14 | ) { 15 | companion object { 16 | val empty: UIState 17 | get() = UIState( 18 | username = "", 19 | hasUsernameError = false, 20 | password = "", 21 | hasPasswordError = false, 22 | isPerformingLogin = false, 23 | loginButtonEnabled = false, 24 | errorSnackbarMessage = null, 25 | navigateScreenEvent = null 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /libraries/core/src/main/res/drawable/ic_blackcat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /libraries/database/src/main/java/com/chesire/nekome/database/converters/ImageModelConverter.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import androidx.room.TypeConverter 4 | import com.chesire.nekome.core.models.ImageModel 5 | import com.chesire.nekome.core.models.ImageModelJsonAdapter 6 | import com.squareup.moshi.Moshi 7 | 8 | /** 9 | * Converter for [ImageModel] -> [String]. 10 | * 11 | * For saving an [ImageModel] into the database. 12 | */ 13 | class ImageModelConverter { 14 | private val adapter: ImageModelJsonAdapter by lazy { 15 | ImageModelJsonAdapter(Moshi.Builder().build()) 16 | } 17 | 18 | /** 19 | * Converts an [ImageModel] into a [String]. 20 | */ 21 | @TypeConverter 22 | fun fromImageModel(model: ImageModel): String = adapter.toJson(model) 23 | 24 | /** 25 | * Converts a [String] into a [ImageModel]. 26 | */ 27 | @TypeConverter 28 | fun toImageModel(model: String): ImageModel = adapter.fromJson(model) ?: ImageModel.empty 29 | } 30 | -------------------------------------------------------------------------------- /libraries/kitsu/auth/src/main/java/com/chesire/nekome/kitsu/auth/KitsuAuthService.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.auth 2 | 3 | import com.chesire.nekome.kitsu.auth.dto.AuthResponseDto 4 | import com.chesire.nekome.kitsu.auth.dto.LoginRequestDto 5 | import com.chesire.nekome.kitsu.auth.dto.RefreshTokenRequestDto 6 | import retrofit2.Response 7 | import retrofit2.http.Body 8 | import retrofit2.http.POST 9 | 10 | /** 11 | * Constructed using Retrofit to interface with the Kitsu API for queries related to authenticating. 12 | */ 13 | interface KitsuAuthService { 14 | 15 | /** 16 | * Performs a login request with the API. 17 | */ 18 | @POST("api/oauth/token") 19 | suspend fun loginAsync(@Body body: LoginRequestDto): Response 20 | 21 | /** 22 | * Performs a refresh token request with the API. 23 | */ 24 | @POST("api/oauth/token") 25 | suspend fun refreshAccessTokenAsync( 26 | @Body body: RefreshTokenRequestDto 27 | ): Response 28 | } 29 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # Make it more obvious that a PR is a work in progress and shouldn't be merged yet 2 | warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]" 3 | 4 | # Warn when there is a big PR 5 | warn("Big PR") if git.lines_of_code > 500 6 | 7 | # General 8 | failure "Please provide a summary in the Pull Request description" if github.pr_body.length < 5 9 | warn "This PR does not have any assignees yet." unless github.pr_json["assignee"] 10 | can_merge = github.pr_json["mergeable"] 11 | warn("This PR cannot be merged yet.", sticky: false) unless can_merge 12 | github.dismiss_out_of_range_messages 13 | 14 | # AndroidLint 15 | lint_dir = "**/reports/lint-results*.xml" 16 | Dir[lint_dir].each do |file_name| 17 | android_lint.skip_gradle_task = true 18 | android_lint.filtering = true 19 | android_lint.report_file = file_name 20 | android_lint.lint(inline_mode: true) 21 | end 22 | 23 | # CheckstyleFormat 24 | checkstyle_format.base_path = Dir.pwd 25 | checkstyle_format.report 'build/reports/detekt/detekt.xml' 26 | -------------------------------------------------------------------------------- /libraries/core/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # Keep the flags that Moshi needs to generate adapters 24 | #noinspection ShrinkerUnresolvedReference 25 | -keep class com.chesire.nekome.core.flags.** { *; } 26 | -------------------------------------------------------------------------------- /libraries/datasource/user/src/main/java/com/chesire/nekome/datasource/user/UserMapper.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.datasource.user 2 | 3 | import com.chesire.nekome.database.entity.UserEntity 4 | import javax.inject.Inject 5 | 6 | /** 7 | * Provides ability to map instances of [UserDomain]. 8 | */ 9 | class UserMapper @Inject constructor() { 10 | 11 | /** 12 | * Converts an instance of [UserDomain] into an instance of [UserEntity]. 13 | */ 14 | fun toUserEntity(input: UserDomain) = 15 | UserEntity( 16 | input.userId, 17 | input.name, 18 | input.avatar, 19 | input.coverImage, 20 | input.service 21 | ) 22 | 23 | /** 24 | * Converts an instance of [UserEntity] into an instance of [UserDomain]. 25 | */ 26 | fun toUserDomain(input: UserEntity) = 27 | UserDomain( 28 | input.userId, 29 | input.name, 30 | input.avatar, 31 | input.coverImage, 32 | input.service 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /libraries/datasource/auth/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.chesire.nekome.datasource.auth" 8 | compileSdk = libs.versions.sdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation(project(":core:preferences")) 19 | implementation(project(":libraries:core")) 20 | 21 | implementation(libs.androidx.core) 22 | implementation(libs.androidx.security.crypto) 23 | implementation(libs.google.hilt.android) 24 | implementation(libs.kotlin.coroutines.android) 25 | implementation(libs.kotlin.coroutines.core) 26 | implementation(libs.kotlin.result) 27 | implementation(libs.squareup.retrofit2) 28 | implementation(libs.timber) 29 | 30 | testImplementation(project(":testing")) 31 | testImplementation(libs.junit) 32 | testImplementation(libs.mockk) 33 | } 34 | -------------------------------------------------------------------------------- /libraries/core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.google.devtools.ksp) 4 | alias(libs.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "com.chesire.nekome.core" 9 | compileSdk = libs.versions.sdk.get().toInt() 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(project(":core:resources")) 20 | 21 | implementation(libs.androidx.appcompat) 22 | implementation(libs.androidx.browser) 23 | implementation(libs.androidx.core) 24 | implementation(libs.androidx.room.runtime) 25 | implementation(libs.google.hilt.android) 26 | implementation(libs.google.material) 27 | implementation(libs.squareup.moshi) 28 | ksp(libs.google.hilt.android.compiler) 29 | ksp(libs.squareup.moshi.codegen) 30 | 31 | testImplementation(project(":testing")) 32 | testImplementation(libs.junit) 33 | testImplementation(libs.mockk) 34 | } 35 | -------------------------------------------------------------------------------- /libraries/database/src/main/java/com/chesire/nekome/database/converters/MapConverter.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import androidx.room.TypeConverter 4 | import com.squareup.moshi.JsonAdapter 5 | import com.squareup.moshi.Moshi 6 | import com.squareup.moshi.Types 7 | 8 | /** 9 | * Converter for [Map] -> [String]. 10 | * 11 | * For saving the otherTitles into the database. 12 | */ 13 | class MapConverter { 14 | 15 | private val adapter: JsonAdapter> by lazy { 16 | val type = Types.newParameterizedType( 17 | Map::class.java, 18 | String::class.java, 19 | String::class.java 20 | ) 21 | Moshi.Builder().build().adapter(type) 22 | } 23 | 24 | @TypeConverter 25 | fun fromMap(map: Map): String = adapter.toJson(map) 26 | 27 | @TypeConverter 28 | fun toMap(data: String): Map = if (data.isNotEmpty()) { 29 | adapter.fromJson(data) ?: emptyMap() 30 | } else { 31 | emptyMap() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libraries/database/src/main/java/com/chesire/nekome/database/entity/SeriesEntity.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.chesire.nekome.core.flags.SeriesStatus 6 | import com.chesire.nekome.core.flags.SeriesType 7 | import com.chesire.nekome.core.flags.Subtype 8 | import com.chesire.nekome.core.flags.UserSeriesStatus 9 | import com.chesire.nekome.core.models.ImageModel 10 | 11 | /** 12 | * Data for a singular series entity. 13 | */ 14 | @Entity 15 | data class SeriesEntity( 16 | @PrimaryKey 17 | val id: Int, 18 | val userId: Int, 19 | val type: SeriesType, 20 | val subtype: Subtype, 21 | val slug: String, 22 | val title: String, 23 | val otherTitles: Map, 24 | val seriesStatus: SeriesStatus, 25 | val userSeriesStatus: UserSeriesStatus, 26 | val progress: Int, 27 | val totalLength: Int, 28 | val rating: Int, 29 | val posterImage: ImageModel, 30 | val startDate: String, 31 | val endDate: String 32 | ) 33 | -------------------------------------------------------------------------------- /features/settings/src/test/java/com/chesire/nekome/app/settings/config/core/UpdateRateSeriesUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import io.mockk.clearAllMocks 5 | import io.mockk.coEvery 6 | import io.mockk.just 7 | import io.mockk.mockk 8 | import io.mockk.runs 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Before 11 | import org.junit.Test 12 | 13 | class UpdateRateSeriesUseCaseTest { 14 | 15 | private val pref = mockk() 16 | private lateinit var updateRateSeries: UpdateRateSeriesUseCase 17 | 18 | @Before 19 | fun setup() { 20 | clearAllMocks() 21 | 22 | updateRateSeries = UpdateRateSeriesUseCase(pref) 23 | } 24 | 25 | @Test 26 | fun `When invoking, update pref with new value`() = runTest { 27 | coEvery { pref.updateRateSeriesOnCompletion(any()) } just runs 28 | 29 | updateRateSeries(true) 30 | 31 | coEvery { pref.updateRateSeriesOnCompletion(true) } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/helpers/creation/SearchDomain.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.helpers.creation 2 | 3 | import com.chesire.nekome.core.flags.SeriesType 4 | import com.chesire.nekome.core.flags.Subtype 5 | import com.chesire.nekome.core.models.ImageModel 6 | import com.chesire.nekome.core.preferences.flags.TitleLanguage 7 | import com.chesire.nekome.datasource.search.SearchDomain 8 | 9 | /** 10 | * Creates a new [SearchDomain]. 11 | */ 12 | @Suppress("LongParameterList") 13 | fun createSearchDomain( 14 | id: Int = 0, 15 | type: SeriesType = SeriesType.Anime, 16 | synopsis: String = "synopsis", 17 | canonicalTitle: String = "canonicalTitle", 18 | titles: Map = mapOf( 19 | TitleLanguage.English.key to "enTitle", 20 | TitleLanguage.Japanese.key to "japaneseTitle" 21 | ), 22 | subtype: Subtype = Subtype.TV, 23 | posterImage: ImageModel = ImageModel.empty 24 | ) = SearchDomain( 25 | id, 26 | type, 27 | synopsis, 28 | canonicalTitle, 29 | titles, 30 | subtype, 31 | posterImage 32 | ) 33 | -------------------------------------------------------------------------------- /features/settings/src/test/java/com/chesire/nekome/app/settings/config/core/UpdateThemeUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.ApplicationPreferences 4 | import com.chesire.nekome.core.preferences.flags.Theme 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coEvery 7 | import io.mockk.just 8 | import io.mockk.mockk 9 | import io.mockk.runs 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | class UpdateThemeUseCaseTest { 15 | 16 | private val pref = mockk() 17 | private lateinit var updateTheme: UpdateThemeUseCase 18 | 19 | @Before 20 | fun setup() { 21 | clearAllMocks() 22 | 23 | updateTheme = UpdateThemeUseCase(pref) 24 | } 25 | 26 | @Test 27 | fun `When invoking, update pref with new value`() = runTest { 28 | coEvery { pref.updateTheme(any()) } just runs 29 | 30 | updateTheme(Theme.Light) 31 | 32 | coEvery { pref.updateTheme(Theme.Light) } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/injection/MemoryDatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.chesire.nekome.database.RoomDB 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | import dagger.hilt.testing.TestInstallIn 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @TestInstallIn( 15 | components = [SingletonComponent::class], 16 | replaces = [DatabaseModule::class] 17 | ) 18 | object MemoryDatabaseModule { 19 | 20 | @Provides 21 | @Singleton 22 | fun provideDB(@ApplicationContext context: Context): RoomDB = 23 | Room.inMemoryDatabaseBuilder(context, RoomDB::class.java) 24 | .fallbackToDestructiveMigration() 25 | .build() 26 | 27 | @Provides 28 | @Singleton 29 | fun provideSeries(db: RoomDB) = db.series() 30 | 31 | @Provides 32 | @Singleton 33 | fun provideUser(db: RoomDB) = db.user() 34 | } 35 | -------------------------------------------------------------------------------- /features/series/src/test/java/com/chesire/nekome/app/series/collection/core/UpdateSortUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.core.preferences.flags.SortOption 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coEvery 7 | import io.mockk.just 8 | import io.mockk.mockk 9 | import io.mockk.runs 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | class UpdateSortUseCaseTest { 15 | 16 | private val pref = mockk() 17 | private lateinit var updateSort: UpdateSortUseCase 18 | 19 | @Before 20 | fun setup() { 21 | clearAllMocks() 22 | 23 | updateSort = UpdateSortUseCase(pref) 24 | } 25 | 26 | @Test 27 | fun `When invoking, update sort with new option`() = runTest { 28 | coEvery { pref.updateSort(any()) } just runs 29 | 30 | pref.updateSort(SortOption.Rating) 31 | 32 | coEvery { pref.updateSort(SortOption.Rating) } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /features/series/src/test/java/com/chesire/nekome/app/series/collection/core/CurrentSortUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.core.preferences.flags.SortOption 5 | import io.mockk.clearAllMocks 6 | import io.mockk.every 7 | import io.mockk.mockk 8 | import kotlinx.coroutines.flow.flowOf 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Assert.assertEquals 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | class CurrentSortUseCaseTest { 15 | 16 | private val pref = mockk() 17 | private lateinit var currentSort: CurrentSortUseCase 18 | 19 | @Before 20 | fun setup() { 21 | clearAllMocks() 22 | 23 | currentSort = CurrentSortUseCase(pref) 24 | } 25 | 26 | @Test 27 | fun `When invoking, then expected sort option is returned`() = runTest { 28 | every { pref.sort } returns flowOf(SortOption.Title) 29 | 30 | val result = currentSort() 31 | 32 | assertEquals(SortOption.Title, result) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /features/series/src/test/java/com/chesire/nekome/app/series/item/core/RetrieveItemUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.item.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesRepository 4 | import com.chesire.nekome.testing.createSeriesDomain 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coEvery 7 | import io.mockk.mockk 8 | import kotlinx.coroutines.test.runTest 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Before 11 | import org.junit.Test 12 | 13 | class RetrieveItemUseCaseTest { 14 | 15 | private val seriesRepo = mockk() 16 | private lateinit var retrieveItem: RetrieveItemUseCase 17 | 18 | @Before 19 | fun setup() { 20 | clearAllMocks() 21 | 22 | retrieveItem = RetrieveItemUseCase(seriesRepo) 23 | } 24 | 25 | @Test 26 | fun `When invoking, series for the passed in id is returned`() = runTest { 27 | val expected = createSeriesDomain() 28 | coEvery { seriesRepo.getSeries(123) } returns expected 29 | 30 | val result = retrieveItem(123) 31 | 32 | assertEquals(expected, result) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/collection/core/RefreshSeriesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesRepository 4 | import com.github.michaelbull.result.Err 5 | import com.github.michaelbull.result.Ok 6 | import com.github.michaelbull.result.Result 7 | import javax.inject.Inject 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.async 10 | import kotlinx.coroutines.awaitAll 11 | import kotlinx.coroutines.withContext 12 | 13 | class RefreshSeriesUseCase @Inject constructor(private val repo: SeriesRepository) { 14 | 15 | suspend operator fun invoke(): Result { 16 | return withContext(Dispatchers.IO) { 17 | val animeJob = async(Dispatchers.IO) { repo.refreshAnime() } 18 | val mangaJob = async(Dispatchers.IO) { repo.refreshManga() } 19 | 20 | val results = awaitAll(animeJob, mangaJob) 21 | if (results.all { it is Ok }) { 22 | Ok(Unit) 23 | } else { 24 | Err(Unit) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/preferences/src/test/java/com/chesire/nekome/core/preferences/flags/TitleLanguageTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.preferences.flags 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class TitleLanguageTests { 7 | 8 | @Test 9 | fun `forIndex TitleLanguage#Canonical returns expected value`() { 10 | assertEquals( 11 | TitleLanguage.Canonical, 12 | TitleLanguage.forIndex(0) 13 | ) 14 | } 15 | 16 | @Test 17 | fun `forIndex TitleLanguage#English returns expected value`() { 18 | assertEquals( 19 | TitleLanguage.English, 20 | TitleLanguage.forIndex(1) 21 | ) 22 | } 23 | 24 | @Test 25 | fun `forIndex TitleLanguage#Romaji returns expected value`() { 26 | assertEquals( 27 | TitleLanguage.Romaji, 28 | TitleLanguage.forIndex(2) 29 | ) 30 | } 31 | 32 | @Test 33 | fun `forIndex TitleLanguage#Japanese returns expected value`() { 34 | assertEquals( 35 | TitleLanguage.Japanese, 36 | TitleLanguage.forIndex(3) 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /features/settings/src/test/java/com/chesire/nekome/app/settings/config/core/UpdateImageQualityUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.core.preferences.flags.ImageQuality 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coEvery 7 | import io.mockk.just 8 | import io.mockk.mockk 9 | import io.mockk.runs 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | class UpdateImageQualityUseCaseTest { 15 | 16 | private val pref = mockk() 17 | private lateinit var updateImageQuality: UpdateImageQualityUseCase 18 | 19 | @Before 20 | fun setup() { 21 | clearAllMocks() 22 | 23 | updateImageQuality = UpdateImageQualityUseCase(pref) 24 | } 25 | 26 | @Test 27 | fun `When invoking, update pref with new value`() = runTest { 28 | coEvery { pref.updateImageQuality(any()) } just runs 29 | 30 | updateImageQuality(ImageQuality.Medium) 31 | 32 | coEvery { pref.updateImageQuality(ImageQuality.Medium) } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libraries/kitsu/activity/src/main/java/com/chesire/nekome/kitsu/activity/RetrieveActivityDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.activity 2 | 3 | import com.chesire.nekome.datasource.activity.ActivityDomain 4 | import com.chesire.nekome.datasource.activity.Event 5 | import com.chesire.nekome.kitsu.activity.dto.ChangedDataContainer 6 | import com.chesire.nekome.kitsu.activity.dto.RetrieveActivityDto 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Performs mapping of activity related dtos to domain objects. 11 | */ 12 | class RetrieveActivityDtoMapper @Inject constructor() { 13 | 14 | /** 15 | * Maps instances of [RetrieveActivityDto.Data] into an [ActivityDomain]. 16 | */ 17 | fun toActivityDomain(input: RetrieveActivityDto.Data) = 18 | ActivityDomain( 19 | input.id, 20 | input.attributes.updatedAt, 21 | createEvents(input.attributes.changedData) 22 | ) 23 | 24 | private fun createEvents(input: ChangedDataContainer): List { 25 | return input.changedData.map { dataItem -> 26 | Event(dataItem.from, dataItem.to, dataItem.type) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libraries/kitsu/user/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.google.devtools.ksp) 4 | alias(libs.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "com.chesire.nekome.kitsu.user" 9 | compileSdk = libs.versions.sdk.get().toInt() 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(project(":libraries:core")) 20 | implementation(project(":libraries:datasource:user")) 21 | implementation(project(":libraries:kitsu")) 22 | 23 | implementation(libs.google.hilt.android) 24 | implementation(libs.kotlin.coroutines.android) 25 | implementation(libs.kotlin.coroutines.core) 26 | implementation(libs.kotlin.result) 27 | implementation(libs.squareup.retrofit2) 28 | implementation(libs.squareup.retrofit2.converter.moshi) 29 | implementation(libs.timber) 30 | ksp(libs.squareup.moshi.codegen) 31 | 32 | testImplementation(project(":testing")) 33 | testImplementation(libs.junit) 34 | testImplementation(libs.mockk) 35 | } 36 | -------------------------------------------------------------------------------- /libraries/kitsu/search/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.google.devtools.ksp) 4 | alias(libs.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "com.chesire.nekome.kitsu.search" 9 | compileSdk = libs.versions.sdk.get().toInt() 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(project(":libraries:core")) 20 | implementation(project(":libraries:datasource:search")) 21 | implementation(project(":libraries:kitsu")) 22 | 23 | implementation(libs.google.hilt.android) 24 | implementation(libs.kotlin.coroutines.android) 25 | implementation(libs.kotlin.coroutines.core) 26 | implementation(libs.kotlin.result) 27 | implementation(libs.squareup.retrofit2) 28 | implementation(libs.squareup.retrofit2.converter.moshi) 29 | implementation(libs.timber) 30 | ksp(libs.squareup.moshi.codegen) 31 | 32 | testImplementation(project(":testing")) 33 | testImplementation(libs.junit) 34 | testImplementation(libs.mockk) 35 | } 36 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chesire/nekome/helpers/PreferencesExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.helpers 2 | 3 | import com.chesire.nekome.core.flags.UserSeriesStatus 4 | import com.chesire.nekome.core.preferences.ApplicationPreferences 5 | import com.chesire.nekome.core.preferences.SeriesPreferences 6 | import com.chesire.nekome.core.preferences.flags.HomeScreenOptions 7 | import com.chesire.nekome.core.preferences.flags.SortOption 8 | import com.chesire.nekome.core.preferences.flags.Theme 9 | 10 | /** 11 | * Resets the [ApplicationPreferences] back to a default state. 12 | */ 13 | suspend fun ApplicationPreferences.reset() { 14 | updateDefaultSeriesState(UserSeriesStatus.Current) 15 | updateDefaultHomeScreen(HomeScreenOptions.Anime) 16 | updateTheme(Theme.System) 17 | } 18 | 19 | /** 20 | * Resets the [SeriesPreferences] back to a default state. 21 | */ 22 | suspend fun SeriesPreferences.reset() { 23 | updateFilter( 24 | mapOf( 25 | 0 to true, 26 | 1 to false, 27 | 2 to false, 28 | 3 to false, 29 | 4 to false 30 | ) 31 | ) 32 | updateSort(SortOption.Default) 33 | } 34 | -------------------------------------------------------------------------------- /libraries/kitsu/activity/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.google.devtools.ksp) 4 | alias(libs.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "com.chesire.nekome.kitsu.activity" 9 | compileSdk = libs.versions.sdk.get().toInt() 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(project(":libraries:core")) 20 | implementation(project(":libraries:datasource:activity")) 21 | implementation(project(":libraries:kitsu")) 22 | 23 | implementation(libs.google.hilt.android) 24 | implementation(libs.kotlin.coroutines.android) 25 | implementation(libs.kotlin.coroutines.core) 26 | implementation(libs.kotlin.result) 27 | implementation(libs.squareup.retrofit2) 28 | implementation(libs.squareup.retrofit2.converter.moshi) 29 | implementation(libs.timber) 30 | ksp(libs.squareup.moshi.codegen) 31 | 32 | testImplementation(project(":testing")) 33 | testImplementation(libs.junit) 34 | testImplementation(libs.mockk) 35 | } 36 | -------------------------------------------------------------------------------- /libraries/kitsu/library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.google.devtools.ksp) 4 | alias(libs.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "com.chesire.nekome.kitsu.library" 9 | compileSdk = libs.versions.sdk.get().toInt() 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(project(":libraries:core")) 20 | implementation(project(":libraries:datasource:series")) 21 | implementation(project(":libraries:kitsu")) 22 | 23 | implementation(libs.google.hilt.android) 24 | implementation(libs.kotlin.coroutines.android) 25 | implementation(libs.kotlin.coroutines.core) 26 | implementation(libs.kotlin.result) 27 | implementation(libs.squareup.retrofit2) 28 | implementation(libs.squareup.retrofit2.converter.moshi) 29 | implementation(libs.timber) 30 | ksp(libs.squareup.moshi.codegen) 31 | 32 | testImplementation(project(":testing")) 33 | testImplementation(libs.junit) 34 | testImplementation(libs.mockk) 35 | } 36 | -------------------------------------------------------------------------------- /libraries/kitsu/trending/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.google.devtools.ksp) 4 | alias(libs.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "com.chesire.nekome.kitsu.trending" 9 | compileSdk = libs.versions.sdk.get().toInt() 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(project(":libraries:core")) 20 | implementation(project(":libraries:datasource:trending")) 21 | implementation(project(":libraries:kitsu")) 22 | 23 | implementation(libs.google.hilt.android) 24 | implementation(libs.kotlin.coroutines.android) 25 | implementation(libs.kotlin.coroutines.core) 26 | implementation(libs.kotlin.result) 27 | implementation(libs.squareup.retrofit2) 28 | implementation(libs.squareup.retrofit2.converter.moshi) 29 | implementation(libs.timber) 30 | ksp(libs.squareup.moshi.codegen) 31 | 32 | testImplementation(project(":testing")) 33 | testImplementation(libs.junit) 34 | testImplementation(libs.mockk) 35 | } 36 | -------------------------------------------------------------------------------- /app/src/test/java/com/chesire/nekome/LogoutHandlerTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome 2 | 3 | import com.chesire.nekome.database.RoomDB 4 | import com.chesire.nekome.datasource.auth.AccessTokenRepository 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coVerify 7 | import io.mockk.mockk 8 | import kotlinx.coroutines.test.runTest 9 | import org.junit.Before 10 | import org.junit.Test 11 | 12 | class LogoutHandlerTests { 13 | 14 | private val accessTokenRepository = mockk(relaxed = true) 15 | private val roomDB = mockk(relaxed = true) 16 | private lateinit var logoutHandler: LogoutHandler 17 | 18 | @Before 19 | fun setup() { 20 | clearAllMocks() 21 | 22 | logoutHandler = LogoutHandler(accessTokenRepository, roomDB) 23 | } 24 | 25 | @Test 26 | fun `executeLogout clears db`() = runTest { 27 | logoutHandler.executeLogout() 28 | 29 | coVerify { roomDB.clearAllTables() } 30 | } 31 | 32 | @Test 33 | fun `executeLogout clears authProvider`() = runTest { 34 | logoutHandler.executeLogout() 35 | 36 | coVerify { accessTokenRepository.clear() } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /features/settings/src/test/java/com/chesire/nekome/app/settings/config/core/UpdateTitleLanguageUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.SeriesPreferences 4 | import com.chesire.nekome.core.preferences.flags.TitleLanguage 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coEvery 7 | import io.mockk.just 8 | import io.mockk.mockk 9 | import io.mockk.runs 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | class UpdateTitleLanguageUseCaseTest { 15 | 16 | private val pref = mockk() 17 | private lateinit var updateTitleLanguage: UpdateTitleLanguageUseCase 18 | 19 | @Before 20 | fun setup() { 21 | clearAllMocks() 22 | 23 | updateTitleLanguage = UpdateTitleLanguageUseCase(pref) 24 | } 25 | 26 | @Test 27 | fun `When invoking, update pref with new value`() = runTest { 28 | coEvery { pref.updateTitleLanguage(any()) } just runs 29 | 30 | updateTitleLanguage(TitleLanguage.Romaji) 31 | 32 | coEvery { pref.updateTitleLanguage(TitleLanguage.Romaji) } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /features/login/src/test/java/com/chesire/nekome/app/login/syncing/core/SyncSeriesUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.login.syncing.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesRepository 4 | import com.github.michaelbull.result.Ok 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coEvery 7 | import io.mockk.coVerify 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Before 11 | import org.junit.Test 12 | 13 | class SyncSeriesUseCaseTest { 14 | 15 | private val seriesRepo = mockk() 16 | private lateinit var syncSeries: SyncSeriesUseCase 17 | 18 | @Before 19 | fun setup() { 20 | clearAllMocks() 21 | 22 | syncSeries = SyncSeriesUseCase(seriesRepo) 23 | } 24 | 25 | @Test 26 | fun `When invoking, Then anime and manga is refreshed`() = runTest { 27 | coEvery { seriesRepo.refreshAnime() } returns Ok(listOf()) 28 | coEvery { seriesRepo.refreshManga() } returns Ok(listOf()) 29 | 30 | syncSeries() 31 | 32 | coVerify { 33 | seriesRepo.refreshAnime() 34 | seriesRepo.refreshManga() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /libraries/database/src/test/java/com/chesire/nekome/database/converters/ImageModelConverterTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import com.chesire.nekome.core.models.ImageModel 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class ImageModelConverterTests { 8 | @Suppress("MaxLineLength") 9 | private val jsonString = 10 | """{"tiny":{"url":"tinyUrl","width":0,"height":1},"small":{"url":"smallUrl","width":2,"height":3},"medium":{"url":"mediumUrl","width":4,"height":5},"large":{"url":"largeUrl","width":6,"height":7}}""".trimIndent() 11 | private val model = ImageModel( 12 | ImageModel.ImageData("tinyUrl", 0, 1), 13 | ImageModel.ImageData("smallUrl", 2, 3), 14 | ImageModel.ImageData("mediumUrl", 4, 5), 15 | ImageModel.ImageData("largeUrl", 6, 7) 16 | ) 17 | 18 | @Test 19 | fun `fromImageModel converts to String`() { 20 | assertEquals(jsonString, ImageModelConverter().fromImageModel(model)) 21 | } 22 | 23 | @Test 24 | fun `toImageModel converts to ImageModel`() { 25 | assertEquals(model, ImageModelConverter().toImageModel(jsonString)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libraries/kitsu/activity/src/main/java/com/chesire/nekome/kitsu/activity/dto/RetrieveActivityDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.activity.dto 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | /** 7 | * DTO from the Kitsu activity endpoint. 8 | */ 9 | @JsonClass(generateAdapter = true) 10 | data class RetrieveActivityDto( 11 | @Json(name = "data") 12 | val data: List 13 | ) { 14 | 15 | /** 16 | * Data of the Kitsu activity item. 17 | */ 18 | @JsonClass(generateAdapter = true) 19 | data class Data( 20 | @Json(name = "id") 21 | val id: Int, 22 | @Json(name = "attributes") 23 | val attributes: Attributes 24 | ) { 25 | 26 | /** 27 | * Attributes of the Kitsu activity item. 28 | */ 29 | @JsonClass(generateAdapter = true) 30 | data class Attributes( 31 | @Json(name = "createdAt") 32 | val createdAt: String, 33 | @Json(name = "updatedAt") 34 | val updatedAt: String, 35 | @Json(name = "changedData") 36 | val changedData: ChangedDataContainer 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /features/search/src/test/java/com/chesire/nekome/app/search/search/core/RememberSearchGroupUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.search.search.core 2 | 3 | import com.chesire.nekome.app.search.search.core.model.SearchGroup 4 | import com.chesire.nekome.app.search.search.data.SearchPreferences 5 | import io.mockk.clearAllMocks 6 | import io.mockk.every 7 | import io.mockk.just 8 | import io.mockk.mockk 9 | import io.mockk.runs 10 | import io.mockk.verify 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | class RememberSearchGroupUseCaseTest { 15 | 16 | private val searchPreferences = mockk() 17 | private lateinit var rememberSearchGroup: RememberSearchGroupUseCase 18 | 19 | @Before 20 | fun setup() { 21 | clearAllMocks() 22 | 23 | rememberSearchGroup = RememberSearchGroupUseCase(searchPreferences) 24 | } 25 | 26 | @Test 27 | fun `When invoke, Then search group is saved to preferences`() { 28 | every { searchPreferences.lastSearchGroup = any() } just runs 29 | 30 | rememberSearchGroup.invoke(SearchGroup.Manga) 31 | 32 | verify { searchPreferences.lastSearchGroup = "Manga" } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libraries/kitsu/src/main/java/com/chesire/nekome/kitsu/adapters/SeriesTypeAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.adapters 2 | 3 | import com.chesire.nekome.core.flags.SeriesType 4 | import com.squareup.moshi.FromJson 5 | import com.squareup.moshi.ToJson 6 | 7 | private const val ANIME = "anime" 8 | private const val MANGA = "manga" 9 | private const val UNKNOWN = "unknown" 10 | 11 | /** 12 | * Adapter to plug into retrofit to convert strings into [SeriesType] values. 13 | */ 14 | class SeriesTypeAdapter { 15 | /** 16 | * Converts [type] into the [SeriesType] value. 17 | */ 18 | @FromJson 19 | fun seriesTypeFromString(type: String): SeriesType { 20 | return when (type) { 21 | ANIME -> SeriesType.Anime 22 | MANGA -> SeriesType.Manga 23 | else -> SeriesType.Unknown 24 | } 25 | } 26 | 27 | /** 28 | * Converts [type] into its [String] value. 29 | */ 30 | @ToJson 31 | fun seriesTypeToString(type: SeriesType): String { 32 | return when (type) { 33 | SeriesType.Anime -> ANIME 34 | SeriesType.Manga -> MANGA 35 | else -> UNKNOWN 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /features/search/src/main/java/com/chesire/nekome/app/search/search/core/SearchInitializeUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.search.search.core 2 | 3 | import com.chesire.nekome.app.search.search.core.model.SearchGroup 4 | import com.chesire.nekome.app.search.search.data.SearchPreferences 5 | import javax.inject.Inject 6 | import timber.log.Timber 7 | 8 | class SearchInitializeUseCase @Inject constructor( 9 | private val searchPreferences: SearchPreferences 10 | ) { 11 | 12 | operator fun invoke() = InitializeArgs(initialGroup = retrieveInitialGroup()) 13 | 14 | private fun retrieveInitialGroup(): SearchGroup { 15 | val lastGroup = searchPreferences.lastSearchGroup 16 | 17 | return if (lastGroup.isBlank()) { 18 | SearchGroup.Anime 19 | } else { 20 | try { 21 | SearchGroup.valueOf(searchPreferences.lastSearchGroup) 22 | } catch (ex: IllegalArgumentException) { 23 | Timber.w("Failed to parse the search group - [$lastGroup]") 24 | SearchGroup.Anime 25 | } 26 | } 27 | } 28 | } 29 | 30 | data class InitializeArgs( 31 | val initialGroup: SearchGroup 32 | ) 33 | -------------------------------------------------------------------------------- /features/series/src/test/java/com/chesire/nekome/app/series/collection/core/CollectSeriesUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesRepository 4 | import com.chesire.nekome.testing.createSeriesDomain 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coEvery 7 | import io.mockk.mockk 8 | import kotlinx.coroutines.flow.flowOf 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Assert.assertEquals 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | class CollectSeriesUseCaseTest { 15 | 16 | private val seriesRepo = mockk() 17 | private lateinit var collectSeries: CollectSeriesUseCase 18 | 19 | @Before 20 | fun setup() { 21 | clearAllMocks() 22 | 23 | collectSeries = CollectSeriesUseCase(seriesRepo) 24 | } 25 | 26 | @Test 27 | fun `When invoking, then flow of series is returned`() = runTest { 28 | val expected = flowOf(listOf(createSeriesDomain())) 29 | coEvery { seriesRepo.getSeries() } returns expected 30 | 31 | val result = collectSeries() 32 | 33 | assertEquals(expected, result) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /libraries/kitsu/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.google.devtools.ksp) 4 | alias(libs.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "com.chesire.nekome.kitsu" 9 | compileSdk = libs.versions.sdk.get().toInt() 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(project(":libraries:core")) 20 | implementation(project(":libraries:datasource:auth")) 21 | 22 | implementation(libs.androidx.appcompat) 23 | implementation(libs.androidx.core) 24 | implementation(libs.google.hilt.android) 25 | implementation(libs.kotlin.coroutines.android) 26 | implementation(libs.kotlin.coroutines.core) 27 | implementation(libs.kotlin.result) 28 | implementation(libs.squareup.retrofit2) 29 | implementation(libs.squareup.retrofit2.converter.moshi) 30 | ksp(libs.google.hilt.android.compiler) 31 | ksp(libs.squareup.moshi.codegen) 32 | 33 | testImplementation(project(":testing")) 34 | testImplementation(libs.junit) 35 | testImplementation(libs.mockk) 36 | } 37 | -------------------------------------------------------------------------------- /libraries/kitsu/auth/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.google.devtools.ksp) 4 | alias(libs.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "com.chesire.nekome.kitsu.auth" 9 | compileSdk = libs.versions.sdk.get().toInt() 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(project(":libraries:core")) 20 | implementation(project(":libraries:datasource:auth")) 21 | implementation(project(":libraries:kitsu")) 22 | 23 | implementation(libs.androidx.core) 24 | implementation(libs.google.hilt.android) 25 | implementation(libs.kotlin.coroutines.android) 26 | implementation(libs.kotlin.coroutines.core) 27 | implementation(libs.kotlin.result) 28 | implementation(libs.squareup.retrofit2) 29 | implementation(libs.squareup.retrofit2.converter.moshi) 30 | implementation(libs.timber) 31 | ksp(libs.squareup.moshi.codegen) 32 | 33 | testImplementation(project(":testing")) 34 | testImplementation(libs.junit) 35 | testImplementation(libs.mockk) 36 | } 37 | -------------------------------------------------------------------------------- /features/serieswidget/src/main/java/com/chesire/nekome/feature/serieswidget/core/UpdateSeriesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.feature.serieswidget.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesRepository 4 | import com.github.michaelbull.result.Result 5 | import com.github.michaelbull.result.mapEither 6 | import javax.inject.Inject 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.withContext 9 | 10 | class UpdateSeriesUseCase @Inject constructor( 11 | private val seriesRepository: SeriesRepository 12 | ) { 13 | 14 | suspend operator fun invoke(seriesId: Int): Result { 15 | val currentSeries = seriesRepository.getSeries(seriesId) 16 | return withContext(Dispatchers.IO) { 17 | seriesRepository 18 | .updateSeries( 19 | currentSeries.userId, 20 | currentSeries.progress + 1, 21 | currentSeries.userSeriesStatus, 22 | currentSeries.rating 23 | ) 24 | .mapEither( 25 | success = { Unit }, 26 | failure = { Unit } 27 | ) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /features/settings/src/test/java/com/chesire/nekome/app/settings/config/core/UpdateDefaultSeriesStateUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.flags.UserSeriesStatus 4 | import com.chesire.nekome.core.preferences.ApplicationPreferences 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coEvery 7 | import io.mockk.just 8 | import io.mockk.mockk 9 | import io.mockk.runs 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | class UpdateDefaultSeriesStateUseCaseTest { 15 | 16 | private val pref = mockk() 17 | private lateinit var updateDefaultSeriesState: UpdateDefaultSeriesStateUseCase 18 | 19 | @Before 20 | fun setup() { 21 | clearAllMocks() 22 | 23 | updateDefaultSeriesState = UpdateDefaultSeriesStateUseCase(pref) 24 | } 25 | 26 | @Test 27 | fun `When invoking, update pref with new value`() = runTest { 28 | coEvery { pref.updateDefaultSeriesState(any()) } just runs 29 | 30 | updateDefaultSeriesState(UserSeriesStatus.OnHold) 31 | 32 | coEvery { pref.updateDefaultSeriesState(UserSeriesStatus.OnHold) } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libraries/kitsu/search/src/main/java/com/chesire/nekome/kitsu/search/dto/SearchItemDto.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.kitsu.search.dto 2 | 3 | import com.chesire.nekome.core.flags.SeriesType 4 | import com.chesire.nekome.core.flags.Subtype 5 | import com.chesire.nekome.core.models.ImageModel 6 | import com.squareup.moshi.Json 7 | import com.squareup.moshi.JsonClass 8 | 9 | /** 10 | * DTO from the Kitsu search endpoint. 11 | */ 12 | @JsonClass(generateAdapter = true) 13 | data class SearchItemDto( 14 | @Json(name = "id") 15 | val id: Int, 16 | @Json(name = "type") 17 | val type: SeriesType, 18 | @Json(name = "attributes") 19 | val attributes: Attributes 20 | ) { 21 | /** 22 | * Attributes of the search item. 23 | */ 24 | @JsonClass(generateAdapter = true) 25 | data class Attributes( 26 | @Json(name = "synopsis") 27 | val synopsis: String, 28 | @Json(name = "titles") 29 | val titles: Map, 30 | @Json(name = "canonicalTitle") 31 | val canonicalTitle: String, 32 | @Json(name = "subtype") 33 | val subtype: Subtype, 34 | @Json(name = "posterImage") 35 | val posterImage: ImageModel? 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /features/settings/src/test/java/com/chesire/nekome/app/settings/config/core/UpdateDefaultHomeScreenUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.core.preferences.ApplicationPreferences 4 | import com.chesire.nekome.core.preferences.flags.HomeScreenOptions 5 | import io.mockk.clearAllMocks 6 | import io.mockk.coEvery 7 | import io.mockk.just 8 | import io.mockk.mockk 9 | import io.mockk.runs 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Before 12 | import org.junit.Test 13 | 14 | class UpdateDefaultHomeScreenUseCaseTest { 15 | 16 | private val pref = mockk() 17 | private lateinit var updateDefaultHomeScreen: UpdateDefaultHomeScreenUseCase 18 | 19 | @Before 20 | fun setup() { 21 | clearAllMocks() 22 | 23 | updateDefaultHomeScreen = UpdateDefaultHomeScreenUseCase(pref) 24 | } 25 | 26 | @Test 27 | fun `When invoking, update pref with new value`() = runTest { 28 | coEvery { pref.updateDefaultHomeScreen(any()) } just runs 29 | 30 | updateDefaultHomeScreen(HomeScreenOptions.Manga) 31 | 32 | coEvery { pref.updateDefaultHomeScreen(HomeScreenOptions.Manga) } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/chesire/nekome/core/AuthCaster.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core 2 | 3 | import javax.inject.Inject 4 | import javax.inject.Singleton 5 | 6 | /** 7 | * Handles events for authorization. 8 | */ 9 | @Singleton 10 | class AuthCaster @Inject constructor() { 11 | private val subscriptions = mutableSetOf() 12 | 13 | /** 14 | * Subscribe to errors occurring for Auth. 15 | */ 16 | fun subscribeToAuthError(listener: AuthCasterListener) = subscriptions.add(listener) 17 | 18 | /** 19 | * Unsubscribe from errors occurring for Auth. 20 | */ 21 | fun unsubscribeFromAuthError(listener: AuthCasterListener) = subscriptions.remove(listener) 22 | 23 | /** 24 | * Notify listeners that there was an issue refreshing the auth token. 25 | */ 26 | fun issueRefreshingToken() = subscriptions.forEach { it.unableToRefresh() } 27 | 28 | /** 29 | * Listener for when auth events occur. 30 | */ 31 | interface AuthCasterListener { 32 | /** 33 | * Unable to refresh the token, a 401 has occurred. 34 | * This requires the user logging back in. 35 | */ 36 | fun unableToRefresh() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/services/DataRefreshNotifier.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.services 2 | 3 | import androidx.work.ExistingWorkPolicy 4 | import androidx.work.OneTimeWorkRequestBuilder 5 | import androidx.work.WorkManager 6 | import com.chesire.nekome.datasource.series.SeriesRepository 7 | import javax.inject.Inject 8 | import javax.inject.Singleton 9 | 10 | private const val WIDGET_DATA_NOTIFY_TAG = "WidgetData" 11 | private const val WIDGET_DATA_UNIQUE_NAME = "WidgetSync" 12 | 13 | @Singleton 14 | class DataRefreshNotifier @Inject constructor( 15 | private val workManager: WorkManager, 16 | private val seriesRepository: SeriesRepository 17 | ) { 18 | 19 | /** 20 | * Initialize the notifier and listen to any data updates. 21 | */ 22 | suspend fun initialize() { 23 | seriesRepository.getSeries().collect { 24 | val request = OneTimeWorkRequestBuilder() 25 | .addTag(WIDGET_DATA_NOTIFY_TAG) 26 | .build() 27 | 28 | workManager.enqueueUniqueWork( 29 | WIDGET_DATA_UNIQUE_NAME, 30 | ExistingWorkPolicy.REPLACE, 31 | request 32 | ) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/collection/core/IncrementSeriesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.collection.core 2 | 3 | import com.chesire.nekome.datasource.series.SeriesDomain 4 | import com.chesire.nekome.datasource.series.SeriesRepository 5 | import com.github.michaelbull.result.Result 6 | import com.github.michaelbull.result.mapEither 7 | import javax.inject.Inject 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.withContext 10 | 11 | class IncrementSeriesUseCase @Inject constructor(private val repo: SeriesRepository) { 12 | 13 | suspend operator fun invoke(userSeriesId: Int, rating: Int?): Result { 14 | return withContext(Dispatchers.IO) { 15 | val domain = repo.getSeries(userSeriesId) 16 | val newRating = rating ?: domain.rating 17 | repo.updateSeries( 18 | userSeriesId = domain.userId, 19 | progress = domain.progress + 1, 20 | status = domain.userSeriesStatus, 21 | rating = newRating 22 | ).mapEither( 23 | success = { it }, 24 | failure = { } 25 | ) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/injection/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import android.content.Context 4 | import com.chesire.nekome.database.RoomDB 5 | import com.chesire.nekome.database.dao.SeriesDao 6 | import com.chesire.nekome.database.dao.UserDao 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | /** 15 | * Dagger [Module] for the [com.chesire.nekome.database] package. 16 | */ 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | object DatabaseModule { 20 | 21 | /** 22 | * Provides the build [RoomDB]. 23 | */ 24 | @Provides 25 | @Singleton 26 | fun provideDB(@ApplicationContext context: Context) = RoomDB.build(context) 27 | 28 | /** 29 | * Provides the [SeriesDao] table from [RoomDB]. 30 | */ 31 | @Provides 32 | @Singleton 33 | fun provideSeries(db: RoomDB) = db.series() 34 | 35 | /** 36 | * Provides the [UserDao] table from [RoomDB]. 37 | */ 38 | @Provides 39 | @Singleton 40 | fun provideUser(db: RoomDB) = db.user() 41 | } 42 | -------------------------------------------------------------------------------- /core/compose/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.chesire.nekome.core.compose" 8 | compileSdk = libs.versions.sdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | consumerProguardFiles("consumer-rules.pro") 14 | vectorDrawables { 15 | useSupportLibrary = true 16 | } 17 | } 18 | buildFeatures { 19 | compose = true 20 | } 21 | composeOptions { 22 | kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() 23 | } 24 | packaging { 25 | resources { 26 | excludes += listOf("/META-INF/{AL2.0,LGPL2.1}") 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation(platform(libs.androidx.compose.bom)) 33 | implementation(libs.androidx.compose.material3) 34 | implementation(libs.androidx.compose.ui) 35 | implementation(libs.androidx.compose.ui.test.manifest) 36 | implementation(libs.androidx.compose.ui.tooling.preview) 37 | implementation(libs.google.accompanist.systemuicontroller) 38 | debugImplementation(libs.androidx.compose.ui.tooling) 39 | } 40 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven("https://jitpack.io") 14 | } 15 | } 16 | 17 | rootProject.name = "Nekome" 18 | include( 19 | ":app", 20 | ":core:compose", 21 | ":core:preferences", 22 | ":core:resources", 23 | ":features:login", 24 | ":features:search", 25 | ":features:series", 26 | ":features:serieswidget", 27 | ":features:settings", 28 | ":libraries:core", 29 | ":libraries:database", 30 | ":libraries:datasource:activity", 31 | ":libraries:datasource:auth", 32 | ":libraries:datasource:search", 33 | ":libraries:datasource:series", 34 | ":libraries:datasource:trending", 35 | ":libraries:datasource:user", 36 | ":libraries:kitsu", 37 | ":libraries:kitsu:activity", 38 | ":libraries:kitsu:auth", 39 | ":libraries:kitsu:library", 40 | ":libraries:kitsu:search", 41 | ":libraries:kitsu:trending", 42 | ":libraries:kitsu:user", 43 | ":testing" 44 | ) 45 | -------------------------------------------------------------------------------- /core/preferences/src/main/java/com/chesire/nekome/core/preferences/flags/HomeScreenOptions.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.preferences.flags 2 | 3 | import android.content.Context 4 | import androidx.annotation.StringRes 5 | import com.chesire.nekome.resources.StringResource 6 | 7 | /** 8 | * Options for default home screen. 9 | */ 10 | enum class HomeScreenOptions(val index: Int, @StringRes val stringId: Int) { 11 | Anime(0, StringResource.nav_anime), 12 | Manga(1, StringResource.nav_manga); 13 | 14 | companion object { 15 | /** 16 | * Gets a map of [index] to the string acquired from the [stringId]. 17 | */ 18 | fun getValueMap(context: Context) = HomeScreenOptions.values() 19 | .associate { it.index to context.getString(it.stringId) } 20 | 21 | /** 22 | * Gets the [HomeScreenOptions] from a given [index], the [index] should be the index field. 23 | * in the the enum class, but as a string. 24 | */ 25 | fun getFromIndex(index: String): HomeScreenOptions { 26 | return index.toIntOrNull()?.let { intIndex -> 27 | HomeScreenOptions.values().find { it.index == intIndex } ?: Anime 28 | } ?: Anime 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /features/settings/src/test/java/com/chesire/nekome/app/settings/config/core/RetrieveUserUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.settings.config.core 2 | 3 | import com.chesire.nekome.datasource.user.User 4 | import com.chesire.nekome.datasource.user.UserRepository 5 | import com.chesire.nekome.testing.createUserDomain 6 | import io.mockk.clearAllMocks 7 | import io.mockk.coEvery 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.flow.first 10 | import kotlinx.coroutines.flow.flowOf 11 | import kotlinx.coroutines.runBlocking 12 | import org.junit.Assert.assertEquals 13 | import org.junit.Before 14 | import org.junit.Test 15 | 16 | class RetrieveUserUseCaseTest { 17 | 18 | private val userRepository = mockk() 19 | private lateinit var retrieveUser: RetrieveUserUseCase 20 | 21 | @Before 22 | fun setup() { 23 | clearAllMocks() 24 | 25 | retrieveUser = RetrieveUserUseCase(userRepository) 26 | } 27 | 28 | @Test 29 | fun `invoking returns a flow of the User object`() = runBlocking { 30 | val user = User.Found(createUserDomain()) 31 | coEvery { userRepository.user } returns flowOf(user) 32 | 33 | val result = retrieveUser() 34 | 35 | assertEquals(user, result.first()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /libraries/database/src/test/java/com/chesire/nekome/database/converters/MapConverterTests.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.database.converters 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class MapConverterTests { 7 | 8 | @Test 9 | fun `fromMap converts to String from Map`() { 10 | val input = mapOf( 11 | "en" to "enValue", 12 | "jp" to "jpValue" 13 | ) 14 | val expected = """{"en":"enValue","jp":"jpValue"}""" 15 | 16 | val result = MapConverter().fromMap(input) 17 | 18 | assertEquals(expected, result) 19 | } 20 | 21 | @Test 22 | fun `toMap converts to Map from String`() { 23 | val input = """{"en":"enValue","jp":"jpValue"}""" 24 | val expected = mapOf( 25 | "en" to "enValue", 26 | "jp" to "jpValue" 27 | ) 28 | 29 | val result = MapConverter().toMap(input) 30 | 31 | assertEquals(expected, result) 32 | } 33 | 34 | @Test 35 | fun `toMap converts to empty Map from empty String`() { 36 | val input = """""" 37 | val expected = emptyMap() 38 | 39 | val result = MapConverter().toMap(input) 40 | 41 | assertEquals(expected, result) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/chesire/nekome/injection/SeriesModule.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.injection 2 | 3 | import com.chesire.nekome.binders.UserProviderBinder 4 | import com.chesire.nekome.database.dao.SeriesDao 5 | import com.chesire.nekome.datasource.series.SeriesMapper 6 | import com.chesire.nekome.datasource.series.SeriesRepository 7 | import com.chesire.nekome.datasource.series.UserProvider 8 | import com.chesire.nekome.datasource.series.remote.SeriesApi 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | 14 | /** 15 | * Dagger [Module] for the [com.chesire.nekome.series] package. 16 | */ 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | object SeriesModule { 20 | 21 | /** 22 | * Provides a [UserProvider] to the series repository. 23 | */ 24 | @Provides 25 | fun bindUserProvider(binder: UserProviderBinder): UserProvider = binder 26 | 27 | /** 28 | * Provides the [SeriesRepository] to the dependency graph. 29 | */ 30 | @Provides 31 | fun provideSeriesRepository( 32 | dao: SeriesDao, 33 | api: SeriesApi, 34 | user: UserProvider, 35 | map: SeriesMapper 36 | ) = SeriesRepository(dao, api, user, map) 37 | } 38 | -------------------------------------------------------------------------------- /core/compose/src/main/java/com/chesire/nekome/core/compose/LazyListStateExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.core.compose 2 | 3 | import androidx.compose.foundation.lazy.LazyListState 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.derivedStateOf 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.mutableIntStateOf 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.runtime.setValue 10 | 11 | /** 12 | * Returns whether the lazy list is currently scrolling up. 13 | */ 14 | @Composable 15 | fun LazyListState.isScrollingUp(): Boolean { 16 | var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } 17 | var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) } 18 | return remember(this) { 19 | derivedStateOf { 20 | if (previousIndex != firstVisibleItemIndex) { 21 | previousIndex > firstVisibleItemIndex 22 | } else { 23 | previousScrollOffset >= firstVisibleItemScrollOffset 24 | }.also { 25 | previousIndex = firstVisibleItemIndex 26 | previousScrollOffset = firstVisibleItemScrollOffset 27 | } 28 | } 29 | }.value 30 | } 31 | -------------------------------------------------------------------------------- /features/series/src/main/java/com/chesire/nekome/app/series/item/core/UpdateItemUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.chesire.nekome.app.series.item.core 2 | 3 | import com.chesire.nekome.core.flags.UserSeriesStatus 4 | import com.chesire.nekome.datasource.series.SeriesRepository 5 | import com.github.michaelbull.result.Result 6 | import com.github.michaelbull.result.mapEither 7 | import javax.inject.Inject 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.withContext 10 | 11 | class UpdateItemUseCase @Inject constructor(private val seriesRepo: SeriesRepository) { 12 | 13 | suspend operator fun invoke(updateItemModel: UpdateItemModel): Result { 14 | return withContext(Dispatchers.IO) { 15 | seriesRepo.updateSeries( 16 | userSeriesId = updateItemModel.userSeriesId, 17 | progress = updateItemModel.progress, 18 | status = updateItemModel.newStatus, 19 | rating = updateItemModel.rating 20 | ).mapEither( 21 | success = {}, 22 | failure = {} 23 | ) 24 | } 25 | } 26 | } 27 | 28 | data class UpdateItemModel( 29 | val userSeriesId: Int, 30 | val progress: Int, 31 | val newStatus: UserSeriesStatus, 32 | val rating: Int 33 | ) 34 | --------------------------------------------------------------------------------