├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yaml │ ├── feature-request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md ├── release-drafter-config.yml ├── renovate.json └── workflows │ ├── android-spotless.yml │ ├── android-test.yml │ ├── auto-approve.yml │ ├── first-contribution-greeting.yaml │ ├── gradle-dokka.yml │ ├── gradle-wrapper-validation.yaml │ ├── release-drafter.yml │ └── version-updater.yaml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── jarRepositories.xml ├── kotlinScripting.xml ├── kotlinc.xml ├── migrations.xml ├── render.experimental.xml └── vcs.xml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .config │ └── configuration.properties ├── .gitignore ├── .graphqlconfig ├── build.gradle.kts ├── proguard-rules.pro ├── sampledata │ ├── avatar │ │ └── 7859175.webp │ ├── nifty.json │ ├── nifty │ │ └── 7382.webp │ └── user.json ├── schema.graphql ├── schemas │ └── co.anitrend.retrofit.graphql.data.arch.database.SampleStore │ │ └── 1.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── graphql │ │ ├── fragments │ │ ├── bucket │ │ │ └── BucketFile.graphql │ │ ├── market │ │ │ └── MarketPlaceListingCore.graphql │ │ ├── paging │ │ │ └── PageInfo.graphql │ │ ├── repository │ │ │ └── RepositoryCore.graphql │ │ └── user │ │ │ └── UserCore.graphql │ │ ├── mutations │ │ ├── bucket │ │ │ └── UploadToStorageBucket.graphql │ │ └── repository │ │ │ └── AddReactionToIssue.graphql │ │ └── queries │ │ ├── bucket │ │ └── StorageBucketFiles.graphql │ │ ├── market │ │ └── GetMarketPlaceApps.graphql │ │ ├── repository │ │ ├── FindIssueId.graphql │ │ └── GetRepository.graphql │ │ ├── search │ │ └── GeneralSearch.graphql │ │ └── user │ │ └── GetCurrentUser.graphql │ ├── kotlin │ └── co │ │ └── anitrend │ │ └── retrofit │ │ └── graphql │ │ ├── core │ │ ├── SampleApp.kt │ │ ├── extension │ │ │ └── CoreExt.kt │ │ ├── helpers │ │ │ ├── logger │ │ │ │ └── KoinLogger.kt │ │ │ └── runtime │ │ │ │ └── UncaughtExceptionHandler.kt │ │ ├── koin │ │ │ └── CoreModules.kt │ │ ├── model │ │ │ └── FragmentItem.kt │ │ ├── settings │ │ │ └── Settings.kt │ │ └── view │ │ │ ├── SampleActivity.kt │ │ │ └── SampleListFragment.kt │ │ ├── data │ │ ├── api │ │ │ ├── common │ │ │ │ └── EndpointType.kt │ │ │ ├── converter │ │ │ │ ├── SampleConverterFactory.kt │ │ │ │ └── request │ │ │ │ │ └── SampleRequestConverter.kt │ │ │ ├── interceptor │ │ │ │ └── GraphClientInterceptor.kt │ │ │ └── provider │ │ │ │ ├── RetrofitProvider.kt │ │ │ │ └── extensions │ │ │ │ └── ProviderExtensions.kt │ │ ├── arch │ │ │ ├── Annotations.kt │ │ │ ├── common │ │ │ │ └── SampleMapper.kt │ │ │ ├── controller │ │ │ │ ├── SampleController.kt │ │ │ │ ├── policy │ │ │ │ │ ├── OfflineControllerPolicy.kt │ │ │ │ │ └── OnlineControllerPolicy.kt │ │ │ │ └── strategy │ │ │ │ │ └── ControllerStrategy.kt │ │ │ ├── database │ │ │ │ ├── ISampleStore.kt │ │ │ │ ├── SampleStore.kt │ │ │ │ ├── common │ │ │ │ │ └── ILocalSource.kt │ │ │ │ ├── converter │ │ │ │ │ └── SampleTypeConverters.kt │ │ │ │ └── extensions │ │ │ │ │ └── DatabaseExtensions.kt │ │ │ ├── extensions │ │ │ │ └── SampleExtensions.kt │ │ │ ├── koin │ │ │ │ └── Modules.kt │ │ │ └── mapper │ │ │ │ └── GraphQLMapper.kt │ │ ├── authentication │ │ │ └── settings │ │ │ │ └── IAuthenticationSettings.kt │ │ ├── bucket │ │ │ ├── datasource │ │ │ │ └── remote │ │ │ │ │ └── BucketRemoteSource.kt │ │ │ ├── helper │ │ │ │ └── UploadMutationHelper.kt │ │ │ ├── koin │ │ │ │ └── Modules.kt │ │ │ ├── mapper │ │ │ │ ├── BucketResponseMapper.kt │ │ │ │ └── UploadResponseMapper.kt │ │ │ ├── model │ │ │ │ ├── StorageBucket.kt │ │ │ │ ├── node │ │ │ │ │ └── BucketFileNode.kt │ │ │ │ └── upload │ │ │ │ │ ├── UploadResult.kt │ │ │ │ │ └── mutation │ │ │ │ │ └── UploadMutation.kt │ │ │ ├── repository │ │ │ │ ├── BucketRepositoryImpl.kt │ │ │ │ └── upload │ │ │ │ │ └── UploadRepositoryImpl.kt │ │ │ ├── source │ │ │ │ ├── browse │ │ │ │ │ ├── BucketSourceImpl.kt │ │ │ │ │ └── contract │ │ │ │ │ │ └── BucketSource.kt │ │ │ │ └── upload │ │ │ │ │ ├── BucketUploadSourceImpl.kt │ │ │ │ │ └── contract │ │ │ │ │ └── BucketUploadSource.kt │ │ │ └── usecase │ │ │ │ ├── BucketUseCaseImpl.kt │ │ │ │ └── upload │ │ │ │ └── UploadUseCaseImpl.kt │ │ ├── graphql │ │ │ ├── common │ │ │ │ └── INode.kt │ │ │ ├── connection │ │ │ │ └── IConnection.kt │ │ │ ├── edge │ │ │ │ └── IEdge.kt │ │ │ └── paging │ │ │ │ └── PagingInfo.kt │ │ ├── market │ │ │ ├── converters │ │ │ │ └── MarketPlaceConverters.kt │ │ │ ├── datasource │ │ │ │ ├── local │ │ │ │ │ └── MarketPlaceLocalSource.kt │ │ │ │ └── remote │ │ │ │ │ └── MarketPlaceRemoteSource.kt │ │ │ ├── entity │ │ │ │ └── MarketPlaceEntity.kt │ │ │ ├── koin │ │ │ │ └── Modules.kt │ │ │ ├── mapper │ │ │ │ └── MarketPlaceResponseMapper.kt │ │ │ ├── model │ │ │ │ ├── MarketPlaceListings.kt │ │ │ │ ├── connection │ │ │ │ │ └── MarketPlaceListingConnection.kt │ │ │ │ ├── edge │ │ │ │ │ └── MarketPlaceListingEdge.kt │ │ │ │ ├── node │ │ │ │ │ └── MarketPlaceNode.kt │ │ │ │ └── query │ │ │ │ │ └── MarketPlaceListingQuery.kt │ │ │ ├── repository │ │ │ │ └── MarketPlaceRepositoryImpl.kt │ │ │ ├── source │ │ │ │ ├── MarketPlaceSourceImpl.kt │ │ │ │ └── contract │ │ │ │ │ └── MarketPlaceSource.kt │ │ │ └── usecase │ │ │ │ └── MarketPlaceUseCaseImpl.kt │ │ ├── search │ │ │ └── model │ │ │ │ └── SearchQuery.kt │ │ └── user │ │ │ ├── converters │ │ │ └── UserConverters.kt │ │ │ ├── datasource │ │ │ ├── local │ │ │ │ └── UserLocalSource.kt │ │ │ └── remote │ │ │ │ └── UserRemoteSource.kt │ │ │ ├── entity │ │ │ └── UserEntity.kt │ │ │ ├── koin │ │ │ └── Modules.kt │ │ │ ├── mapper │ │ │ └── UserResponseMapper.kt │ │ │ ├── model │ │ │ ├── Viewer.kt │ │ │ └── node │ │ │ │ └── UserNode.kt │ │ │ ├── repository │ │ │ └── UserRepositoryImpl.kt │ │ │ ├── source │ │ │ ├── UserSourceImpl.kt │ │ │ └── contract │ │ │ │ └── UserSource.kt │ │ │ └── usecase │ │ │ └── UserUseCaseImpl.kt │ │ ├── domain │ │ ├── common │ │ │ └── EntityId.kt │ │ ├── entities │ │ │ ├── bucket │ │ │ │ └── BucketFile.kt │ │ │ ├── market │ │ │ │ └── MarketPlaceListing.kt │ │ │ ├── registry │ │ │ │ └── RegistryItem.kt │ │ │ └── user │ │ │ │ └── User.kt │ │ ├── models │ │ │ ├── common │ │ │ │ └── IGraphQuery.kt │ │ │ └── enums │ │ │ │ └── SearchType.kt │ │ ├── repositories │ │ │ ├── BucketRepository.kt │ │ │ ├── MarketPlaceRepository.kt │ │ │ ├── UploadRepository.kt │ │ │ └── UserRepository.kt │ │ └── usecases │ │ │ ├── BucketUseCase.kt │ │ │ ├── MarketPlaceUseCase.kt │ │ │ ├── UploadUseCase.kt │ │ │ └── UserUseCase.kt │ │ └── sample │ │ ├── App.kt │ │ ├── di │ │ └── AppModules.kt │ │ ├── initializer │ │ ├── CoilInitializer.kt │ │ ├── KoinInitializer.kt │ │ └── TimberInitializer.kt │ │ ├── presenter │ │ ├── BucketPresenter.kt │ │ └── MainPresenter.kt │ │ ├── view │ │ ├── MainScreen.kt │ │ └── content │ │ │ ├── bucket │ │ │ ├── BucketContent.kt │ │ │ ├── ui │ │ │ │ ├── adapter │ │ │ │ │ └── BucketAdapter.kt │ │ │ │ └── controller │ │ │ │ │ ├── helper │ │ │ │ │ └── ControllerHelpers.kt │ │ │ │ │ └── model │ │ │ │ │ └── BucketFileItem.kt │ │ │ └── viewmodel │ │ │ │ ├── BucketViewModel.kt │ │ │ │ └── UploadViewModel.kt │ │ │ └── market │ │ │ ├── MarketPlaceContent.kt │ │ │ ├── ui │ │ │ ├── adapter │ │ │ │ ├── MarketPlaceAdapter.kt │ │ │ │ └── MarketPlaceCategoryAdapter.kt │ │ │ └── controller │ │ │ │ ├── helpers │ │ │ │ └── ControllerHelpers.kt │ │ │ │ └── model │ │ │ │ ├── MarketPlaceCategoryItem.kt │ │ │ │ └── MarketPlaceListingItem.kt │ │ │ └── viewmodel │ │ │ └── MarketPlaceViewModel.kt │ │ └── viewmodel │ │ └── MainViewModel.kt │ └── res │ ├── drawable │ ├── bottom_dialog_drawable.xml │ ├── ic_apps_24.xml │ ├── ic_cloud_upload_24.xml │ ├── ic_gesture_black_24dp.xml │ ├── ic_menu_adaptable_24dp.xml │ ├── ic_search_24dp.xml │ ├── ic_whatshot_24dp.xml │ ├── nav_item_background.xml │ └── nav_item_background_selected.xml │ ├── layout │ ├── activity_content.xml │ ├── activity_main.xml │ ├── bucket_content.xml │ ├── bucket_file_item.xml │ ├── market_place_category_item.xml │ ├── market_place_item.xml │ ├── nav_header_main.xml │ └── shared_list_content.xml │ ├── menu │ ├── main_menu.xml │ └── nav_menu.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-night │ ├── colors.xml │ └── styles.xml │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── settings.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── network_security_config.xml ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ ├── java │ └── co │ │ └── anitrend │ │ └── retrofit │ │ └── graphql │ │ └── buildSrc │ │ ├── module │ │ └── Modules.kt │ │ └── plugin │ │ ├── CorePlugin.kt │ │ ├── components │ │ ├── AndroidConfiguration.kt │ │ ├── AndroidDependencies.kt │ │ ├── AndroidOptions.kt │ │ ├── AndroidPlugins.kt │ │ └── PropertiesComponent.kt │ │ ├── extensions │ │ ├── DependencyHandlerExtensions.kt │ │ └── ProjectExtensions.kt │ │ └── strategy │ │ └── DependencyStrategy.kt │ └── resources │ └── META-INF │ └── gradle-plugins │ └── co.anitrend.retrofit.graphql.properties ├── gradle.properties ├── gradle ├── libs.versions.toml ├── version.properties └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── logo │ ├── graphql-icon.svg │ ├── ic_launcher.zip │ └── ic_launcher_round.zip └── screenshots │ ├── assets_files.png │ ├── sample_img_001.png │ ├── sample_img_002.png │ ├── sample_img_003.png │ ├── sample_img_004.png │ └── todo_domain_files.png ├── jitpack.yml ├── library ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── kotlin │ │ └── io │ │ └── github │ │ └── wax911 │ │ └── library │ │ ├── annotation │ │ ├── GraphQuery.kt │ │ └── processor │ │ │ ├── GraphProcessor.kt │ │ │ ├── contract │ │ │ └── AbstractGraphProcessor.kt │ │ │ ├── fragment │ │ │ ├── FragmentAnalysis.kt │ │ │ ├── FragmentAnalyzer.kt │ │ │ ├── FragmentPatcher.kt │ │ │ ├── GraphRegexUtil.kt │ │ │ └── RegexFragmentAnalyzer.kt │ │ │ └── plugin │ │ │ ├── AssetManagerDiscoveryPlugin.kt │ │ │ └── contract │ │ │ └── AbstractDiscoveryPlugin.kt │ │ ├── converter │ │ ├── GraphConverter.kt │ │ ├── request │ │ │ └── GraphRequestConverter.kt │ │ └── response │ │ │ └── GraphResponseConverter.kt │ │ ├── logger │ │ ├── DefaultGraphLogger.kt │ │ ├── contract │ │ │ └── ILogger.kt │ │ └── core │ │ │ └── AbstractLogger.kt │ │ ├── model │ │ ├── attribute │ │ │ └── GraphError.kt │ │ ├── body │ │ │ └── GraphContainer.kt │ │ └── request │ │ │ ├── PersistedQuery.kt │ │ │ ├── PersistedQueryUrlParameterBuilder.kt │ │ │ ├── PersistedQueryUrlParameters.kt │ │ │ ├── QueryContainer.kt │ │ │ └── QueryContainerBuilder.kt │ │ ├── persisted │ │ ├── contract │ │ │ └── IAutomaticPersistedQuery.kt │ │ └── query │ │ │ ├── AutomaticPersistedQueryCalculator.kt │ │ │ └── error │ │ │ └── AutomaticPersistedQueryErrors.kt │ │ ├── persistedquery │ │ ├── PersistedQueryErrors.kt │ │ └── PersistedQueryHashCalculator.kt │ │ └── util │ │ ├── GraphErrorUtil.kt │ │ └── Logger.kt │ └── test │ ├── kotlin │ └── io │ │ └── github │ │ └── wax911 │ │ └── library │ │ ├── annotation │ │ └── processor │ │ │ ├── GraphProcessorTest.kt │ │ │ ├── fragment │ │ │ ├── FragmentPatcherTest.kt │ │ │ ├── GraphRegexUtilTest.kt │ │ │ ├── Operation.kt │ │ │ └── RegexFragmentAnalyzerTest.kt │ │ │ └── plugin │ │ │ └── contract │ │ │ └── AbstractDiscoveryPluginTest.kt │ │ ├── helpers │ │ ├── ResourcesDiscoveryPlugin.kt │ │ ├── annotations │ │ │ └── MockAnnotationStubs.kt │ │ └── logger │ │ │ └── TestLogger.kt │ │ └── logger │ │ └── core │ │ └── AbstractLoggerTest.kt │ └── resources │ └── io │ └── github │ └── wax911 │ └── library │ └── helpers │ └── graphql │ ├── fragments │ ├── bucket │ │ └── BucketFile.graphql │ ├── market │ │ └── MarketPlaceListingCore.graphql │ ├── paging │ │ └── PageInfo.graphql │ ├── repository │ │ └── RepositoryCore.graphql │ └── user │ │ └── UserCore.graphql │ ├── mutations │ ├── bucket │ │ └── UploadToStorageBucket.graphql │ └── repository │ │ └── AddReactionToIssue.graphql │ └── queries │ ├── bucket │ └── StorageBucketFiles.graphql │ ├── market │ └── GetMarketPlaceApps.graphql │ ├── repository │ ├── FindIssueId.graphql │ └── GetRepository.graphql │ ├── search │ └── GeneralSearch.graphql │ └── user │ └── GetCurrentUser.graphql ├── scripts └── configuration.sh ├── settings.gradle.kts └── spotless └── copyright.kt /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report any issues regarding the project and help us identify bugs quicker 4 | labels: ["bug"] 5 | --- 6 | 7 | # Issue Guidelines 8 | 9 | Before opening a new issue, please take a moment to review our [**community guidelines**](https://github.com/AniTrend/retrofit-graphql/blob/master/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved. 10 | 11 | **You may find an answer in already closed issues**: 12 | https://github.com/AniTrend/retrofit-graphql/issues?q=is%3Aissue+is%3Aclosed 13 | 14 | ## Description Of Bug 15 | 16 | 17 | ## Expected Behaviour 18 | 19 | 20 | ## Additional Context 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yaml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🗣 Ask a Question, Discuss 4 | url: https://github.com/anitrend/retrofit-graphql/discussions 5 | about: General discussion, suggest ideas, share concepts or resources or ask questions, like "How does this work 🤔?" 6 | - name: 🤗 Support the Project 7 | url: https://patreon.com/wax911 8 | about: Support the AniTrend financially. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Got a suggestion? Then this is what you should use 4 | labels: ["feature request"] 5 | --- 6 | 7 | # Issue Guidelines 8 | 9 | Before opening a new issue, please take a moment to review our [**community guidelines**](https://github.com/AniTrend/retrofit-graphql/blob/master/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved. 10 | 11 | **You may find an answer in already closed issues**: 12 | https://github.com/AniTrend/retrofit-graphql/issues?q=is%3Aissue+is%3Aclosed 13 | 14 | ## Feature Information 15 | 16 | 17 | ## Solution Information 18 | 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask any general question regarding the project, also if you use this if you don't know what category to use 4 | labels: ["question"] 5 | --- 6 | 7 | # Issue Guidelines 8 | 9 | Before opening a new issue, please take a moment to review our [**community guidelines**](https://github.com/AniTrend/retrofit-graphql/blob/master/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved. 10 | 11 | **You may find an answer in already closed issues**: 12 | https://github.com/AniTrend/retrofit-graphql/issues?q=is%3Aissue+is%3Aclosed 13 | 14 | ## Question 15 | 16 | 17 | ## Additional Context 18 | 19 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request Template 2 | 3 | Thank you for contributing! Please take a moment to review our [**contributing guidelines**](https://github.com/AniTrend/retrofit-graphql/blob/develop/CONTRIBUTING.md) 4 | to make the process easy and effective for everyone involved. 5 | 6 | **Please open an issue** before embarking on any significant pull request, especially those that 7 | add a new library or change existing tests, otherwise you risk spending a lot of time working 8 | on something that might not end up being merged into the project. 9 | 10 | Before opening a pull request, please ensure: 11 | 12 | 13 | 14 | - You have followed our [**contributing guidelines**](https://github.com/AniTrend/retrofit-graphql/blob/develop/CONTRIBUTING.md) 15 | - Double-check your branch is based on `develop` and targets `develop` (where applicable) 16 | - Pull request has tests (If applicable) 17 | - Code is well-commented, linted and follows project conventions 18 | - Documentation is updated (if necessary) 19 | - Description explains the issue/use-case resolved 20 | 21 | 22 | ## Description 23 | 24 | 25 | 26 | ## Types of changes 27 | 28 | - [ ] Bug fix (non-breaking change which fixes an issue) 29 | - [ ] New feature (non-breaking change which adds functionality) 30 | - [ ] Enhancement (Improve existing functionality) 31 | 32 | 33 | 34 | **IMPORTANT**: By submitting a patch, you agree to allow the project 35 | owners to license your work under the terms of the [Apache License](https://github.com/AniTrend/retrofit-graphql/blob/develop/LICENSE). 36 | -------------------------------------------------------------------------------- /.github/release-drafter-config.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: '$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | collapse-after: 5 8 | - title: '✨ Improvements' 9 | labels: 10 | - 'enhancement' 11 | collapse-after: 5 12 | - title: '🛠️ Bug Fixes' 13 | labels: 14 | - 'bug fix' 15 | collapse-after: 5 16 | - title: '🧰 Maintenance' 17 | labels: 18 | - 'refactor' 19 | collapse-after: 5 20 | - title: '📦 Dependencies' 21 | labels: 22 | - 'dependencies' 23 | collapse-after: 5 24 | - title: '🔖 Other changes' 25 | autolabeler: 26 | - label: 'docs' 27 | files: 28 | - '*.md' 29 | branch: 30 | - '/docs{0,1}\/.+/' 31 | - label: 'bug fix' 32 | branch: 33 | - '/bugfix\/.+/' 34 | - '/hotfix\/.+/' 35 | - label: 'feature' 36 | branch: 37 | - '/feature\/.+/' 38 | - label: 'enhancement' 39 | branch: 40 | - '/enhancement\/.+/' 41 | - label: 'refactor' 42 | branch: 43 | - '/refactor\/.+/' 44 | - label: 'dependencies' 45 | branch: 46 | - '/dependencies\/.+/' 47 | - '/renovate\/.+/' 48 | change-template: '- $TITLE by @$AUTHOR in #$NUMBER' 49 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 50 | version-resolver: 51 | major: 52 | labels: 53 | - 'breaking' 54 | minor: 55 | labels: 56 | - 'enhancement' 57 | - 'feature' 58 | patch: 59 | labels: 60 | - 'bug fix' 61 | - 'dependencies' 62 | - 'refactor' 63 | default: patch 64 | exclude-labels: 65 | - 'skip-changelog' 66 | template: | 67 | # What's Changed 68 | 69 | $CHANGES 70 | 71 | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION 72 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "branchPrefix": "dependencies/", 7 | "reviewers": ["wax911"], 8 | "baseBranches": [ 9 | "develop" 10 | ], 11 | "automerge": true, 12 | "automergeType": "pr", 13 | "automergeStrategy": "rebase", 14 | "packageRules": [ 15 | { 16 | "groupName": "Sample Module Dependencies", 17 | "packagePatterns": [ 18 | "^androidx\\.activity", 19 | "^androidx\\.fragment", 20 | "^androidx\\.core", 21 | "^androidx\\.constraintlayout", 22 | "^androidx\\.swiperefreshlayout", 23 | "^androidx\\.preference", 24 | "^androidx\\.recyclerview", 25 | "^com\\.google\\.android\\.material", 26 | "^io\\.coil-kt", 27 | "^com\\.github\\.anitrend\\.support-arch", 28 | "^com\\.jakewharton\\.threetenabp", 29 | "^com\\.jakewharton\\.timber" 30 | ], 31 | "matchPaths": [ 32 | "sample/**" 33 | ] 34 | }, 35 | { 36 | "groupName": "Room Dependencies", 37 | "packagePatterns": [ 38 | "^androidx\\.room" 39 | ], 40 | "matchPaths": [ 41 | "sample/**" 42 | ] 43 | }, 44 | { 45 | "groupName": "Paging Dependencies", 46 | "packagePatterns": [ 47 | "^androidx\\.paging" 48 | ], 49 | "matchPaths": [ 50 | "sample/**" 51 | ] 52 | }, 53 | { 54 | "groupName": "Kotlinx Serialization", 55 | "packagePatterns": [ 56 | "^org\\.jetbrains\\.kotlinx\\.serialization" 57 | ], 58 | "matchPaths": [ 59 | "sample/**" 60 | ] 61 | }, 62 | { 63 | "groupName": "Koin Dependencies", 64 | "packagePatterns": [ 65 | "^org\\.koin" 66 | ], 67 | "matchPaths": [ 68 | "sample/**" 69 | ] 70 | }, 71 | { 72 | "groupName": "Testing Dependencies", 73 | "packagePatterns": [ 74 | "^junit", 75 | "^io\\.mockk", 76 | "^androidx\\.test", 77 | "^androidx\\.test\\.espresso", 78 | "^androidx\\.test\\.ext" 79 | ] 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /.github/workflows/android-spotless.yml: -------------------------------------------------------------------------------- 1 | name: android-spotless 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: 8 | - '*' 9 | 10 | env: 11 | CI: "true" 12 | 13 | jobs: 14 | android-spotless: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-java@v4 19 | with: 20 | java-version: 21 21 | distribution: 'adopt' 22 | - name: Setup Gradle 23 | uses: gradle/actions/setup-gradle@v4 24 | with: 25 | cache-read-only: ${{ github.ref != 'refs/heads/develop' }} 26 | - name: Grant execute permission for gradlew 27 | run: chmod +x gradlew 28 | - name: Run spotless check 29 | run: ./gradlew spotlessCheck 30 | -------------------------------------------------------------------------------- /.github/workflows/android-test.yml: -------------------------------------------------------------------------------- 1 | name: android-unit-test 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: 8 | - develop 9 | 10 | env: 11 | CI: "true" 12 | 13 | jobs: 14 | android-unit-test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-java@v4 19 | with: 20 | java-version: 21 21 | distribution: 'adopt' 22 | - name: Setup Gradle 23 | uses: gradle/actions/setup-gradle@v4 24 | with: 25 | cache-read-only: ${{ github.ref != 'refs/heads/develop' }} 26 | - name: Grant execute permission for gradlew 27 | run: chmod +x gradlew 28 | - name: Run tests 29 | run: ./gradlew test 30 | - name: Publish Test Report 31 | uses: mikepenz/action-junit-report@v5 32 | if: always() # always run even if the previous step fails 33 | with: 34 | report_paths: '**/build/test-results/**/TEST-*.xml' 35 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | name: auto-approve 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - synchronize 8 | workflow_dispatch: 9 | inputs: 10 | pullRequestNumber: 11 | description: Pull request number to auto-approve 12 | required: false 13 | 14 | jobs: 15 | auto-approve: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | pull-requests: write 19 | if: github.actor == 'renovate[bot]' || github.event_name == 'workflow_dispatch' 20 | steps: 21 | - uses: hmarr/auto-approve-action@v3 22 | with: 23 | review-message: "Auto approved automated PR" 24 | pull-request-number: ${{ github.event.inputs.pullRequestNumber }} -------------------------------------------------------------------------------- /.github/workflows/first-contribution-greeting.yaml: -------------------------------------------------------------------------------- 1 | name: first-contribution-greeting 2 | 3 | on: [pull_request_target, issues] 4 | 5 | jobs: 6 | first-contribution-greeting: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: actions/first-interaction@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-message: "Hey there! Thank you for creating an issue :) Please take a moment to review our [**community guidelines**](https://github.com/AniTrend/retrofit-graphql/blob/develop/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved." 16 | pr-message: "Hey there! Thank you for this PR :) Please take a moment to review our [**community guidelines**](https://github.com/AniTrend/retrofit-graphql/blob/develop/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved." -------------------------------------------------------------------------------- /.github/workflows/gradle-dokka.yml: -------------------------------------------------------------------------------- 1 | name: gradle-dokka 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | 7 | env: 8 | CI: "true" 9 | 10 | jobs: 11 | gradle-dokka: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-java@v4 16 | with: 17 | java-version: 21 18 | distribution: 'adopt' 19 | - name: Setup Gradle 20 | uses: gradle/actions/setup-gradle@v4 21 | with: 22 | cache-read-only: ${{ github.ref != 'refs/heads/develop' }} 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Generate docs with dokka 26 | run: ./gradlew dokkaHtml 27 | 28 | - name: Deploy 🚀 29 | uses: JamesIves/github-pages-deploy-action@v4.7.3 30 | with: 31 | branch: docs # The branch the action should deploy to. 32 | folder: library/build/docs/dokka # The folder the action should deploy. 33 | -------------------------------------------------------------------------------- /.github/workflows/gradle-wrapper-validation.yaml: -------------------------------------------------------------------------------- 1 | name: gradle-wrapper-validation 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | gradle-wrapper-validation: 6 | name: "Validation" 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: gradle/actions/wrapper-validation@v4 11 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: release-drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'develop' 7 | pull_request_target: 8 | # Only following types are handled by the action, but one can default to all as well 9 | types: [ opened, reopened, synchronize ] 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | update-draft-release: 16 | permissions: 17 | contents: write 18 | pull-requests: write 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: release-drafter/release-drafter@v6 22 | id: release_drafter 23 | with: 24 | config-name: release-drafter-config.yml 25 | disable-autolabeler: false 26 | commitish: develop 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | - name: Repository Dispatch 30 | uses: peter-evans/repository-dispatch@v3 31 | with: 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | event-type: version-update-and-push 34 | client-payload: '{"version": "${{ steps.release_drafter.outputs.resolved_version }}"}' 35 | -------------------------------------------------------------------------------- /.github/workflows/version-updater.yaml: -------------------------------------------------------------------------------- 1 | name: version-update 2 | 3 | on: 4 | repository_dispatch: 5 | types: [version-update-and-push] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | version-update: 12 | permissions: 13 | contents: write 14 | pull-requests: write 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | ref: develop 21 | 22 | - name: Extract version information to file 23 | run: echo "${{ github.event.client_payload.version }}" > VERSION 24 | 25 | - name: Create version.properties using extracted version 26 | run: | 27 | IFS='.' read -ra VER <<< "$(cat VERSION)" 28 | MAJOR=$((VER[0] * 1000000000)) 29 | MINOR=$((VER[1] * 1000000)) 30 | PATCH=$((VER[2] * 1000)) 31 | CODE=$((MAJOR + MINOR + PATCH)) 32 | echo "version=$(cat VERSION)" > gradle/version.properties 33 | echo "code=$CODE" >> gradle/version.properties 34 | echo "name=v$(cat VERSION)" >> gradle/version.properties 35 | 36 | - name: Clean up version information file 37 | run: rm VERSION 38 | 39 | - name: Preview created version.properties 40 | run: cat gradle/version.properties 41 | 42 | - name: Create Pull Request 43 | uses: peter-evans/create-pull-request@v7 44 | with: 45 | token: ${{ secrets.GITHUB_TOKEN }} 46 | signoff: true 47 | delete-branch: true 48 | commit-message: "automation: update version.properties" 49 | author: "Author " 50 | title: "platform: automated version update" 51 | body: | 52 | This PR was automatically generated to update `version.properties` 53 | branch: platform/update-version-meta-data 54 | labels: "skip-changelog" 55 | assignees: "wax911" 56 | reviewers: "wax911" 57 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/kotlinScripting.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/render.experimental.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change via a pull request. 4 | 5 | Please note we have a code of conduct, please follow it in all your interactions with the project. 6 | 7 | ## Contributing Guidelines 8 | 9 | Please ensure your pull request adheres to the following guidelines: 10 | 11 | - Search previous suggestions for duplicates before making a new one. 12 | - Make an individual pull request for each suggestion or feature. 13 | - Use the following format: `- [Bookmark Title](link): Description.` 14 | - Titles should be [capitalized](http://grammar.yourdictionary.com/capitalization/rules-for-capitalization-in-titles.html). 15 | - New categories or improvements to the existing categorization are welcome. 16 | - Be sure not to stage any files in excluded in .gitignore 17 | - Check your spelling and grammar. 18 | 19 | See [code of conduct](./CODE_OF_CONDUCT.md) 20 | -------------------------------------------------------------------------------- /app/.config/configuration.properties: -------------------------------------------------------------------------------- 1 | # Configuration gradle file 2 | github="https://api.github.com" 3 | # Change this to whatever your staging server will be running on 4 | # TODO: Change me to your test domain 5 | bucket="http://pi.hole:4000" 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/.graphqlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "name": "retrofit-graphql schemas", 3 | "schemaPath": "schema.graphql", 4 | "extensions": { 5 | "endpoints": { 6 | "GitHub Endpoint": { 7 | "url": "https://api.github.com/graphql", 8 | "headers": { 9 | "user-agent": "JS GraphQL", 10 | "authorization": "bearer token" 11 | }, 12 | "introspect": true 13 | }, 14 | "Bucket Endpoint": { 15 | "url": "http://pi.hole:4000/graphql", 16 | "headers": { 17 | "user-agent": "JS GraphQL" 18 | }, 19 | "introspect": true 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/proguard-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.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 | -------------------------------------------------------------------------------- /app/sampledata/avatar/7859175.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/sampledata/avatar/7859175.webp -------------------------------------------------------------------------------- /app/sampledata/nifty.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "MDE4Ok1hcmtldHBsYWNlTGlzdGluZzczODI=", 3 | "primaryCategory": { 4 | "description": "Organize, manage, and track your project with tools that build on top of issues and pull requests.", 5 | "name": "Project management" 6 | }, 7 | "secondaryCategory": { 8 | "description": "Get insights into how your teams are developing software using GitHub.", 9 | "name": "Reporting" 10 | }, 11 | "slug": "nifty-pm", 12 | "shortDescription": "Delivering the future of project management", 13 | "isPaid": false, 14 | "isVerified": false, 15 | "logoBackgroundColor": "ffffff", 16 | "logoUrl": "https://avatars3.githubusercontent.com/ml/7382?s=400&v=4", 17 | "name": "Nifty PM" 18 | } -------------------------------------------------------------------------------- /app/sampledata/nifty/7382.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/sampledata/nifty/7382.webp -------------------------------------------------------------------------------- /app/sampledata/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "viewer": { 3 | "avatarUrl": "https://avatars2.githubusercontent.com/u/7859175?u=f7ab29e931e304d6dba4a1cb8413233dc27b3a8b&v=4", 4 | "bio": "Lover of all things technology related, a healthy curious mind and happy personality", 5 | "company": null, 6 | "id": "MDQ6VXNlcjc4NTkxNzU=", 7 | "status": { 8 | "createdAt": "2019-01-10T07:42:53Z", 9 | "emoji": ":rocket:", 10 | "message": "Working on a top secret mission" 11 | }, 12 | "login": "wax911" 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/fragments/bucket/BucketFile.graphql: -------------------------------------------------------------------------------- 1 | fragment BucketFile on File { 2 | contentType 3 | filename 4 | id 5 | url 6 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/fragments/market/MarketPlaceListingCore.graphql: -------------------------------------------------------------------------------- 1 | fragment MarketPlaceListingCore on MarketplaceListing { 2 | id 3 | primaryCategory { 4 | name 5 | } 6 | secondaryCategory { 7 | name 8 | } 9 | slug 10 | shortDescription 11 | isPaid 12 | isVerified 13 | logoBackgroundColor 14 | logoUrl 15 | name 16 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/fragments/paging/PageInfo.graphql: -------------------------------------------------------------------------------- 1 | fragment PageInfo on PageInfo { 2 | endCursor 3 | hasNextPage 4 | hasPreviousPage 5 | startCursor 6 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/fragments/repository/RepositoryCore.graphql: -------------------------------------------------------------------------------- 1 | fragment RepositoryCore on Repository { 2 | createdAt 3 | description 4 | forkCount 5 | homepageUrl 6 | id 7 | isFork 8 | isLocked 9 | isPrivate 10 | isTemplate 11 | name 12 | projectsUrl 13 | url 14 | viewerHasStarred 15 | watchers { 16 | totalCount 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/fragments/user/UserCore.graphql: -------------------------------------------------------------------------------- 1 | fragment UserCore on User { 2 | avatarUrl 3 | bio 4 | company 5 | id 6 | status { 7 | createdAt 8 | emoji 9 | message 10 | } 11 | login 12 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/mutations/bucket/UploadToStorageBucket.graphql: -------------------------------------------------------------------------------- 1 | mutation UploadToStorageBucket($upload: Upload!) { 2 | uploadFile(fileData: $upload) { 3 | ...BucketFile 4 | } 5 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/mutations/repository/AddReactionToIssue.graphql: -------------------------------------------------------------------------------- 1 | mutation AddReactionToIssue($reactionInput: AddReactionInput!) { 2 | addReaction(input: $reactionInput) { 3 | reaction { 4 | content 5 | } 6 | subject { 7 | id 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/queries/bucket/StorageBucketFiles.graphql: -------------------------------------------------------------------------------- 1 | query StorageBucketFiles { 2 | files { 3 | ...BucketFile 4 | } 5 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/queries/market/GetMarketPlaceApps.graphql: -------------------------------------------------------------------------------- 1 | query GetMarketPlaceApps($first: Int, $after: String, $before: String) { 2 | marketplaceListings(first: $first, after: $after, before: $before) { 3 | edges { 4 | cursor 5 | node { 6 | ... MarketPlaceListingCore 7 | } 8 | } 9 | pageInfo { 10 | ... PageInfo 11 | } 12 | totalCount 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/queries/repository/FindIssueId.graphql: -------------------------------------------------------------------------------- 1 | query FindIssueID($owner: String!, $name: String!, $issueId: Int!) { 2 | repository(owner:$owner, name:$name) { 3 | issue(number:$issueId) { 4 | id 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/queries/repository/GetRepository.graphql: -------------------------------------------------------------------------------- 1 | query GetRepository($name: String!, $owner: String!) { 2 | repository(name: $name, owner: $owner) { 3 | ... RepositoryCore 4 | } 5 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/queries/search/GeneralSearch.graphql: -------------------------------------------------------------------------------- 1 | query GeneralSearch($after: String, $before: String, $first: Int, $last: Int, $query: String!, $type: SearchType!) { 2 | search(after: $after,before: $before,first: $first,last: $last,query: $query,type: $type) { 3 | edges { 4 | cursor 5 | node { 6 | ... RepositoryCore 7 | ... UserCore 8 | ... MarketPlaceListingCore 9 | } 10 | textMatches { 11 | fragment 12 | highlights { 13 | beginIndice, 14 | endIndice, 15 | text 16 | } 17 | property 18 | } 19 | } 20 | pageInfo { 21 | ... PageInfo 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/assets/graphql/queries/user/GetCurrentUser.graphql: -------------------------------------------------------------------------------- 1 | query GetCurrentUser { 2 | viewer { 3 | ... UserCore 4 | } 5 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/core/SampleApp.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.core 2 | 3 | import android.app.Application 4 | import androidx.appcompat.app.AppCompatDelegate 5 | import io.wax911.emojify.EmojiManager 6 | 7 | abstract class SampleApp : Application() { 8 | 9 | /** 10 | * Uncaught exception handler 11 | */ 12 | protected abstract fun createUncaughtExceptionHandler() 13 | 14 | /** 15 | * Called when the application is starting, before any activity, service, 16 | * or receiver objects (excluding content providers) have been created. 17 | * 18 | * Implementations should be as quick as possible (for example using 19 | * lazy initialization of state) since the time spent in this function 20 | * directly impacts the performance of starting the first activity, 21 | * service, or receiver in a process. 22 | * 23 | * If you override this method, be sure to call `super.onCreate()`. 24 | * 25 | * Be aware that direct boot may also affect callback order on 26 | * Android [android.os.Build.VERSION_CODES.N] and later devices. 27 | * Until the user unlocks the device, only direct boot aware components are 28 | * allowed to run. You should consider that all direct boot unaware 29 | * components, including such [android.content.ContentProvider], are 30 | * disabled until user unlock happens, especially when component callback 31 | * order matters. 32 | */ 33 | override fun onCreate() { 34 | super.onCreate() 35 | AppCompatDelegate.setDefaultNightMode( 36 | AppCompatDelegate.MODE_NIGHT_NO 37 | ) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/core/extension/CoreExt.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.core.extension 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.os.Bundle 5 | import android.os.Parcelable 6 | import androidx.annotation.IdRes 7 | import androidx.fragment.app.FragmentManager 8 | import androidx.fragment.app.FragmentTransaction 9 | import androidx.fragment.app.commit 10 | import co.anitrend.arch.ui.view.image.SupportImageView 11 | import co.anitrend.retrofit.graphql.core.model.FragmentItem 12 | import coil.Coil 13 | import coil.load 14 | import coil.request.Disposable 15 | import coil.transform.Transformation 16 | import coil.transition.CrossfadeTransition 17 | import timber.log.Timber 18 | 19 | /** 20 | * Helper extension for changing parcels to bundles 21 | */ 22 | fun Parcelable.toBundle(key: String) = 23 | Bundle().apply { 24 | putParcelable(key, this@toBundle) 25 | } 26 | 27 | 28 | /** 29 | * Checks for existing fragment in [FragmentManager], if one exists that is used otherwise 30 | * a new instance is created. 31 | * 32 | * @return tag of the fragment 33 | * 34 | * @see androidx.fragment.app.commit 35 | */ 36 | inline fun FragmentManager.commit( 37 | @IdRes contentFrame: Int, 38 | fragmentItem: FragmentItem<*>?, 39 | action: FragmentTransaction.() -> Unit 40 | ) : String? { 41 | if (fragmentItem != null) { 42 | val fragmentTag = fragmentItem.tag() 43 | val backStack = findFragmentByTag(fragmentTag) 44 | 45 | commit { 46 | action() 47 | backStack?.let { 48 | replace(contentFrame, it, fragmentTag) 49 | } ?: replace( 50 | contentFrame, 51 | fragmentItem.fragment, 52 | fragmentItem.parameter, 53 | fragmentTag 54 | ) 55 | } 56 | return fragmentTag 57 | } 58 | 59 | Timber.v("FragmentItem model is null") 60 | return null 61 | } 62 | 63 | /** 64 | * Image loader helper for [Coil] 65 | */ 66 | fun SupportImageView.using( 67 | imageUrl: String?, 68 | placeHolder: Drawable? = null, 69 | vararg transformations: Transformation = emptyArray() 70 | ): Disposable { 71 | return load(imageUrl) { 72 | placeholder(placeHolder) 73 | transformations(transformations.toList()) 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/core/helpers/logger/KoinLogger.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.core.helpers.logger 2 | 3 | import org.koin.core.logger.Level 4 | import org.koin.core.logger.Logger 5 | import org.koin.core.logger.MESSAGE 6 | import timber.log.Timber 7 | 8 | /** 9 | * A logger proxy for Timber 10 | */ 11 | internal class KoinLogger( 12 | logLevel: Level 13 | ) : Logger(logLevel) { 14 | override fun display(level: Level, msg: MESSAGE) { 15 | when (level) { 16 | Level.DEBUG -> Timber.d(msg) 17 | Level.INFO -> Timber.i(msg) 18 | Level.ERROR -> Timber.e(msg) 19 | Level.NONE -> {} 20 | Level.WARNING -> Timber.w(msg) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/core/helpers/runtime/UncaughtExceptionHandler.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.core.helpers.runtime 2 | 3 | import timber.log.Timber 4 | 5 | /** 6 | * An uncaught exception handler for the application 7 | */ 8 | internal class UncaughtExceptionHandler( 9 | private val exceptionHandler: Thread.UncaughtExceptionHandler? 10 | ) : Thread.UncaughtExceptionHandler { 11 | 12 | /** 13 | * Method invoked when the given thread terminates due to the given uncaught exception. 14 | * 15 | * Any exception thrown by this method will be ignored by the Java Virtual Machine. 16 | * 17 | * @param thread the thread 18 | * @param throwable the exception 19 | */ 20 | override fun uncaughtException(thread: Thread, throwable: Throwable) { 21 | Timber.e(throwable) 22 | exceptionHandler?.uncaughtException(thread, throwable) 23 | } 24 | 25 | companion object { 26 | private val TAG = 27 | UncaughtExceptionHandler::class.java.simpleName 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/core/koin/CoreModules.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.core.koin 2 | 3 | import co.anitrend.arch.extension.dispatchers.SupportDispatcher 4 | import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher 5 | import co.anitrend.retrofit.graphql.core.settings.Settings 6 | import coil.ImageLoader 7 | import coil.ImageLoaderFactory 8 | import okhttp3.Cache 9 | import okhttp3.OkHttpClient 10 | import okhttp3.logging.HttpLoggingInterceptor 11 | import org.koin.android.ext.koin.androidApplication 12 | import org.koin.android.ext.koin.androidContext 13 | import org.koin.core.qualifier.named 14 | import org.koin.dsl.binds 15 | import org.koin.dsl.module 16 | import java.util.concurrent.TimeUnit 17 | 18 | private val coreModule = module { 19 | factory { 20 | Settings( 21 | androidContext() 22 | ) 23 | } binds(Settings.BINDINGS) 24 | single { 25 | SupportDispatcher() 26 | } 27 | } 28 | 29 | private val coilModules = module { 30 | factory(named("coilOkHttp")) { 31 | OkHttpClient.Builder() 32 | .retryOnConnectionFailure(true) 33 | .connectTimeout(15, TimeUnit.SECONDS) 34 | .readTimeout(15, TimeUnit.SECONDS) 35 | .addInterceptor( 36 | HttpLoggingInterceptor().apply { 37 | level = HttpLoggingInterceptor.Level.HEADERS 38 | } 39 | ) 40 | .cache( 41 | Cache(androidApplication().cacheDir, 50 * 1024 * 1024) 42 | ).build() 43 | } 44 | factory { 45 | ImageLoaderFactory { 46 | ImageLoader.Builder(androidContext()) 47 | .crossfade(true) 48 | .allowHardware(true) 49 | .okHttpClient { 50 | get(named("coilOkHttp")) 51 | } 52 | .build() 53 | } 54 | } 55 | } 56 | 57 | val coreModules = listOf(coreModule, coilModules) -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/core/model/FragmentItem.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.core.model 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | 6 | /** 7 | * Fragment loader helper 8 | */ 9 | data class FragmentItem( 10 | val parameter: Bundle? = null, 11 | val fragment: Class 12 | ) { 13 | fun tag(): String = fragment.simpleName 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/core/settings/Settings.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.core.settings 2 | 3 | import android.content.Context 4 | import co.anitrend.arch.extension.preference.SupportPreference 5 | import co.anitrend.arch.extension.settings.BooleanSetting 6 | import co.anitrend.arch.extension.settings.IntSetting 7 | import co.anitrend.arch.extension.settings.StringSetting 8 | import co.anitrend.retrofit.graphql.data.authentication.settings.IAuthenticationSettings 9 | import co.anitrend.retrofit.graphql.sample.R 10 | 11 | class Settings(context: Context) : SupportPreference(context), IAuthenticationSettings { 12 | 13 | override val authenticatedUserId = StringSetting( 14 | key = R.string.setting_authenticated_user_id, 15 | default = IAuthenticationSettings.INVALID_USER_ID, 16 | resources = context.resources, 17 | preference = this, 18 | ) 19 | 20 | override val isNewInstallation = BooleanSetting( 21 | key = R.string.setting_is_new_installation, 22 | default = true, 23 | resources = context.resources, 24 | preference = this, 25 | ) 26 | 27 | override val versionCode = IntSetting( 28 | key = R.string.setting_version_code, 29 | default = 1, 30 | resources = context.resources, 31 | preference = this, 32 | ) 33 | 34 | companion object { 35 | val BINDINGS = arrayOf( 36 | Settings::class, SupportPreference::class, IAuthenticationSettings::class 37 | ) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/core/view/SampleListFragment.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.core.view 2 | 3 | import co.anitrend.arch.core.model.ISupportViewModelState 4 | import co.anitrend.arch.ui.fragment.list.SupportFragmentList 5 | import org.koin.android.scope.AndroidScopeComponent 6 | import org.koin.androidx.scope.fragmentScope 7 | import org.koin.core.component.KoinScopeComponent 8 | 9 | abstract class SampleListFragment : SupportFragmentList(), 10 | AndroidScopeComponent, KoinScopeComponent { 11 | 12 | override val scope by fragmentScope() 13 | 14 | /** 15 | * Proxy for a view model state if one exists 16 | */ 17 | override fun viewModelState(): ISupportViewModelState<*>? = null 18 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/api/common/EndpointType.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.api.common 2 | 3 | import co.anitrend.retrofit.graphql.sample.BuildConfig 4 | import okhttp3.HttpUrl 5 | import okhttp3.HttpUrl.Companion.toHttpUrl 6 | 7 | internal enum class EndpointType(val url: HttpUrl) { 8 | GITHUB(BuildConfig.github.toHttpUrl()), 9 | BUCKET(BuildConfig.bucket.toHttpUrl()); 10 | 11 | companion object { 12 | const val BASE_ENDPOINT_PATH = "/graphql" 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/api/converter/request/SampleRequestConverter.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.api.converter.request 2 | 3 | import co.anitrend.retrofit.graphql.data.bucket.helper.UploadMutationHelper 4 | import co.anitrend.retrofit.graphql.data.bucket.helper.UploadMutationHelper.containsImage 5 | import co.anitrend.retrofit.graphql.data.bucket.helper.UploadMutationHelper.createMultiPartBody 6 | import com.google.gson.Gson 7 | import io.github.wax911.library.annotation.processor.contract.AbstractGraphProcessor 8 | import io.github.wax911.library.converter.request.GraphRequestConverter 9 | import io.github.wax911.library.model.request.QueryContainerBuilder 10 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 11 | import okhttp3.RequestBody 12 | import okhttp3.RequestBody.Companion.toRequestBody 13 | 14 | /** 15 | * Request converter that handles how [QueryContainerBuilder] should be converted to 16 | * a [RequestBody] 17 | */ 18 | internal class SampleRequestConverter( 19 | annotations: Array, 20 | processor: AbstractGraphProcessor, 21 | gson: Gson 22 | ) : GraphRequestConverter(annotations, processor, gson) { 23 | 24 | /** 25 | * Converter for the request body, gets the GraphQL query from the method annotation 26 | * and constructs a GraphQL request body to send over the network. 27 | * 28 | * @param containerBuilder The constructed builder method of your query with variables 29 | * @return Request body 30 | */ 31 | override fun convert(containerBuilder: QueryContainerBuilder): RequestBody { 32 | val miniQuery = graphProcessor.getQuery(methodAnnotations) 33 | val mediaType = MIME_TYPE.toMediaTypeOrNull() 34 | 35 | /** 36 | * Simply checking if [containerBuilder] has a key which we believe to be an image, we 37 | * can also make use of [UploadMutationHelper.supportsFileUpload] as well e.g. 38 | * `methodAnnotations.supportsFileUpload()` 39 | */ 40 | if (containerBuilder.containsImage()) 41 | return containerBuilder.createMultiPartBody(gson, miniQuery, mediaType) 42 | 43 | val queryContainer = containerBuilder.setQuery(miniQuery).build() 44 | val queryJson = gson.toJson(queryContainer) 45 | return queryJson.toRequestBody(mediaType) 46 | } 47 | 48 | companion object { 49 | internal const val MIME_TYPE = "application/json" 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/api/interceptor/GraphClientInterceptor.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.api.interceptor 2 | 3 | import co.anitrend.retrofit.graphql.data.api.common.EndpointType 4 | import co.anitrend.retrofit.graphql.data.api.converter.request.SampleRequestConverter 5 | import co.anitrend.retrofit.graphql.sample.BuildConfig 6 | import io.github.wax911.library.converter.GraphConverter 7 | import okhttp3.Interceptor 8 | import okhttp3.Response 9 | 10 | /** 11 | * ClientInterceptor interceptor add headers dynamically adds accept headers. 12 | * The context in which an [Interceptor] may be parallel or asynchronous depending 13 | * on the dispatching caller, as such take care to assure thread safety 14 | */ 15 | internal class GraphClientInterceptor: Interceptor { 16 | 17 | override fun intercept(chain: Interceptor.Chain): Response { 18 | val original = chain.request() 19 | val requestBuilder = original.newBuilder() 20 | .header(ACCEPT, SampleRequestConverter.MIME_TYPE) 21 | .method(original.method, original.body) 22 | 23 | if (original.header(CONTENT_TYPE).isNullOrEmpty()) 24 | requestBuilder.header(CONTENT_TYPE, GraphConverter.MIME_TYPE) 25 | 26 | if (original.url.host == EndpointType.GITHUB.url.host) 27 | requestBuilder.header("Authorization", "bearer ${BuildConfig.token}") 28 | 29 | return chain.proceed(requestBuilder.build()) 30 | } 31 | 32 | companion object { 33 | const val CONTENT_TYPE = "Content-Type" 34 | const val ACCEPT = "Accept" 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/api/provider/extensions/ProviderExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.api.provider.extensions 2 | 3 | import co.anitrend.retrofit.graphql.data.api.common.EndpointType 4 | import co.anitrend.retrofit.graphql.data.api.provider.RetrofitProvider 5 | import org.koin.core.scope.Scope 6 | 7 | /** 8 | * Facade for supplying retrofit interface types 9 | */ 10 | internal inline fun Scope.api(endpointType: EndpointType): T = 11 | RetrofitProvider.provideRetrofit(endpointType, this).create(T::class.java) -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/Annotations.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch 2 | 3 | /** 4 | * Example annotation to use as an indicator that a method supports file uploads 5 | * 6 | * @see co.anitrend.retrofit.graphql.data.bucket.helper.UploadMutationHelper 7 | */ 8 | @Retention(AnnotationRetention.RUNTIME) 9 | @Target(AnnotationTarget.FUNCTION) 10 | internal annotation class GraphMultiPartUpload -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/common/SampleMapper.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch.common 2 | 3 | import co.anitrend.arch.data.mapper.SupportResponseMapper 4 | import co.anitrend.retrofit.graphql.domain.common.EntityId 5 | 6 | /** 7 | * @param E entity 8 | * @param M model 9 | */ 10 | internal abstract class SampleMapper { 11 | protected abstract fun from(): SupportResponseMapper 12 | protected abstract fun to(): SupportResponseMapper 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OfflineControllerPolicy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package co.anitrend.retrofit.graphql.data.arch.controller.policy 18 | 19 | import co.anitrend.arch.domain.entities.RequestError 20 | import co.anitrend.arch.request.callback.RequestCallback 21 | import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy 22 | import timber.log.Timber 23 | 24 | /** 25 | * Does not run any connectivity check before prior to execution, this is useful for sources 26 | * that may have caching on the network level through interception or cache-control from 27 | * the origin server. 28 | */ 29 | internal class OfflineControllerPolicy private constructor() : ControllerStrategy() { 30 | 31 | /** 32 | * Execute a task under an implementation strategy 33 | * 34 | * @param callback event emitter 35 | * @param block what will be executed 36 | */ 37 | override suspend fun invoke(callback: RequestCallback, block: suspend () -> D?): D? { 38 | runCatching { 39 | block() 40 | callback.recordSuccess() 41 | }.exceptionOrNull()?.also { e -> 42 | Timber.e(e) 43 | when (e) { 44 | is RequestError -> callback.recordFailure(e) 45 | else -> callback.recordFailure(RequestError(e)) 46 | } 47 | } 48 | return null 49 | } 50 | 51 | companion object { 52 | fun create() = 53 | OfflineControllerPolicy() 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OnlineControllerPolicy.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch.controller.policy 2 | 3 | import co.anitrend.arch.domain.entities.RequestError 4 | import co.anitrend.arch.extension.network.SupportConnectivity 5 | import co.anitrend.arch.request.callback.RequestCallback 6 | import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy 7 | import timber.log.Timber 8 | 9 | /** 10 | * Runs connectivity check before prior to execution 11 | */ 12 | internal class OnlineControllerPolicy private constructor( 13 | private val connectivity: SupportConnectivity 14 | ) : ControllerStrategy() { 15 | 16 | 17 | /** 18 | * Execute a task under an implementation strategy 19 | * 20 | * @param callback event emitter 21 | * @param block what will be executed 22 | */ 23 | override suspend fun invoke( 24 | callback: RequestCallback, 25 | block: suspend () -> D? 26 | ): D? { 27 | runCatching { 28 | if (connectivity.isConnected) 29 | block() 30 | else 31 | throw RequestError( 32 | "No internet connection", 33 | "Make sure you have an active internet connection" 34 | ) 35 | }.onSuccess { result -> 36 | callback.recordSuccess() 37 | return result 38 | }.onFailure { exception -> 39 | Timber.e(exception) 40 | when (exception) { 41 | is RequestError -> callback.recordFailure(exception) 42 | else -> callback.recordFailure(RequestError(exception)) 43 | } 44 | } 45 | 46 | return null 47 | } 48 | 49 | companion object { 50 | fun create(connectivity: SupportConnectivity) = 51 | OnlineControllerPolicy( 52 | connectivity 53 | ) 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/strategy/ControllerStrategy.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch.controller.strategy 2 | 3 | import co.anitrend.arch.request.callback.RequestCallback 4 | 5 | internal abstract class ControllerStrategy { 6 | 7 | /** 8 | * Execute a task under an implementation strategy 9 | * 10 | * @param callback event emitter 11 | * @param block what will be executed 12 | */ 13 | internal abstract suspend operator fun invoke( 14 | callback: RequestCallback, 15 | block: suspend () -> D? 16 | ): D? 17 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/database/ISampleStore.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch.database 2 | 3 | import co.anitrend.retrofit.graphql.data.market.datasource.local.MarketPlaceLocalSource 4 | import co.anitrend.retrofit.graphql.data.user.datasource.local.UserLocalSource 5 | 6 | internal interface ISampleStore { 7 | fun appStoreDao(): MarketPlaceLocalSource 8 | fun userDao(): UserLocalSource 9 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/database/SampleStore.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch.database 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import androidx.room.TypeConverters 8 | import co.anitrend.retrofit.graphql.data.arch.database.converter.SampleTypeConverters 9 | import co.anitrend.retrofit.graphql.data.market.entity.MarketPlaceEntity 10 | import co.anitrend.retrofit.graphql.data.user.entity.UserEntity 11 | 12 | @Database( 13 | entities = [ 14 | MarketPlaceEntity::class, UserEntity::class 15 | ], 16 | version = SampleStore.DATABASE_SCHEMA_VERSION 17 | ) 18 | 19 | @TypeConverters( 20 | value = [SampleTypeConverters::class] 21 | ) 22 | internal abstract class SampleStore: RoomDatabase(), ISampleStore { 23 | 24 | companion object { 25 | const val DATABASE_SCHEMA_VERSION = 1 26 | 27 | internal fun create(applicationContext: Context): ISampleStore { 28 | return Room.databaseBuilder( 29 | applicationContext, 30 | SampleStore::class.java, 31 | "sample-db" 32 | ).fallbackToDestructiveMigration() 33 | .build() 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/database/common/ILocalSource.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch.database.common 2 | 3 | import androidx.room.* 4 | 5 | @Dao 6 | internal interface ILocalSource { 7 | suspend fun count(): Int 8 | suspend fun clear() 9 | 10 | /** 11 | * Inserts a new item into the database ignoring items with the same primary key, 12 | * for both insert or update behavior use: [upsert] 13 | * 14 | * @param attribute item/s to insert 15 | */ 16 | @Insert(onConflict = OnConflictStrategy.IGNORE) 17 | suspend fun insert(attribute: T) 18 | 19 | /** 20 | * Inserts new items into the database ignoring items with the same primary key, 21 | * for both insert or update behavior use: [upsert] 22 | * 23 | * @param attribute item/s to insert 24 | */ 25 | @Insert(onConflict = OnConflictStrategy.IGNORE) 26 | suspend fun insert(attribute: List) 27 | 28 | /** 29 | * Updates an item in the underlying database 30 | * 31 | * @param attribute item/s to update 32 | */ 33 | @Update(onConflict = OnConflictStrategy.REPLACE) 34 | suspend fun update(attribute: T) 35 | 36 | /** 37 | * Updates a list of items in the underlying database 38 | * 39 | * @param attribute item/s to update 40 | */ 41 | @Update(onConflict = OnConflictStrategy.REPLACE) 42 | suspend fun update(attribute: List) 43 | 44 | /** 45 | * Deletes an item from the underlying database 46 | * 47 | * @param attribute item/s to delete 48 | */ 49 | @Delete 50 | suspend fun delete(attribute: T) 51 | 52 | /** 53 | * Deletes a list of items from the underlying database 54 | * 55 | * @param attribute item/s to delete 56 | */ 57 | @Delete 58 | suspend fun delete(attribute: List) 59 | 60 | /** 61 | * Inserts or updates matching attributes on conflict 62 | * 63 | * @param attribute item/s to insert 64 | */ 65 | @Insert(onConflict = OnConflictStrategy.REPLACE) 66 | suspend fun upsert(attribute: T) 67 | 68 | /** 69 | * Inserts or updates matching attributes on conflict 70 | * 71 | * @param attribute item/s to insert 72 | */ 73 | @Insert(onConflict = OnConflictStrategy.REPLACE) 74 | suspend fun upsert(attribute: List) 75 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/database/converter/SampleTypeConverters.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch.database.converter 2 | 3 | import androidx.room.TypeConverter 4 | import co.anitrend.retrofit.graphql.data.arch.database.extensions.fromCommaSeparatedValues 5 | import co.anitrend.retrofit.graphql.data.arch.database.extensions.toCommaSeparatedValues 6 | 7 | internal object SampleTypeConverters { 8 | 9 | @TypeConverter 10 | @JvmStatic 11 | fun fromListOfString(source: List) = source.toCommaSeparatedValues() 12 | 13 | @TypeConverter 14 | @JvmStatic 15 | fun toListOfString(source: String) = source.fromCommaSeparatedValues() 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/database/extensions/DatabaseExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch.database.extensions 2 | 3 | import co.anitrend.arch.extension.ext.empty 4 | import co.anitrend.retrofit.graphql.data.arch.database.ISampleStore 5 | import org.koin.core.scope.Scope 6 | 7 | internal fun List<*>.toCommaSeparatedValues(): String { 8 | return if (isNotEmpty()) { 9 | joinToString(separator = ",") 10 | } else 11 | String.empty() 12 | } 13 | 14 | internal fun String.fromCommaSeparatedValues(): List { 15 | return if (isNotBlank()) 16 | split(',') 17 | else 18 | emptyList() 19 | } 20 | 21 | internal inline fun > String.toEnum(): T { 22 | val `class` = T::class.java 23 | return `class`.enumConstants?.first { it.name == this }!! 24 | } 25 | 26 | internal fun Enum<*>.fromEnum(): String { 27 | return name 28 | } 29 | 30 | /** 31 | * Facade for obtaining a database contract 32 | */ 33 | internal fun Scope.db() = get() -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/mapper/GraphQLMapper.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.arch.mapper 2 | 3 | import co.anitrend.arch.data.mapper.SupportResponseMapper 4 | import io.github.wax911.library.model.body.GraphContainer 5 | import timber.log.Timber 6 | 7 | /** 8 | * GraphQLMapper specific mapper, assures that all requests respond with [GraphContainer] 9 | * as the root tree object. 10 | * 11 | * Making it easier for us to implement error logging and provide better error messages 12 | */ 13 | internal abstract class GraphQLMapper : SupportResponseMapper() { 14 | 15 | /** 16 | * Simple logger for empty response 17 | */ 18 | protected fun onEmptyResponse() { 19 | Timber.v( 20 | "onResponseDatabaseInsert -> mappedData is empty" 21 | ) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/authentication/settings/IAuthenticationSettings.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.authentication.settings 2 | 3 | import co.anitrend.arch.extension.settings.contract.AbstractSetting 4 | 5 | interface IAuthenticationSettings { 6 | val authenticatedUserId: AbstractSetting 7 | 8 | companion object { 9 | const val INVALID_USER_ID: String = "" 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/datasource/remote/BucketRemoteSource.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.datasource.remote 2 | 3 | import co.anitrend.retrofit.graphql.data.api.common.EndpointType 4 | import co.anitrend.retrofit.graphql.data.arch.GraphMultiPartUpload 5 | import co.anitrend.retrofit.graphql.data.bucket.model.StorageBucket 6 | import co.anitrend.retrofit.graphql.data.bucket.model.upload.UploadResult 7 | import io.github.wax911.library.annotation.GraphQuery 8 | import io.github.wax911.library.model.body.GraphContainer 9 | import io.github.wax911.library.model.request.QueryContainerBuilder 10 | import retrofit2.Response 11 | import retrofit2.http.Body 12 | import retrofit2.http.POST 13 | 14 | internal interface BucketRemoteSource { 15 | 16 | @POST(EndpointType.BASE_ENDPOINT_PATH) 17 | @GraphQuery("StorageBucketFiles") 18 | suspend fun getStorageBucketFiles( 19 | @Body builder: QueryContainerBuilder 20 | ): Response> 21 | 22 | /** 23 | * [GraphMultiPartUpload] is just a simple annotation to 24 | * indicate the characteristics of this method. 25 | */ 26 | @GraphMultiPartUpload 27 | @POST(EndpointType.BASE_ENDPOINT_PATH) 28 | @GraphQuery("UploadToStorageBucket") 29 | suspend fun uploadToStorageBucket( 30 | @Body builder: QueryContainerBuilder 31 | ): Response> 32 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/mapper/BucketResponseMapper.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.mapper 2 | 3 | import co.anitrend.retrofit.graphql.data.arch.mapper.GraphQLMapper 4 | import co.anitrend.retrofit.graphql.data.bucket.model.StorageBucket 5 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 6 | import co.anitrend.retrofit.graphql.sample.BuildConfig 7 | 8 | internal class BucketResponseMapper : GraphQLMapper>() { 9 | /** 10 | * Inserts the given object into the implemented room database, 11 | * 12 | * @param mappedData mapped object from [onResponseMapFrom] to insert into the database 13 | */ 14 | override suspend fun onResponseDatabaseInsert(mappedData: List) { 15 | // not going to persist anything into the db 16 | } 17 | 18 | /** 19 | * Creates mapped objects and handles the database operations which may be required to map various objects, 20 | * 21 | * @param source the incoming data source type 22 | * @return mapped object that will be consumed by [onResponseDatabaseInsert] 23 | */ 24 | override suspend fun onResponseMapFrom(source: StorageBucket): List { 25 | return source.files.map { node -> 26 | BucketFile( 27 | id = node.id, 28 | contentType = node.contentType, 29 | fileName = node.filename, 30 | url = "${BuildConfig.bucket}/${node.url}" 31 | ) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/mapper/UploadResponseMapper.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.mapper 2 | 3 | import co.anitrend.retrofit.graphql.data.arch.mapper.GraphQLMapper 4 | import co.anitrend.retrofit.graphql.data.bucket.model.upload.UploadResult 5 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 6 | 7 | internal class UploadResponseMapper : GraphQLMapper() { 8 | /** 9 | * Inserts the given object into the implemented room database, 10 | * 11 | * @param mappedData mapped object from [onResponseMapFrom] to insert into the database 12 | */ 13 | override suspend fun onResponseDatabaseInsert(mappedData: BucketFile) { 14 | // not going to persist anything into the db 15 | } 16 | 17 | /** 18 | * Creates mapped objects and handles the database operations which may be required to map various objects, 19 | * 20 | * @param source the incoming data source type 21 | * @return mapped object that will be consumed by [onResponseDatabaseInsert] 22 | */ 23 | override suspend fun onResponseMapFrom(source: UploadResult): BucketFile { 24 | return BucketFile( 25 | id = source.uploaded.id, 26 | contentType = source.uploaded.contentType, 27 | fileName = source.uploaded.filename, 28 | url = source.uploaded.url 29 | ) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/model/StorageBucket.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.model 2 | 3 | import co.anitrend.retrofit.graphql.data.bucket.model.node.BucketFileNode 4 | import com.google.gson.annotations.SerializedName 5 | 6 | internal data class StorageBucket( 7 | @SerializedName("files") val files: List 8 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/model/node/BucketFileNode.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.model.node 2 | 3 | import co.anitrend.retrofit.graphql.data.graphql.common.INode 4 | import com.google.gson.annotations.SerializedName 5 | 6 | internal data class BucketFileNode( 7 | @SerializedName("id") override val id: String, 8 | @SerializedName("contentType") val contentType: String, 9 | @SerializedName("filename") val filename: String, 10 | @SerializedName("url") val url: String 11 | ) : INode -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/model/upload/UploadResult.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.model.upload 2 | 3 | import co.anitrend.retrofit.graphql.data.bucket.model.node.BucketFileNode 4 | import com.google.gson.annotations.SerializedName 5 | 6 | internal data class UploadResult( 7 | @SerializedName("uploadFile") val uploaded: BucketFileNode 8 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/model/upload/mutation/UploadMutation.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.model.upload.mutation 2 | 3 | import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery 4 | 5 | data class UploadMutation( 6 | val upload: String 7 | ) : IGraphQuery { 8 | override fun toMap() = 9 | mapOf(KEY to upload) 10 | 11 | companion object { 12 | internal const val KEY = "upload" 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/BucketRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.repository 2 | 3 | import co.anitrend.arch.data.repository.SupportRepository 4 | import co.anitrend.arch.data.state.DataState 5 | import co.anitrend.arch.data.state.DataState.Companion.create 6 | import co.anitrend.retrofit.graphql.data.bucket.source.browse.contract.BucketSource 7 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 8 | import co.anitrend.retrofit.graphql.domain.repositories.BucketRepository 9 | 10 | internal class BucketRepositoryImpl( 11 | private val source: BucketSource 12 | ) : SupportRepository(source), BucketRepositoryContract { 13 | override fun getAllFiles() = 14 | source.create( 15 | model = source() 16 | ) 17 | } 18 | 19 | typealias BucketRepositoryContract = BucketRepository>> -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/upload/UploadRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.repository.upload 2 | 3 | import co.anitrend.arch.data.repository.SupportRepository 4 | import co.anitrend.arch.data.state.DataState 5 | import co.anitrend.arch.data.state.DataState.Companion.create 6 | import co.anitrend.retrofit.graphql.data.bucket.source.upload.contract.BucketUploadSource 7 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 8 | import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery 9 | import co.anitrend.retrofit.graphql.domain.repositories.UploadRepository 10 | 11 | internal class UploadRepositoryImpl( 12 | private val source: BucketUploadSource 13 | ) : SupportRepository(source), UploadRepositoryContract { 14 | 15 | override fun uploadToBucket(mutation: IGraphQuery) = 16 | source.create( 17 | model = source(mutation) 18 | ) 19 | } 20 | 21 | typealias UploadRepositoryContract = UploadRepository> -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/BucketSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.source.browse 2 | 3 | import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher 4 | import co.anitrend.arch.request.callback.RequestCallback 5 | import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy 6 | import co.anitrend.retrofit.graphql.data.arch.extensions.controller 7 | import co.anitrend.retrofit.graphql.data.bucket.datasource.remote.BucketRemoteSource 8 | import co.anitrend.retrofit.graphql.data.bucket.mapper.BucketResponseMapper 9 | import co.anitrend.retrofit.graphql.data.bucket.source.browse.contract.BucketSource 10 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 11 | import io.github.wax911.library.model.request.QueryContainerBuilder 12 | import kotlinx.coroutines.CoroutineDispatcher 13 | import kotlinx.coroutines.async 14 | import kotlinx.coroutines.flow.MutableStateFlow 15 | 16 | internal class BucketSourceImpl( 17 | private val mapper: BucketResponseMapper, 18 | private val remoteSource: BucketRemoteSource, 19 | private val strategy: ControllerStrategy>, 20 | override val dispatcher: ISupportDispatcher, 21 | ) : BucketSource() { 22 | 23 | override val observable = 24 | MutableStateFlow?>(null) 25 | 26 | override suspend fun getStorageBucketFiles(requestCallback: RequestCallback) { 27 | val deferred = async { 28 | val queryBuilder = QueryContainerBuilder() 29 | remoteSource.getStorageBucketFiles( 30 | queryBuilder 31 | ) 32 | } 33 | 34 | val controller = 35 | mapper.controller(strategy, dispatcher) 36 | 37 | val result = controller(deferred, requestCallback) 38 | observable.value = result 39 | } 40 | 41 | /** 42 | * Clears data sources (databases, preferences, e.t.c) 43 | * 44 | * @param context Dispatcher context to run in 45 | */ 46 | override suspend fun clearDataSource(context: CoroutineDispatcher) { 47 | observable.value = null 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/contract/BucketSource.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.source.browse.contract 2 | 3 | import co.anitrend.arch.data.source.core.SupportCoreDataSource 4 | import co.anitrend.arch.request.callback.RequestCallback 5 | import co.anitrend.arch.request.model.Request 6 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.StateFlow 9 | import kotlinx.coroutines.flow.mapNotNull 10 | import kotlinx.coroutines.launch 11 | 12 | internal abstract class BucketSource : SupportCoreDataSource() { 13 | 14 | protected abstract val observable: StateFlow?> 15 | 16 | protected abstract suspend fun getStorageBucketFiles(requestCallback: RequestCallback) 17 | 18 | operator fun invoke(): Flow> { 19 | scope.launch { 20 | requestHelper.runIfNotRunning( 21 | Request.Default("getStorageBucketFiles", Request.Type.INITIAL) 22 | ) { 23 | getStorageBucketFiles(it) 24 | } 25 | } 26 | return observable.mapNotNull { it } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/BucketUploadSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.source.upload 2 | 3 | import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher 4 | import co.anitrend.arch.request.callback.RequestCallback 5 | import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy 6 | import co.anitrend.retrofit.graphql.data.arch.extensions.controller 7 | import co.anitrend.retrofit.graphql.data.bucket.datasource.remote.BucketRemoteSource 8 | import co.anitrend.retrofit.graphql.data.bucket.mapper.UploadResponseMapper 9 | import co.anitrend.retrofit.graphql.data.bucket.source.upload.contract.BucketUploadSource 10 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 11 | import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery 12 | import io.github.wax911.library.model.request.QueryContainerBuilder 13 | import kotlinx.coroutines.CoroutineDispatcher 14 | import kotlinx.coroutines.async 15 | import kotlinx.coroutines.flow.MutableStateFlow 16 | 17 | internal class BucketUploadSourceImpl( 18 | private val mapper: UploadResponseMapper, 19 | private val remoteSource: BucketRemoteSource, 20 | private val strategy: ControllerStrategy, 21 | override val dispatcher: ISupportDispatcher, 22 | ) : BucketUploadSource() { 23 | 24 | override val observable = 25 | MutableStateFlow(null) 26 | 27 | override suspend fun uploadToStorageBucket( 28 | mutation: IGraphQuery, 29 | requestCallback: RequestCallback, 30 | ) { 31 | val deferred = async { 32 | val queryBuilder = QueryContainerBuilder() 33 | .putVariables(mutation.toMap()) 34 | remoteSource.uploadToStorageBucket(queryBuilder) 35 | } 36 | 37 | val controller = 38 | mapper.controller(strategy, dispatcher) 39 | 40 | val result = controller(deferred, requestCallback) 41 | observable.value = result 42 | } 43 | 44 | /** 45 | * Clears data sources (databases, preferences, e.t.c) 46 | * 47 | * @param context Dispatcher context to run in 48 | */ 49 | override suspend fun clearDataSource(context: CoroutineDispatcher) { 50 | observable.value = null 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/contract/BucketUploadSource.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.source.upload.contract 2 | 3 | import co.anitrend.arch.data.source.core.SupportCoreDataSource 4 | import co.anitrend.arch.request.callback.RequestCallback 5 | import co.anitrend.arch.request.model.Request 6 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 7 | import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.StateFlow 10 | import kotlinx.coroutines.flow.mapNotNull 11 | import kotlinx.coroutines.launch 12 | 13 | internal abstract class BucketUploadSource: SupportCoreDataSource() { 14 | 15 | protected abstract val observable: StateFlow 16 | 17 | protected abstract suspend fun uploadToStorageBucket( 18 | mutation: IGraphQuery, 19 | requestCallback: RequestCallback 20 | ) 21 | 22 | operator fun invoke(mutation: IGraphQuery): Flow { 23 | scope.launch { 24 | requestHelper.runIfNotRunning( 25 | Request.Default("uploadToStorageBucket", Request.Type.INITIAL) 26 | ) { 27 | uploadToStorageBucket(mutation, it) 28 | } 29 | } 30 | return observable.mapNotNull { it } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/BucketUseCaseImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.usecase 2 | 3 | import co.anitrend.arch.data.repository.contract.ISupportRepository 4 | import co.anitrend.arch.data.state.DataState 5 | import co.anitrend.retrofit.graphql.data.bucket.repository.BucketRepositoryContract 6 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 7 | import co.anitrend.retrofit.graphql.domain.usecases.BucketUseCase 8 | 9 | internal class BucketUseCaseImpl( 10 | repository: BucketRepositoryContract 11 | ) : BucketUseCaseContract(repository) { 12 | /** 13 | * Informs underlying repositories or related components running background operations to stop 14 | */ 15 | override fun onCleared() { 16 | repository as ISupportRepository 17 | repository.onCleared() 18 | } 19 | } 20 | 21 | typealias BucketUseCaseContract = BucketUseCase>> -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/upload/UploadUseCaseImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.bucket.usecase.upload 2 | 3 | import co.anitrend.arch.data.repository.contract.ISupportRepository 4 | import co.anitrend.arch.data.state.DataState 5 | import co.anitrend.retrofit.graphql.data.bucket.repository.upload.UploadRepositoryContract 6 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 7 | import co.anitrend.retrofit.graphql.domain.usecases.UploadUseCase 8 | 9 | internal class UploadUseCaseImpl( 10 | repository: UploadRepositoryContract 11 | ) : UploadUseCaseContract(repository) { 12 | /** 13 | * Informs underlying repositories or related components running background operations to stop 14 | */ 15 | override fun onCleared() { 16 | repository as ISupportRepository 17 | repository.onCleared() 18 | } 19 | } 20 | 21 | typealias UploadUseCaseContract = UploadUseCase> -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/graphql/common/INode.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.graphql.common 2 | 3 | interface INode { 4 | val id: String 5 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/graphql/connection/IConnection.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.graphql.connection 2 | 3 | import co.anitrend.retrofit.graphql.data.graphql.common.INode 4 | import co.anitrend.retrofit.graphql.data.graphql.edge.IEdge 5 | import co.anitrend.retrofit.graphql.data.graphql.paging.PagingInfo 6 | import com.google.gson.annotations.SerializedName 7 | 8 | internal interface IConnection { 9 | @get:SerializedName("edges") val edges: List? 10 | @get:SerializedName("nodes") val nodes: List? 11 | @get:SerializedName("pageInfo") val pageInfo: PagingInfo? 12 | @get:SerializedName("totalCount") val totalCount: Int 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/graphql/edge/IEdge.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.graphql.edge 2 | 3 | import co.anitrend.retrofit.graphql.data.graphql.common.INode 4 | import com.google.gson.annotations.SerializedName 5 | 6 | internal interface IEdge { 7 | @get:SerializedName("cursor") 8 | val cursor: String 9 | @get:SerializedName("node") 10 | val node: INode? 11 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/graphql/paging/PagingInfo.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.graphql.paging 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | internal data class PagingInfo( 6 | @SerializedName("endCursor") val endCursor: String?, 7 | @SerializedName("hasNextPage") val hasNextPage: Boolean, 8 | @SerializedName("hasPreviousPage") val hasPreviousPage: Boolean, 9 | @SerializedName("startCursor") val startCursor: String? 10 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/converters/MarketPlaceConverters.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.converters 2 | 3 | import co.anitrend.arch.data.converter.SupportConverter 4 | import co.anitrend.retrofit.graphql.data.market.entity.MarketPlaceEntity 5 | import co.anitrend.retrofit.graphql.data.market.model.edge.MarketPlaceListingEdge 6 | import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing 7 | 8 | internal class MarketPlaceEntityConverter( 9 | override val fromType: (MarketPlaceEntity) -> MarketPlaceListing = { 10 | MarketPlaceListing( 11 | id = it.id, 12 | cursorId = it.cursorId, 13 | logoUrl = it.logoUrl, 14 | background = it.logoBackground, 15 | name = it.name, 16 | categories = it.categories, 17 | slug = it.slug, 18 | description = it.description, 19 | isPaid = it.isPaid, 20 | isVerified = it.isVerified 21 | ) 22 | }, 23 | override val toType: (MarketPlaceListing) -> MarketPlaceEntity = { throw NotImplementedError() } 24 | ) : SupportConverter() 25 | 26 | internal class MarketPlaceModelConverter( 27 | override val fromType: (MarketPlaceEntity) -> MarketPlaceListingEdge = { throw NotImplementedError() }, 28 | override val toType: (MarketPlaceListingEdge) -> MarketPlaceEntity = { 29 | val categories = listOf( 30 | it.node.primaryCategory.name, 31 | it.node.secondaryCategory?.name 32 | ).mapNotNull { name -> name } 33 | 34 | MarketPlaceEntity( 35 | id = it.node.id, 36 | cursorId = it.cursor, 37 | logoUrl = it.node.logo, 38 | logoBackground = it.node.background, 39 | name = it.node.name, 40 | categories = categories, 41 | slug = it.node.slug, 42 | description = it.node.description, 43 | isPaid = it.node.isPaid, 44 | isVerified = it.node.isVerified 45 | ) 46 | } 47 | ) : SupportConverter() 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/datasource/local/MarketPlaceLocalSource.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.datasource.local 2 | 3 | import androidx.paging.DataSource 4 | import androidx.room.Dao 5 | import androidx.room.Query 6 | import co.anitrend.retrofit.graphql.data.arch.database.common.ILocalSource 7 | import co.anitrend.retrofit.graphql.data.market.entity.MarketPlaceEntity 8 | 9 | @Dao 10 | internal interface MarketPlaceLocalSource : ILocalSource { 11 | 12 | @Query(""" 13 | select count(id) 14 | from market_place 15 | """) 16 | override suspend fun count(): Int 17 | 18 | @Query(""" 19 | delete from market_place 20 | """) 21 | override suspend fun clear() 22 | 23 | @Query( 24 | """ 25 | select * 26 | from market_place 27 | """ 28 | ) 29 | fun findAllByFactory(): DataSource.Factory 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/datasource/remote/MarketPlaceRemoteSource.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.datasource.remote 2 | 3 | import co.anitrend.retrofit.graphql.data.api.common.EndpointType 4 | import co.anitrend.retrofit.graphql.data.market.model.MarketPlaceListings 5 | import io.github.wax911.library.annotation.GraphQuery 6 | import io.github.wax911.library.model.body.GraphContainer 7 | import io.github.wax911.library.model.request.QueryContainerBuilder 8 | import retrofit2.Response 9 | import retrofit2.http.Body 10 | import retrofit2.http.POST 11 | 12 | internal interface MarketPlaceRemoteSource { 13 | 14 | @POST(EndpointType.BASE_ENDPOINT_PATH) 15 | @GraphQuery("GetMarketPlaceApps") 16 | suspend fun getMarketPlaceApps( 17 | @Body builder: QueryContainerBuilder 18 | ): Response> 19 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/entity/MarketPlaceEntity.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.Index 6 | import co.anitrend.retrofit.graphql.domain.common.EntityId 7 | 8 | @Entity( 9 | tableName = "market_place", 10 | primaryKeys = ["id"], 11 | indices = [ 12 | Index("cursor_id", unique = true) 13 | ] 14 | ) 15 | internal data class MarketPlaceEntity( 16 | @ColumnInfo(name = "id") override val id: String, 17 | @ColumnInfo(name = "cursor_id") val cursorId: String, 18 | @ColumnInfo(name = "logo_url") val logoUrl: String?, 19 | @ColumnInfo(name = "logo_background") val logoBackground: String, 20 | @ColumnInfo(name = "name") val name: String, 21 | @ColumnInfo(name = "categories") val categories: List, 22 | @ColumnInfo(name = "slug") val slug: String, 23 | @ColumnInfo(name = "description") val description: String, 24 | @ColumnInfo(name = "is_paid") val isPaid: Boolean, 25 | @ColumnInfo(name = "is_verified") val isVerified: Boolean 26 | ) : EntityId -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/mapper/MarketPlaceResponseMapper.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.mapper 2 | 3 | import co.anitrend.retrofit.graphql.data.arch.mapper.GraphQLMapper 4 | import co.anitrend.retrofit.graphql.data.market.converters.MarketPlaceModelConverter 5 | import co.anitrend.retrofit.graphql.data.market.datasource.local.MarketPlaceLocalSource 6 | import co.anitrend.retrofit.graphql.data.market.entity.MarketPlaceEntity 7 | import co.anitrend.retrofit.graphql.data.market.model.MarketPlaceListings 8 | 9 | internal class MarketPlaceResponseMapper( 10 | private val localSource: MarketPlaceLocalSource, 11 | private val converter: MarketPlaceModelConverter 12 | ) : GraphQLMapper>() { 13 | /** 14 | * Inserts the given object into the implemented room database, 15 | * 16 | * @param mappedData mapped object from [onResponseMapFrom] to insert into the database 17 | */ 18 | override suspend fun onResponseDatabaseInsert(mappedData: List) { 19 | if (mappedData.isNotEmpty()) 20 | localSource.upsert(mappedData) 21 | else 22 | onEmptyResponse() 23 | } 24 | 25 | /** 26 | * Creates mapped objects and handles the database operations which may be required to map various objects, 27 | * 28 | * @param source the incoming data source type 29 | * @return mapped object that will be consumed by [onResponseDatabaseInsert] 30 | */ 31 | override suspend fun onResponseMapFrom(source: MarketPlaceListings): List { 32 | val listings = source.marketplaceListings 33 | return converter.convertTo(listings.edges.orEmpty()) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/model/MarketPlaceListings.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.model 2 | 3 | import co.anitrend.retrofit.graphql.data.market.model.connection.MarketPlaceListingConnection 4 | import com.google.gson.annotations.SerializedName 5 | 6 | internal data class MarketPlaceListings( 7 | @SerializedName("marketplaceListings") 8 | val marketplaceListings: MarketPlaceListingConnection 9 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/model/connection/MarketPlaceListingConnection.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.model.connection 2 | 3 | import co.anitrend.retrofit.graphql.data.graphql.connection.IConnection 4 | import co.anitrend.retrofit.graphql.data.graphql.paging.PagingInfo 5 | import co.anitrend.retrofit.graphql.data.market.model.edge.MarketPlaceListingEdge 6 | import co.anitrend.retrofit.graphql.data.market.model.node.MarketPlaceNode 7 | 8 | internal data class MarketPlaceListingConnection( 9 | override val edges: List?, 10 | override val nodes: List?, 11 | override val pageInfo: PagingInfo?, 12 | override val totalCount: Int 13 | ) : IConnection -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/model/edge/MarketPlaceListingEdge.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.model.edge 2 | 3 | import co.anitrend.retrofit.graphql.data.graphql.edge.IEdge 4 | import co.anitrend.retrofit.graphql.data.market.model.node.MarketPlaceNode 5 | 6 | internal data class MarketPlaceListingEdge( 7 | override val cursor: String, 8 | override val node: MarketPlaceNode 9 | ) : IEdge -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/model/node/MarketPlaceNode.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.model.node 2 | 3 | import co.anitrend.retrofit.graphql.data.graphql.common.INode 4 | import com.google.gson.annotations.SerializedName 5 | 6 | internal data class MarketPlaceNode( 7 | @SerializedName("id") override val id: String, 8 | @SerializedName("primaryCategory") val primaryCategory: Category, 9 | @SerializedName("secondaryCategory") val secondaryCategory: Category?, 10 | @SerializedName("slug") val slug: String, 11 | @SerializedName("shortDescription") val description: String, 12 | @SerializedName("isPaid") val isPaid: Boolean, 13 | @SerializedName("isVerified") val isVerified: Boolean, 14 | @SerializedName("logoBackgroundColor") val background: String, 15 | @SerializedName("logoUrl") val logo: String?, 16 | @SerializedName("name") val name: String 17 | ) : INode { 18 | data class Category( 19 | @SerializedName("name") val name: String 20 | ) 21 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/model/query/MarketPlaceListingQuery.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.model.query 2 | 3 | import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery 4 | 5 | /** 6 | * Look up Marketplace listings 7 | * 8 | * @param after The elements in the list that come after the specified cursor 9 | * @param before The elements in the list that come before the specified cursor 10 | * @param first The first _n_ elements from the list 11 | */ 12 | data class MarketPlaceListingQuery( 13 | var after: String? = null, 14 | var before: String? = null, 15 | val first: Int 16 | ) : IGraphQuery { 17 | override fun toMap() = 18 | mapOf( 19 | "after" to after, 20 | "before" to before, 21 | "first" to first 22 | ) 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/repository/MarketPlaceRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.repository 2 | 3 | import androidx.paging.PagedList 4 | import co.anitrend.arch.data.repository.SupportRepository 5 | import co.anitrend.arch.data.state.DataState 6 | import co.anitrend.arch.data.state.DataState.Companion.create 7 | import co.anitrend.retrofit.graphql.data.market.source.contract.MarketPlaceSource 8 | import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing 9 | import co.anitrend.retrofit.graphql.domain.repositories.MarketPlaceRepository 10 | 11 | internal class MarketPlaceRepositoryImpl( 12 | private val source: MarketPlaceSource 13 | ) : SupportRepository(source), MarketPlaceRepositoryContract { 14 | 15 | override fun getMarketPlaceListings() = 16 | source.create( 17 | model = source() 18 | ) 19 | } 20 | 21 | typealias MarketPlaceRepositoryContract = MarketPlaceRepository>> -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/usecase/MarketPlaceUseCaseImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.market.usecase 2 | 3 | import androidx.paging.PagedList 4 | import co.anitrend.arch.data.repository.contract.ISupportRepository 5 | import co.anitrend.arch.data.state.DataState 6 | import co.anitrend.retrofit.graphql.data.market.repository.MarketPlaceRepositoryContract 7 | import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing 8 | import co.anitrend.retrofit.graphql.domain.usecases.MarketPlaceUseCase 9 | 10 | internal class MarketPlaceUseCaseImpl( 11 | repository: MarketPlaceRepositoryContract 12 | ) : MarketPlaceUseCaseContract(repository) { 13 | 14 | /** 15 | * Informs underlying repositories or related components running background operations to stop 16 | */ 17 | override fun onCleared() { 18 | repository as ISupportRepository 19 | repository.onCleared() 20 | } 21 | } 22 | 23 | typealias MarketPlaceUseCaseContract = MarketPlaceUseCase>> -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/search/model/SearchQuery.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.search.model 2 | 3 | import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery 4 | import co.anitrend.retrofit.graphql.domain.models.enums.SearchType 5 | 6 | data class SearchQuery( 7 | val query: String, 8 | val type: SearchType 9 | ) : IGraphQuery { 10 | override fun toMap() = mapOf( 11 | "query" to query, 12 | "type" to type.name 13 | ) 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/converters/UserConverters.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.converters 2 | 3 | import co.anitrend.arch.data.converter.SupportConverter 4 | import co.anitrend.retrofit.graphql.data.user.entity.UserEntity 5 | import co.anitrend.retrofit.graphql.data.user.model.node.UserNode 6 | import co.anitrend.retrofit.graphql.domain.entities.user.User 7 | 8 | internal class UserEntityConverter( 9 | override val fromType: (UserEntity) -> User = { 10 | User( 11 | id = it.id, 12 | avatar = it.avatarUrl, 13 | bio = it.bio.orEmpty(), 14 | status = User.Status( 15 | emoji = it.statusEmoji.orEmpty(), 16 | message = it.statusMessage.orEmpty() 17 | ), 18 | username = it.username 19 | ) 20 | }, 21 | override val toType: (User) -> UserEntity = { throw NotImplementedError() } 22 | ) : SupportConverter() 23 | 24 | internal class UserModelConverter( 25 | override val fromType: (UserEntity) -> UserNode = { throw NotImplementedError() }, 26 | override val toType: (UserNode) -> UserEntity = { 27 | UserEntity( 28 | id = it.id, 29 | username = it.login, 30 | bio = it.bio, 31 | avatarUrl = it.avatarUrl, 32 | statusEmoji = it.status?.emoji, 33 | statusMessage = it.status?.message 34 | ) 35 | } 36 | ): SupportConverter() -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/datasource/local/UserLocalSource.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.datasource.local 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | import co.anitrend.retrofit.graphql.data.arch.database.common.ILocalSource 6 | import co.anitrend.retrofit.graphql.data.user.entity.UserEntity 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | @Dao 10 | internal interface UserLocalSource : ILocalSource { 11 | 12 | @Query(""" 13 | select count(id) 14 | from users 15 | """) 16 | override suspend fun count(): Int 17 | 18 | @Query(""" 19 | delete from users 20 | """) 21 | override suspend fun clear() 22 | 23 | @Query(""" 24 | select * 25 | from users 26 | where id = :id 27 | """) 28 | fun getUserById(id: String): Flow 29 | 30 | @Query(""" 31 | select * 32 | from users 33 | limit 1 34 | """) 35 | fun getDefaultUser(): Flow 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/datasource/remote/UserRemoteSource.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.datasource.remote 2 | 3 | import co.anitrend.retrofit.graphql.data.api.common.EndpointType 4 | import co.anitrend.retrofit.graphql.data.user.model.Viewer 5 | import io.github.wax911.library.annotation.GraphQuery 6 | import io.github.wax911.library.model.body.GraphContainer 7 | import io.github.wax911.library.model.request.QueryContainerBuilder 8 | import retrofit2.Response 9 | import retrofit2.http.Body 10 | import retrofit2.http.POST 11 | 12 | internal interface UserRemoteSource { 13 | 14 | @POST(EndpointType.BASE_ENDPOINT_PATH) 15 | @GraphQuery("GetCurrentUser") 16 | suspend fun getCurrentUser( 17 | @Body builder: QueryContainerBuilder 18 | ): Response> 19 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/entity/UserEntity.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import co.anitrend.retrofit.graphql.domain.common.EntityId 6 | 7 | @Entity( 8 | tableName = "users", 9 | primaryKeys = ["id"] 10 | ) 11 | internal data class UserEntity( 12 | @ColumnInfo(name = "id") override val id: String, 13 | @ColumnInfo(name = "avatar_url") val avatarUrl: String, 14 | @ColumnInfo(name = "bio") val bio: String?, 15 | @ColumnInfo(name = "status_message") val statusMessage: String?, 16 | @ColumnInfo(name = "status_emoji") val statusEmoji: String?, 17 | @ColumnInfo(name = "username") val username: String 18 | ) : EntityId -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/mapper/UserResponseMapper.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.mapper 2 | 3 | import co.anitrend.retrofit.graphql.data.arch.mapper.GraphQLMapper 4 | import co.anitrend.retrofit.graphql.data.user.converters.UserModelConverter 5 | import co.anitrend.retrofit.graphql.data.user.datasource.local.UserLocalSource 6 | import co.anitrend.retrofit.graphql.data.user.entity.UserEntity 7 | import co.anitrend.retrofit.graphql.data.user.model.Viewer 8 | 9 | internal class UserResponseMapper( 10 | private val localSource: UserLocalSource, 11 | private val converter: UserModelConverter 12 | ) : GraphQLMapper() { 13 | /** 14 | * Inserts the given object into the implemented room database, 15 | * 16 | * @param mappedData mapped object from [onResponseMapFrom] to insert into the database 17 | */ 18 | override suspend fun onResponseDatabaseInsert(mappedData: UserEntity) { 19 | localSource.upsert(mappedData) 20 | } 21 | 22 | /** 23 | * Creates mapped objects and handles the database operations which may be required to map various objects, 24 | * 25 | * @param source the incoming data source type 26 | * @return mapped object that will be consumed by [onResponseDatabaseInsert] 27 | */ 28 | override suspend fun onResponseMapFrom(source: Viewer): UserEntity { 29 | val node = source.viewer 30 | return converter.convertTo(node) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/model/Viewer.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.model 2 | 3 | import co.anitrend.retrofit.graphql.data.user.model.node.UserNode 4 | import com.google.gson.annotations.SerializedName 5 | 6 | internal data class Viewer( 7 | @SerializedName("viewer") val viewer: UserNode 8 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/model/node/UserNode.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.model.node 2 | 3 | import co.anitrend.retrofit.graphql.data.graphql.common.INode 4 | import com.google.gson.annotations.SerializedName 5 | 6 | internal data class UserNode( 7 | @SerializedName("id") override val id: String, 8 | @SerializedName("avatarUrl") val avatarUrl: String, 9 | @SerializedName("bio") val bio: String, 10 | @SerializedName("status") val status: Status?, 11 | @SerializedName("login") val login: String 12 | ) : INode { 13 | data class Status( 14 | @SerializedName("emoji") val emoji: String?, 15 | @SerializedName("message") val message: String? 16 | ) 17 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/repository/UserRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.repository 2 | 3 | import co.anitrend.arch.data.repository.SupportRepository 4 | import co.anitrend.arch.data.state.DataState 5 | import co.anitrend.arch.data.state.DataState.Companion.create 6 | import co.anitrend.retrofit.graphql.data.user.source.contract.UserSource 7 | import co.anitrend.retrofit.graphql.domain.entities.user.User 8 | import co.anitrend.retrofit.graphql.domain.repositories.UserRepository 9 | 10 | internal class UserRepositoryImpl( 11 | private val source: UserSource 12 | ) : SupportRepository(source), UserRepositoryContract { 13 | override fun getCurrentUser() = 14 | source.create( 15 | source() 16 | ) 17 | } 18 | 19 | typealias UserRepositoryContract = UserRepository> -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/source/contract/UserSource.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.source.contract 2 | 3 | import co.anitrend.arch.data.source.core.SupportCoreDataSource 4 | import co.anitrend.arch.request.callback.RequestCallback 5 | import co.anitrend.arch.request.model.Request 6 | import co.anitrend.retrofit.graphql.domain.entities.user.User 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.mapNotNull 9 | import kotlinx.coroutines.launch 10 | 11 | internal abstract class UserSource : SupportCoreDataSource() { 12 | 13 | protected abstract val observable: Flow 14 | 15 | protected abstract suspend fun getCurrentUser( 16 | requestCallback: RequestCallback 17 | ) 18 | 19 | operator fun invoke(): Flow { 20 | scope.launch { 21 | requestHelper.runIfNotRunning( 22 | request = Request.Default("getCurrentUser", Request.Type.INITIAL), 23 | handleCallback = ::getCurrentUser 24 | ) 25 | } 26 | return observable.mapNotNull { it } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/usecase/UserUseCaseImpl.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.data.user.usecase 2 | 3 | import co.anitrend.arch.data.repository.contract.ISupportRepository 4 | import co.anitrend.arch.data.state.DataState 5 | import co.anitrend.retrofit.graphql.data.user.repository.UserRepositoryContract 6 | import co.anitrend.retrofit.graphql.domain.entities.user.User 7 | import co.anitrend.retrofit.graphql.domain.usecases.UserUseCase 8 | 9 | internal class UserUseCaseImpl( 10 | repository: UserRepositoryContract 11 | ) : UserUseCaseContract(repository) { 12 | /** 13 | * Informs underlying repositories or related components running background operations to stop 14 | */ 15 | override fun onCleared() { 16 | repository as ISupportRepository 17 | repository.onCleared() 18 | } 19 | } 20 | 21 | typealias UserUseCaseContract = UserUseCase> -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/common/EntityId.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.common 2 | 3 | interface EntityId { 4 | val id: String 5 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/entities/bucket/BucketFile.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.entities.bucket 2 | 3 | import co.anitrend.retrofit.graphql.domain.common.EntityId 4 | 5 | data class BucketFile( 6 | override val id: String, 7 | val contentType: String, 8 | val fileName: String, 9 | val url: String 10 | ) : EntityId -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/entities/market/MarketPlaceListing.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.entities.market 2 | 3 | import co.anitrend.retrofit.graphql.domain.common.EntityId 4 | 5 | /** 6 | * Represents a market place item 7 | */ 8 | data class MarketPlaceListing( 9 | override val id: String, 10 | val cursorId: String, 11 | val logoUrl: String?, 12 | val background: String, 13 | val name: String, 14 | val categories: List, 15 | val slug: String, 16 | val description: String, 17 | val isPaid: Boolean, 18 | val isVerified: Boolean 19 | ) : EntityId -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/entities/registry/RegistryItem.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.entities.registry 2 | 3 | import co.anitrend.retrofit.graphql.domain.common.EntityId 4 | 5 | /** 6 | * Represents a github repository 7 | */ 8 | data class RegistryItem( 9 | override val id: String, 10 | val description: String, 11 | val forkCount: Int, 12 | val isFork: Boolean, 13 | val isLocked: Boolean, 14 | val isPrivate: Boolean, 15 | val isTemplate: Boolean, 16 | val name: String, 17 | val projectsUrl: String, 18 | val url: String, 19 | val viewerHasStarred: Int, 20 | val watchersCount: Int 21 | ) : EntityId -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/entities/user/User.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.entities.user 2 | 3 | import co.anitrend.retrofit.graphql.domain.common.EntityId 4 | 5 | data class User( 6 | override val id: String, 7 | val avatar: String, 8 | val bio: String, 9 | val status: Status, 10 | val username: String 11 | ) : EntityId { 12 | data class Status( 13 | val emoji: String, 14 | val message: String 15 | ) 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/models/common/IGraphQuery.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.models.common 2 | 3 | /** 4 | * Contract type of a query 5 | */ 6 | interface IGraphQuery { 7 | fun toMap(): Map 8 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/models/enums/SearchType.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.models.enums 2 | 3 | enum class SearchType { 4 | ISSUE, 5 | REPOSITORY, 6 | USER 7 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/BucketRepository.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.repositories 2 | 3 | import co.anitrend.arch.domain.state.UiState 4 | 5 | interface BucketRepository> { 6 | fun getAllFiles() : D 7 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/MarketPlaceRepository.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.repositories 2 | 3 | import co.anitrend.arch.domain.state.UiState 4 | 5 | interface MarketPlaceRepository> { 6 | fun getMarketPlaceListings(): D 7 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UploadRepository.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.repositories 2 | 3 | import co.anitrend.arch.domain.state.UiState 4 | import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery 5 | 6 | interface UploadRepository> { 7 | fun uploadToBucket(mutation: IGraphQuery): D 8 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.repositories 2 | 3 | import co.anitrend.arch.domain.state.UiState 4 | 5 | interface UserRepository> { 6 | fun getCurrentUser(): D 7 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/usecases/BucketUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.usecases 2 | 3 | import co.anitrend.arch.domain.common.IUseCase 4 | import co.anitrend.arch.domain.state.UiState 5 | import co.anitrend.retrofit.graphql.domain.repositories.BucketRepository 6 | 7 | abstract class BucketUseCase>( 8 | protected val repository: BucketRepository 9 | ) : IUseCase { 10 | operator fun invoke() = 11 | repository.getAllFiles() 12 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/usecases/MarketPlaceUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.usecases 2 | 3 | import co.anitrend.arch.domain.common.IUseCase 4 | import co.anitrend.arch.domain.state.UiState 5 | import co.anitrend.retrofit.graphql.domain.repositories.MarketPlaceRepository 6 | 7 | abstract class MarketPlaceUseCase>( 8 | protected val repository: MarketPlaceRepository 9 | ) : IUseCase { 10 | operator fun invoke() = 11 | repository.getMarketPlaceListings() 12 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/usecases/UploadUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.usecases 2 | 3 | import co.anitrend.arch.domain.common.IUseCase 4 | import co.anitrend.arch.domain.state.UiState 5 | import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery 6 | import co.anitrend.retrofit.graphql.domain.repositories.UploadRepository 7 | 8 | abstract class UploadUseCase>( 9 | protected val repository: UploadRepository 10 | ) : IUseCase { 11 | operator fun invoke(mutation: IGraphQuery) = 12 | repository.uploadToBucket(mutation) 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/usecases/UserUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.domain.usecases 2 | 3 | import co.anitrend.arch.domain.common.IUseCase 4 | import co.anitrend.arch.domain.state.UiState 5 | import co.anitrend.retrofit.graphql.domain.repositories.UserRepository 6 | 7 | abstract class UserUseCase>( 8 | protected val repository: UserRepository 9 | ) : IUseCase { 10 | operator fun invoke() = 11 | repository.getCurrentUser() 12 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/App.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample 2 | 3 | import co.anitrend.retrofit.graphql.core.SampleApp 4 | import co.anitrend.retrofit.graphql.core.helpers.runtime.UncaughtExceptionHandler 5 | import io.wax911.emojify.EmojiManager 6 | import io.wax911.emojify.serializer.kotlinx.KotlinxDeserializer 7 | 8 | class App : SampleApp() { 9 | 10 | /** 11 | * Called when the application is starting, before any activity, service, 12 | * or receiver objects (excluding content providers) have been created. 13 | * 14 | * Implementations should be as quick as possible (for example using 15 | * lazy initialization of state) since the time spent in this function 16 | * directly impacts the performance of starting the first activity, 17 | * service, or receiver in a process. 18 | * 19 | * If you override this method, be sure to call `super.onCreate()`. 20 | * 21 | * Be aware that direct boot may also affect callback order on 22 | * Android [android.os.Build.VERSION_CODES.N] and later devices. 23 | * Until the user unlocks the device, only direct boot aware components are 24 | * allowed to run. You should consider that all direct boot unaware 25 | * components, including such [android.content.ContentProvider], are 26 | * disabled until user unlock happens, especially when component callback 27 | * order matters. 28 | */ 29 | override fun onCreate() { 30 | super.onCreate() 31 | if (BuildConfig.DEBUG) 32 | createUncaughtExceptionHandler() 33 | } 34 | 35 | /** 36 | * Uncaught exception handler 37 | */ 38 | override fun createUncaughtExceptionHandler() { 39 | val defaultHandler = 40 | Thread.getDefaultUncaughtExceptionHandler() 41 | Thread.setDefaultUncaughtExceptionHandler( 42 | UncaughtExceptionHandler(defaultHandler) 43 | ) 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/initializer/CoilInitializer.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample.initializer 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import coil.Coil 6 | import coil.ImageLoaderFactory 7 | import org.koin.core.component.KoinComponent 8 | import org.koin.core.component.get 9 | 10 | internal class CoilInitializer : Initializer { 11 | 12 | /** 13 | * Initializes and a component given the application [Context] 14 | * 15 | * @param context The application context. 16 | */ 17 | override fun create(context: Context) { 18 | // I could just have koin component declared on the class level 19 | val component = object : KoinComponent {} 20 | val factory = component.get() 21 | Coil.setImageLoader(factory) 22 | } 23 | 24 | /** 25 | * @return A list of dependencies that this [Initializer] depends on. This is 26 | * used to determine initialization order of [Initializer]s. 27 | * 28 | * For e.g. if a [Initializer] `B` defines another 29 | * [Initializer] `A` as its dependency, then `A` gets initialized before `B`. 30 | */ 31 | override fun dependencies(): List>> = 32 | listOf(KoinInitializer::class.java) 33 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/initializer/KoinInitializer.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample.initializer 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import co.anitrend.retrofit.graphql.core.helpers.logger.KoinLogger 6 | import co.anitrend.retrofit.graphql.sample.BuildConfig 7 | import co.anitrend.retrofit.graphql.sample.di.appModules 8 | import org.koin.android.ext.koin.androidContext 9 | import org.koin.androidx.fragment.koin.fragmentFactory 10 | import org.koin.core.KoinApplication 11 | import org.koin.core.context.startKoin 12 | import org.koin.core.logger.Level 13 | 14 | class KoinInitializer : Initializer { 15 | 16 | /** 17 | * Initializes and a component given the application [Context] 18 | * 19 | * @param context The application context. 20 | */ 21 | override fun create(context: Context): KoinApplication { 22 | //val logLevel = if (BuildConfig.DEBUG) Level.DEBUG else Level.INFO 23 | // https://github.com/InsertKoinIO/koin/issues/847 koin crashes after upgrading to 1.4.0 24 | // this is a temporary fix for now 25 | val logLevel = Level.NONE 26 | return startKoin { 27 | fragmentFactory() 28 | androidContext(context) 29 | logger(KoinLogger(logLevel)) 30 | modules(appModules) 31 | } 32 | } 33 | 34 | /** 35 | * @return A list of dependencies that this [Initializer] depends on. This is 36 | * used to determine initialization order of [Initializer]s. 37 | * 38 | * For e.g. if a [Initializer] `B` defines another 39 | * [Initializer] `A` as its dependency, then `A` gets initialized before `B`. 40 | */ 41 | override fun dependencies(): List>> = 42 | listOf(TimberInitializer::class.java) 43 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/initializer/TimberInitializer.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample.initializer 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import co.anitrend.retrofit.graphql.sample.BuildConfig 6 | import timber.log.Timber 7 | 8 | class TimberInitializer : Initializer { 9 | 10 | /** 11 | * Initializes and a component given the application [Context] 12 | * 13 | * @param context The application context. 14 | */ 15 | override fun create(context: Context) { 16 | if (BuildConfig.DEBUG) 17 | Timber.plant(Timber.DebugTree()) 18 | } 19 | 20 | /** 21 | * @return A list of dependencies that this [Initializer] depends on. This is 22 | * used to determine initialization order of [Initializer]s. 23 | * 24 | * For e.g. if a [Initializer] `B` defines another 25 | * [Initializer] `A` as its dependency, then `A` gets initialized before `B`. 26 | */ 27 | override fun dependencies() = 28 | emptyList>>() 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/presenter/MainPresenter.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample.presenter 2 | 3 | import android.content.Context 4 | import co.anitrend.arch.core.presenter.SupportPresenter 5 | import co.anitrend.arch.ui.view.widget.model.StateLayoutConfig 6 | import co.anitrend.retrofit.graphql.core.extension.using 7 | import co.anitrend.retrofit.graphql.core.settings.Settings 8 | import co.anitrend.retrofit.graphql.domain.entities.user.User 9 | import co.anitrend.retrofit.graphql.sample.databinding.NavHeaderMainBinding 10 | import coil.transform.CircleCropTransformation 11 | import io.wax911.emojify.EmojiManager 12 | import io.wax911.emojify.parser.parseToUnicode 13 | 14 | class MainPresenter( 15 | context: Context, 16 | settings: Settings, 17 | private val emojiManager: EmojiManager, 18 | private val stateLayoutConfig: StateLayoutConfig 19 | ) : SupportPresenter(context, settings) { 20 | 21 | fun configureNavigationHeader(binding: NavHeaderMainBinding) { 22 | binding.navStateLayout.stateConfigFlow.value = stateLayoutConfig 23 | } 24 | 25 | fun updateNavigationHeaderView(user: User, binding: NavHeaderMainBinding) { 26 | binding.navAvatar.using(user.avatar, null, CircleCropTransformation()) 27 | binding.navUserName.text = user.username 28 | binding.navUserBio.text = user.bio 29 | binding.navUserStatus.text = emojiManager.parseToUnicode( 30 | "${user.status.emoji} ${user.status.message}" 31 | ) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/ui/controller/helper/ControllerHelpers.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample.view.content.bucket.ui.controller.helper 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 5 | 6 | internal val DIFFER = 7 | object : DiffUtil.ItemCallback() { 8 | override fun areItemsTheSame( 9 | oldItem: BucketFile, 10 | newItem: BucketFile 11 | ): Boolean { 12 | return oldItem.id == newItem.id 13 | } 14 | 15 | override fun areContentsTheSame( 16 | oldItem: BucketFile, 17 | newItem: BucketFile 18 | ): Boolean { 19 | return oldItem.hashCode() == newItem.hashCode() 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/UploadViewModel.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.asLiveData 6 | import androidx.lifecycle.switchMap 7 | import androidx.lifecycle.viewModelScope 8 | import co.anitrend.arch.core.model.ISupportViewModelState 9 | import co.anitrend.arch.data.state.DataState 10 | import co.anitrend.retrofit.graphql.data.bucket.model.upload.mutation.UploadMutation 11 | import co.anitrend.retrofit.graphql.data.bucket.usecase.upload.UploadUseCaseContract 12 | import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile 13 | import kotlinx.coroutines.flow.merge 14 | 15 | class UploadViewModel( 16 | private val useCase: UploadUseCaseContract 17 | ) : ViewModel(), ISupportViewModelState { 18 | 19 | private val state = MutableLiveData>() 20 | 21 | override val model = state.switchMap { 22 | it.model.asLiveData(viewModelScope.coroutineContext) 23 | } 24 | 25 | override val loadState = state.switchMap { 26 | it.loadState.asLiveData(viewModelScope.coroutineContext) 27 | } 28 | 29 | override val refreshState = state.switchMap { 30 | it.refreshState.asLiveData(viewModelScope.coroutineContext) 31 | } 32 | 33 | val combinedLoadState = state.switchMap { 34 | val result = merge(it.loadState, it.refreshState) 35 | result.asLiveData(viewModelScope.coroutineContext) 36 | } 37 | 38 | operator fun invoke(mutation: UploadMutation) { 39 | val result = useCase(mutation) 40 | state.postValue(result) 41 | } 42 | 43 | /** 44 | * Triggers use case to perform refresh operation 45 | */ 46 | override suspend fun refresh() { 47 | val uiModel = state.value 48 | uiModel?.refresh?.invoke() 49 | } 50 | 51 | /** 52 | * Triggers use case to perform a retry operation 53 | */ 54 | override suspend fun retry() { 55 | val uiModel = state.value 56 | uiModel?.retry?.invoke() 57 | } 58 | 59 | override fun onCleared() { 60 | useCase.onCleared() 61 | super.onCleared() 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/controller/helpers/ControllerHelpers.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample.view.content.market.ui.controller.helpers 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing 5 | 6 | internal val DIFFER = 7 | object : DiffUtil.ItemCallback() { 8 | override fun areItemsTheSame( 9 | oldItem: MarketPlaceListing, 10 | newItem: MarketPlaceListing 11 | ): Boolean { 12 | return oldItem.id == newItem.id 13 | } 14 | 15 | override fun areContentsTheSame( 16 | oldItem: MarketPlaceListing, 17 | newItem: MarketPlaceListing 18 | ): Boolean { 19 | return oldItem.hashCode() == newItem.hashCode() 20 | } 21 | } 22 | 23 | internal val CATEGORY_DIFFER = 24 | object : DiffUtil.ItemCallback() { 25 | override fun areItemsTheSame( 26 | oldItem: String, 27 | newItem: String 28 | ): Boolean { 29 | return oldItem == newItem 30 | } 31 | 32 | override fun areContentsTheSame( 33 | oldItem: String, 34 | newItem: String 35 | ): Boolean { 36 | return oldItem == newItem 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/viewmodel/MarketPlaceViewModel.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample.view.content.market.viewmodel 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.asLiveData 6 | import androidx.lifecycle.switchMap 7 | import androidx.lifecycle.viewModelScope 8 | import androidx.paging.PagedList 9 | import co.anitrend.arch.core.model.ISupportViewModelState 10 | import co.anitrend.arch.data.state.DataState 11 | import co.anitrend.retrofit.graphql.data.bucket.model.upload.mutation.UploadMutation 12 | import co.anitrend.retrofit.graphql.data.market.usecase.MarketPlaceUseCaseContract 13 | import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing 14 | import kotlinx.coroutines.flow.merge 15 | 16 | class MarketPlaceViewModel( 17 | private val useCase: MarketPlaceUseCaseContract 18 | ) : ViewModel(), ISupportViewModelState> { 19 | 20 | private val state = MutableLiveData>>() 21 | 22 | override val model = state.switchMap { 23 | it.model.asLiveData(viewModelScope.coroutineContext) 24 | } 25 | 26 | override val loadState = state.switchMap { 27 | it.loadState.asLiveData(viewModelScope.coroutineContext) 28 | } 29 | 30 | override val refreshState = state.switchMap { 31 | it.refreshState.asLiveData(viewModelScope.coroutineContext) 32 | } 33 | 34 | val combinedLoadState = state.switchMap { 35 | val result = merge(it.loadState, it.refreshState) 36 | result.asLiveData(viewModelScope.coroutineContext) 37 | } 38 | 39 | operator fun invoke() { 40 | val result = useCase() 41 | state.postValue(result) 42 | } 43 | 44 | /** 45 | * Triggers use case to perform refresh operation 46 | */ 47 | override suspend fun refresh() { 48 | val uiModel = state.value 49 | uiModel?.refresh?.invoke() 50 | } 51 | 52 | /** 53 | * Triggers use case to perform a retry operation 54 | */ 55 | override suspend fun retry() { 56 | val uiModel = state.value 57 | uiModel?.retry?.invoke() 58 | } 59 | 60 | override fun onCleared() { 61 | useCase.onCleared() 62 | super.onCleared() 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/viewmodel/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.sample.viewmodel 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.asLiveData 6 | import androidx.lifecycle.switchMap 7 | import androidx.lifecycle.viewModelScope 8 | import co.anitrend.arch.core.model.ISupportViewModelState 9 | import co.anitrend.arch.data.state.DataState 10 | import co.anitrend.retrofit.graphql.data.user.usecase.UserUseCaseContract 11 | import co.anitrend.retrofit.graphql.domain.entities.user.User 12 | import kotlinx.coroutines.flow.merge 13 | 14 | class MainViewModel( 15 | private val useCase: UserUseCaseContract 16 | ) : ViewModel(), ISupportViewModelState { 17 | 18 | private val state = MutableLiveData>() 19 | 20 | override val model = state.switchMap { 21 | it.model.asLiveData(viewModelScope.coroutineContext) 22 | } 23 | 24 | override val loadState = state.switchMap { 25 | it.loadState.asLiveData(viewModelScope.coroutineContext) 26 | } 27 | 28 | override val refreshState = state.switchMap { 29 | it.refreshState.asLiveData(viewModelScope.coroutineContext) 30 | } 31 | 32 | val combinedLoadState = state.switchMap { 33 | val result = merge(it.loadState, it.refreshState) 34 | result.asLiveData(viewModelScope.coroutineContext) 35 | } 36 | 37 | operator fun invoke() { 38 | val result = useCase() 39 | state.postValue(result) 40 | } 41 | 42 | /** 43 | * Triggers use case to perform refresh operation 44 | */ 45 | override suspend fun refresh() { 46 | val uiModel = state.value 47 | uiModel?.refresh?.invoke() 48 | } 49 | 50 | /** 51 | * Triggers use case to perform a retry operation 52 | */ 53 | override suspend fun retry() { 54 | val uiModel = state.value 55 | uiModel?.retry?.invoke() 56 | } 57 | 58 | /** 59 | * This method will be called when this ViewModel is no longer used and will be destroyed. 60 | * 61 | * It is useful when ViewModel observes some data and you need to clear this subscription to 62 | * prevent a leak of this ViewModel. 63 | */ 64 | override fun onCleared() { 65 | useCase.onCleared() 66 | super.onCleared() 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/bottom_dialog_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_apps_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cloud_upload_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gesture_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_adaptable_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_whatshot_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nav_item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nav_item_background_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_content.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 11 | 12 | 22 | 23 | 31 | 32 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bucket_file_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 24 | 25 | 28 | 29 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/market_place_category_item.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 17 | 18 | 23 | 24 | 27 | 28 | 35 | 36 | 42 | 43 | 46 | 47 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/shared_list_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/nav_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00BCD4 4 | 5 | #33FFFFFF 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #EAE5E5 4 | @color/colorStateBlue 5 | 6 | #33000000 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 100dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | _authenticatedUser 4 | _isNewInstallation 5 | _versionCode 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | retrofit-graphql 3 | Retry 4 | Search 5 | App Store 6 | Uploads 7 | Navigation 8 | Pick & upload file 9 | Processing & uploading.. 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pi.hole 6 | 7 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath(libs.android.gradle.plugin) 8 | classpath(libs.jetbrains.kotlin.gradle) 9 | classpath(libs.jetbrains.kotlin.serialization) 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | jcenter() 17 | mavenCentral() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | `maven-publish` 4 | `version-catalog` 5 | } 6 | 7 | repositories { 8 | google() 9 | mavenCentral() 10 | gradlePluginPortal() 11 | maven { 12 | setUrl("https://plugins.gradle.org/m2/") 13 | } 14 | } 15 | 16 | dependencies { 17 | /** Depend on the android gradle plugin, since we want to access it in our plugin */ 18 | implementation(libs.android.gradle.plugin) 19 | 20 | /** Depend on the kotlin plugin, since we want to access it in our plugin */ 21 | implementation(libs.jetbrains.kotlin.gradle) 22 | 23 | /** Depend on the dokka plugin, since we want to access it in our plugin */ 24 | implementation(libs.jetbrains.dokka.gradle) 25 | 26 | /** Spotless */ 27 | implementation(libs.spotless.gradle) 28 | 29 | /** Depend on the default Gradle API's since we want to build a custom plugin */ 30 | implementation(gradleApi()) 31 | implementation(localGroovy()) 32 | 33 | /** Work around to include ../.gradle/LibrariesForLibs generated file for version catalog */ 34 | implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) 35 | 36 | testImplementation(kotlin("test")) 37 | } 38 | 39 | tasks.test { 40 | useJUnitPlatform() 41 | } -------------------------------------------------------------------------------- /buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | dependencyResolutionManagement { 4 | versionCatalogs { 5 | create("libs") { 6 | from(files("../gradle/libs.versions.toml")) 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/module/Modules.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.buildSrc.module 2 | 3 | internal object Modules { 4 | 5 | interface Module { 6 | val id: String 7 | 8 | /** 9 | * @return Formatted id of module as a path string 10 | */ 11 | fun path(): String = ":$id" 12 | } 13 | 14 | enum class Components(override val id: String) : Module { 15 | App("app"), 16 | Library("library") 17 | } 18 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/CorePlugin.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.buildSrc.plugin 2 | 3 | import co.anitrend.retrofit.graphql.buildSrc.plugin.components.configureAndroid 4 | import co.anitrend.retrofit.graphql.buildSrc.plugin.components.configureDependencies 5 | import co.anitrend.retrofit.graphql.buildSrc.plugin.components.configureOptions 6 | import co.anitrend.retrofit.graphql.buildSrc.plugin.components.configurePlugins 7 | import co.anitrend.retrofit.graphql.buildSrc.plugin.components.configureSpotless 8 | import org.gradle.api.Plugin 9 | import org.gradle.api.Project 10 | 11 | open class CorePlugin : Plugin { 12 | 13 | /** 14 | * Inspecting available extensions 15 | */ 16 | private fun Project.availableExtensions() { 17 | val extensionSchema = project.extensions.extensionsSchema 18 | extensionSchema.forEach { 19 | logger.lifecycle("Available extension for module ${project.path}: ${it.name} -> ${it.publicType}") 20 | } 21 | } 22 | 23 | /** 24 | * Inspecting available components 25 | */ 26 | private fun Project.availableComponents() { 27 | val collectionSchema = project.components.asMap 28 | collectionSchema.forEach { 29 | logger.lifecycle("Available component for module ${project.path}: ${it.key} -> ${it.value}") 30 | } 31 | } 32 | 33 | override fun apply(project: Project) { 34 | project.configurePlugins() 35 | project.availableExtensions() 36 | project.availableComponents() 37 | project.configureAndroid() 38 | project.configureOptions() 39 | project.configureDependencies() 40 | project.configureSpotless() 41 | } 42 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/components/AndroidDependencies.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.buildSrc.plugin.components 2 | 3 | import co.anitrend.retrofit.graphql.buildSrc.plugin.strategy.DependencyStrategy 4 | import co.anitrend.retrofit.graphql.buildSrc.plugin.extensions.implementation 5 | import org.gradle.api.Project 6 | 7 | internal fun Project.configureDependencies() { 8 | val dependencyStrategy = DependencyStrategy(project) 9 | dependencies.implementation( 10 | fileTree("libs") { 11 | include("*.jar") 12 | } 13 | ) 14 | dependencyStrategy.applyDependenciesOn(dependencies) 15 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/components/AndroidPlugins.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.buildSrc.plugin.components 2 | 3 | import co.anitrend.retrofit.graphql.buildSrc.plugin.extensions.isLibraryModule 4 | import co.anitrend.retrofit.graphql.buildSrc.plugin.extensions.isSampleModule 5 | import org.gradle.api.Project 6 | 7 | private fun Project.applyModulePlugin() { 8 | if (isLibraryModule()) { 9 | plugins.apply("com.android.library") 10 | plugins.apply("org.jetbrains.dokka") 11 | plugins.apply("com.diffplug.spotless") 12 | plugins.apply("maven-publish") 13 | } 14 | else 15 | plugins.apply("com.android.application") 16 | } 17 | 18 | internal fun Project.configurePlugins() { 19 | applyModulePlugin() 20 | plugins.apply("kotlin-android") 21 | if (isSampleModule()) 22 | plugins.apply("kotlin-kapt") 23 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/components/PropertiesComponent.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.buildSrc.plugin.components 2 | 3 | import org.gradle.api.Project 4 | import java.io.File 5 | import java.util.* 6 | 7 | 8 | enum class PropertyTypes(val key: String) { 9 | CODE("code"), 10 | VERSION("version"), 11 | NAME("name"), 12 | } 13 | 14 | class PropertiesReader(project: Project) { 15 | private val properties = Properties(2) 16 | 17 | init { 18 | val releaseFile = File(project.rootDir, "gradle/version.properties") 19 | if (!releaseFile.exists()) { 20 | project.logger.error("Release file cannot be found in path: $releaseFile") 21 | } 22 | 23 | properties.apply { 24 | load(releaseFile.inputStream()) 25 | } 26 | } 27 | 28 | operator fun get(type: PropertyTypes): String { 29 | return properties.getProperty(type.key) 30 | ?: throw IllegalStateException("$type properties were not initialized") 31 | } 32 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/strategy/DependencyStrategy.kt: -------------------------------------------------------------------------------- 1 | package co.anitrend.retrofit.graphql.buildSrc.plugin.strategy 2 | 3 | import org.gradle.api.Project 4 | import co.anitrend.retrofit.graphql.buildSrc.plugin.extensions.isSampleModule 5 | import co.anitrend.retrofit.graphql.buildSrc.plugin.extensions.implementation 6 | import co.anitrend.retrofit.graphql.buildSrc.plugin.extensions.androidTest 7 | import co.anitrend.retrofit.graphql.buildSrc.plugin.extensions.libs 8 | import co.anitrend.retrofit.graphql.buildSrc.plugin.extensions.test 9 | import org.gradle.api.artifacts.dsl.DependencyHandler 10 | 11 | internal class DependencyStrategy( 12 | private val project: Project 13 | ) { 14 | 15 | private fun DependencyHandler.applyDefaultDependencies() { 16 | implementation(project.libs.jetbrains.kotlin.stdlib) 17 | 18 | if (project.isSampleModule()) { 19 | implementation(project.libs.jetbrains.kotlin.reflect) 20 | implementation(project.libs.koin.core) 21 | implementation(project.libs.koin.android) 22 | } 23 | 24 | // Testing libraries 25 | test(project.libs.junit) 26 | test(project.libs.mockk) 27 | } 28 | 29 | 30 | private fun DependencyHandler.applyNetworkingDependencies() { 31 | implementation(project.libs.square.retrofit) 32 | implementation(project.libs.square.retrofit.gson.converter) 33 | } 34 | 35 | fun applyDependenciesOn(handler: DependencyHandler) { 36 | handler.applyDefaultDependencies() 37 | handler.applyNetworkingDependencies() 38 | } 39 | } -------------------------------------------------------------------------------- /buildSrc/src/main/resources/META-INF/gradle-plugins/co.anitrend.retrofit.graphql.properties: -------------------------------------------------------------------------------- 1 | implementation-class=co.anitrend.retrofit.graphql.buildSrc.plugin.CorePlugin 2 | -------------------------------------------------------------------------------- /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 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | org.gradle.parallel=true 14 | 15 | android.useAndroidX=true 16 | android.enableJetifier=true 17 | 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | 21 | # https://kotlinlang.org/docs/dokka-migration.html#enable-migration-helpers 22 | # Will enable when ready to migrade 23 | #org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers 24 | -------------------------------------------------------------------------------- /gradle/version.properties: -------------------------------------------------------------------------------- 1 | version=0.11.12 2 | code=11012000 3 | name=v0.11.12 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /images/logo/ic_launcher.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/images/logo/ic_launcher.zip -------------------------------------------------------------------------------- /images/logo/ic_launcher_round.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/images/logo/ic_launcher_round.zip -------------------------------------------------------------------------------- /images/screenshots/assets_files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/images/screenshots/assets_files.png -------------------------------------------------------------------------------- /images/screenshots/sample_img_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/images/screenshots/sample_img_001.png -------------------------------------------------------------------------------- /images/screenshots/sample_img_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/images/screenshots/sample_img_002.png -------------------------------------------------------------------------------- /images/screenshots/sample_img_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/images/screenshots/sample_img_003.png -------------------------------------------------------------------------------- /images/screenshots/sample_img_004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/images/screenshots/sample_img_004.png -------------------------------------------------------------------------------- /images/screenshots/todo_domain_files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniTrend/retrofit-graphql/106ae1a2f167efbd7297cfd9123dd892abf8befe/images/screenshots/todo_domain_files.png -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk21 3 | #before_install: 4 | # - ./custom_setup.sh 5 | install: 6 | - echo "Running build commands" 7 | - ./gradlew build --stacktrace 8 | - ./gradlew publishMavenPublicationToMavenLocal 9 | env: 10 | CI: "true" 11 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("co.anitrend.retrofit.graphql") 3 | id("kotlin-parcelize") 4 | } 5 | 6 | android { 7 | namespace = "io.github.wax911.library" 8 | } 9 | 10 | dependencies { 11 | implementation(libs.androidx.annotation) 12 | 13 | testImplementation(libs.jetbrains.kotlin.reflect) 14 | } -------------------------------------------------------------------------------- /library/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 | -keep class io.github.wax911.library.model.** { *; } 23 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/annotation/GraphQuery.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.annotation 18 | 19 | @MustBeDocumented 20 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 21 | @Retention(AnnotationRetention.RUNTIME) 22 | annotation class GraphQuery(val value: String = "") 23 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/annotation/processor/fragment/FragmentAnalysis.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.annotation.processor.fragment 18 | 19 | /** 20 | * A simple data class that defines a fragment reference (by name), and whether or not it was defined with some graphql 21 | * content. 22 | * 23 | * @author eschlenz 24 | */ 25 | data class FragmentAnalysis( 26 | val fragmentReference: String, 27 | val isDefined: Boolean, 28 | ) 29 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/annotation/processor/fragment/FragmentAnalyzer.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.annotation.processor.fragment 18 | 19 | /** 20 | * A contract for something that can parse graphql content and provide a full analysis of what fragments are referenced, 21 | * and whether the fragments are defined with the query. 22 | * 23 | * @author eschlenz 24 | */ 25 | interface FragmentAnalyzer { 26 | fun analyzeFragments(graphqlContent: String): Set 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/annotation/processor/fragment/RegexFragmentAnalyzer.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.annotation.processor.fragment 18 | 19 | /** 20 | * An implementation of FragmentAnalyzer that simply uses regular expressions to find fragment references, and whether 21 | * they are defined with the query. 22 | * 23 | * @author eschlenz 24 | */ 25 | class RegexFragmentAnalyzer : FragmentAnalyzer { 26 | override fun analyzeFragments(graphqlContent: String): Set { 27 | val fragmentReferences = GraphRegexUtil.findFragmentReferences(graphqlContent) 28 | val fragmentDefinitions = GraphRegexUtil.findFragmentDefinitions(graphqlContent) 29 | 30 | return fragmentReferences.map { 31 | FragmentAnalysis( 32 | fragmentReference = it, 33 | isDefined = fragmentDefinitions.contains(it), 34 | ) 35 | }.toSet() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/annotation/processor/plugin/contract/AbstractDiscoveryPlugin.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.annotation.processor.plugin.contract 18 | 19 | import androidx.annotation.VisibleForTesting 20 | import io.github.wax911.library.logger.core.AbstractLogger 21 | import java.io.InputStream 22 | 23 | /** 24 | * A discovery plugin that defines a contract to handle multiple sources 25 | */ 26 | abstract class AbstractDiscoveryPlugin( 27 | protected val source: S, 28 | ) { 29 | internal abstract val targetPath: String 30 | internal abstract val targetExtension: String 31 | 32 | /** 33 | * Reads the file contents for a given [inputStream] 34 | */ 35 | @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 36 | protected abstract fun resolveContents( 37 | inputStream: InputStream, 38 | logger: AbstractLogger, 39 | ): String 40 | 41 | /** 42 | * Invokes initial discovery using [source] to produce a map 43 | */ 44 | abstract fun startDiscovery(logger: AbstractLogger): Map 45 | } 46 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/converter/request/GraphRequestConverter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.converter.request 18 | 19 | import com.google.gson.Gson 20 | import io.github.wax911.library.annotation.processor.contract.AbstractGraphProcessor 21 | import io.github.wax911.library.model.request.QueryContainerBuilder 22 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 23 | import okhttp3.RequestBody 24 | import okhttp3.RequestBody.Companion.toRequestBody 25 | import retrofit2.Converter 26 | 27 | /** 28 | * GraphQL request body converter and injector, uses method annotation for a given retrofit method 29 | */ 30 | open class GraphRequestConverter( 31 | protected val methodAnnotations: Array, 32 | protected val graphProcessor: AbstractGraphProcessor, 33 | protected val gson: Gson, 34 | ) : Converter { 35 | /** 36 | * Converter for the request body, gets the GraphQL query from the method annotation 37 | * and constructs a GraphQL request body to send over the network. 38 | * 39 | * @param containerBuilder The constructed builder method of your query with variables 40 | */ 41 | override fun convert(containerBuilder: QueryContainerBuilder): RequestBody { 42 | val rawQuery = graphProcessor.getQuery(methodAnnotations) 43 | val queryContainer = 44 | containerBuilder.setQuery(rawQuery) 45 | .build() 46 | val queryJson = gson.toJson(queryContainer) 47 | return queryJson.toRequestBody(MEDIA_TYPE) 48 | } 49 | 50 | companion object { 51 | private val MEDIA_TYPE = "application/json".toMediaTypeOrNull() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/converter/response/GraphResponseConverter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.converter.response 18 | 19 | import com.google.gson.Gson 20 | import okhttp3.ResponseBody 21 | import retrofit2.Converter 22 | import java.io.IOException 23 | import java.lang.reflect.Type 24 | 25 | /** 26 | * GraphQL response body converter to unwrap nested object results, 27 | * resulting in a smaller generic tree for requests 28 | */ 29 | open class GraphResponseConverter( 30 | protected val type: Type?, 31 | protected val gson: Gson, 32 | ) : Converter { 33 | /** 34 | * Converter contains logic on how to handle responses, since GraphQL responses follow 35 | * the JsonAPI spec it makes sense to wrap our base query response data and errors response 36 | * in here, the logic remains open to the implementation 37 | *

38 | * 39 | * @param responseBody The retrofit response body received from the network 40 | * @return The type declared in the Call of the request 41 | */ 42 | override fun convert(responseBody: ResponseBody): T? { 43 | var response: T? = null 44 | try { 45 | val responseString = responseBody.string() 46 | response = gson.fromJson(responseString, type) 47 | } catch (e: IOException) { 48 | e.printStackTrace() 49 | } 50 | 51 | return response 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/logger/DefaultGraphLogger.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.logger 18 | 19 | import android.util.Log 20 | import io.github.wax911.library.logger.contract.ILogger.Level 21 | import io.github.wax911.library.logger.core.AbstractLogger 22 | 23 | /** 24 | * Default logger for the library that writes to [Log] 25 | * with a default log [level] level of [Level.INFO] 26 | */ 27 | class DefaultGraphLogger( 28 | level: Level = Level.INFO, 29 | ) : AbstractLogger(level) { 30 | /** 31 | * Write a log message to its destination. 32 | * 33 | * @param level Log [Level] filter 34 | * @param tag Identifier log tag 35 | * @param message Optional log message 36 | * @param throwable Optional exception 37 | */ 38 | override fun log( 39 | level: Level, 40 | tag: String, 41 | message: String, 42 | throwable: Throwable?, 43 | ) { 44 | when (level) { 45 | Level.VERBOSE -> Log.v(tag, message, throwable) 46 | Level.DEBUG -> Log.d(tag, message, throwable) 47 | Level.INFO -> Log.i(tag, message, throwable) 48 | Level.WARNING -> Log.w(tag, message, throwable) 49 | Level.ERROR -> Log.e(tag, message, throwable) 50 | Level.NONE -> { /* no logging at none */ } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/logger/contract/ILogger.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.logger.contract 18 | 19 | /** 20 | * Logger contract 21 | */ 22 | interface ILogger { 23 | var level: Level 24 | 25 | /** 26 | * Write a log message to its destination. 27 | * 28 | * @param level Filter used to determine the verbosity level of logs. 29 | * @param tag Used to identify the source of a log message. It usually 30 | * identifies the class or activity where the log call occurs. 31 | * @param message The message you would like logged. 32 | * @param throwable An exception to log 33 | */ 34 | fun log( 35 | level: Level, 36 | tag: String, 37 | message: String, 38 | throwable: Throwable? = null, 39 | ) 40 | 41 | /** 42 | * The levels used to print out and filter log messages 43 | */ 44 | enum class Level { 45 | VERBOSE, 46 | DEBUG, 47 | INFO, 48 | WARNING, 49 | ERROR, 50 | NONE, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/model/attribute/GraphError.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.model.attribute 18 | 19 | /** 20 | * GraphQL error representation that is spec complaint 21 | * 22 | * @param message Description of the error. 23 | * @param path Path of the the response field that encountered the error, as segments that 24 | * represent fields should be strings, and path segments that represent list indices 25 | * should be 0‐indexed integers. If the error happens in an aliased field, the path to the 26 | * error should use the aliased name, since it represents a path in the response, not in the query. 27 | * @param locations List of locations within the GraphQL document at which the exception occurred. 28 | * @param extensions Additional information about the error. 29 | * 30 | * @see [GraphQL Error Specification](http://spec.graphql.org/June2018/#sec-Errors) 31 | */ 32 | data class GraphError( 33 | val message: String?, 34 | val path: List? = null, 35 | val locations: List? = null, 36 | val extensions: Map? = null, 37 | ) { 38 | /** 39 | * Location describing which part of GraphQL document caused an exception. 40 | */ 41 | data class Location( 42 | val line: Int, 43 | val column: Int, 44 | ) 45 | 46 | override fun toString(): String { 47 | return "GraphError{" + 48 | "message='" + message + '\''.toString() + 49 | ", path=" + path?.joinToString() + 50 | ", locations=" + locations?.joinToString() + 51 | ", extensions=" + extensions + 52 | '}'.toString() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/model/body/GraphContainer.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.model.body 18 | 19 | import io.github.wax911.library.model.attribute.GraphError 20 | 21 | /** 22 | * GraphQL response that is spec complaint. 23 | * 24 | * @see [GraphQL Data Specification](http://spec.graphql.org/June2018/#sec-Data) 25 | */ 26 | data class GraphContainer( 27 | val data: T? = null, 28 | val errors: List? = null, 29 | val extensions: Map? = null, 30 | ) 31 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/model/request/PersistedQuery.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.model.request 18 | 19 | /** 20 | * Contents of a PersistedQuery extension inside a QueryContainer 21 | * 22 | * @see QueryContainerBuilder.putPersistedQueryHash 23 | * @see QueryContainerBuilder.putExtension 24 | */ 25 | data class PersistedQuery( 26 | val sha256Hash: String, 27 | val version: Int, 28 | ) 29 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/model/request/PersistedQueryUrlParameterBuilder.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.model.request 18 | 19 | import com.google.gson.Gson 20 | 21 | /** 22 | * Query & Variable builder for URL parameter based GET requests 23 | */ 24 | class PersistedQueryUrlParameterBuilder( 25 | private val queryContainer: QueryContainer = QueryContainer(), 26 | private val gson: Gson, 27 | ) { 28 | fun build(): PersistedQueryUrlParameters { 29 | return PersistedQueryUrlParameters( 30 | extensions = gson.toJson(queryContainer.extensions), 31 | operationName = queryContainer.operationName.orEmpty(), 32 | variables = gson.toJson(queryContainer.variables), 33 | ) 34 | } 35 | 36 | companion object { 37 | const val EXTENSION_KEY_APQ = "persistedQuery" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/model/request/PersistedQueryUrlParameters.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.model.request 18 | 19 | /** 20 | * Data for persisted queries where query content is sent as query parameters in the URL 21 | * instead of a json formatted body 22 | */ 23 | data class PersistedQueryUrlParameters( 24 | val extensions: String, 25 | val operationName: String, 26 | val variables: String, 27 | ) 28 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/model/request/QueryContainer.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.model.request 18 | 19 | import android.os.Parcelable 20 | import kotlinx.parcelize.Parcelize 21 | import kotlinx.parcelize.RawValue 22 | import java.util.WeakHashMap 23 | 24 | /** 25 | * GraphQL request that follows the common GraphQL HTTP request format 26 | * 27 | * @see [GraphQL Over HTTP](https://graphql.org/learn/serving-over-http/#post-request) 28 | */ 29 | @Parcelize 30 | data class QueryContainer internal constructor( 31 | var operationName: String? = null, 32 | var query: String? = null, 33 | val variables: @RawValue MutableMap = WeakHashMap(), 34 | val extensions: @RawValue MutableMap = WeakHashMap(), 35 | ) : Parcelable { 36 | internal fun putVariables(values: Map) { 37 | variables.putAll(values) 38 | } 39 | 40 | internal fun putVariable( 41 | key: String, 42 | value: Any?, 43 | ) { 44 | variables[key] = value 45 | } 46 | 47 | internal fun putExtension( 48 | key: String, 49 | value: Any?, 50 | ) { 51 | extensions[key] = value 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/persisted/contract/IAutomaticPersistedQuery.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.persisted.contract 18 | 19 | interface IAutomaticPersistedQuery { 20 | /** 21 | * Calculates the automated persisted query hash for a given [queryName] 22 | * 23 | * @return Hash or null if a query with the name [queryName] could not be found 24 | */ 25 | fun getOrCreateAPQHash(queryName: String): String? 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/persisted/query/error/AutomaticPersistedQueryErrors.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.persisted.query.error 18 | 19 | /** 20 | * Possible persisted query errors 21 | * 22 | * @author Krillsson 23 | */ 24 | object AutomaticPersistedQueryErrors { 25 | /** 26 | * When the server does not support Automatic Persisted Queries. 27 | * The client should fallback to regular query 28 | */ 29 | const val APQ_NOT_SUPPORTED_ERROR = "PersistedQueryNotSupported" 30 | 31 | /** 32 | * When the server has yet seen the query content. 33 | * The client should fallback to regular query 34 | */ 35 | const val APQ_QUERY_NOT_FOUND_ERROR = "PersistedQueryNotFound" 36 | } 37 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/persistedquery/PersistedQueryErrors.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.persistedquery 18 | 19 | @Deprecated( 20 | "Consider migrating to AutomaticPersistedQueryErrors instead", 21 | ReplaceWith( 22 | "AutomaticPersistedQueryErrors", 23 | "io.github.wax911.library.persisted.query.error.AutomaticPersistedQueryErrors", 24 | ), 25 | ) 26 | object PersistedQueryErrors { 27 | // server does not support Automatic Persisted Queries. The client should fallback to regular query 28 | const val APQ_NOT_SUPPORTED_ERROR = "PersistedQueryNotSupported" 29 | 30 | // the server has yet seen the query content. Client should fallback to regular query 31 | const val APQ_QUERY_NOT_FOUND_ERROR = "PersistedQueryNotFound" 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/kotlin/io/github/wax911/library/util/GraphErrorUtil.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.wax911.library.util 18 | 19 | import com.google.gson.Gson 20 | import com.google.gson.JsonSyntaxException 21 | import com.google.gson.reflect.TypeToken 22 | import io.github.wax911.library.model.attribute.GraphError 23 | import io.github.wax911.library.model.body.GraphContainer 24 | import retrofit2.Response 25 | 26 | /** 27 | * Converts the response error response into an object. 28 | * 29 | * @return The error object, or null if an exception was encountered 30 | * 31 | * @see GraphError 32 | */ 33 | fun Response<*>?.getError(): List? { 34 | try { 35 | if (this != null) { 36 | val responseBody = errorBody() 37 | val message = responseBody?.string() 38 | if (responseBody != null && !message.isNullOrBlank()) { 39 | val graphErrors = message.getGraphQLError() 40 | if (graphErrors != null) { 41 | return graphErrors 42 | } 43 | } 44 | } 45 | } catch (ex: Exception) { 46 | ex.printStackTrace() 47 | } 48 | return null 49 | } 50 | 51 | @Throws(JsonSyntaxException::class) 52 | private fun String.getGraphQLError(): List? { 53 | val tokenType = object : TypeToken>() {}.type 54 | val graphContainer = Gson().fromJson>(this, tokenType) 55 | return graphContainer.errors 56 | } 57 | -------------------------------------------------------------------------------- /library/src/test/kotlin/io/github/wax911/library/annotation/processor/GraphProcessorTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wax911.library.annotation.processor 2 | 3 | import io.github.wax911.library.annotation.processor.contract.AbstractGraphProcessor 4 | import io.github.wax911.library.helpers.ResourcesDiscoveryPlugin 5 | import io.github.wax911.library.helpers.annotations.MockAnnotationStubs 6 | import io.github.wax911.library.helpers.logger.TestLogger 7 | import org.junit.Assert.assertEquals 8 | import org.junit.Assert.assertNotNull 9 | import org.junit.Before 10 | import org.junit.Test 11 | import kotlin.reflect.full.functions 12 | 13 | class GraphProcessorTest { 14 | 15 | private lateinit var processor: AbstractGraphProcessor 16 | 17 | @Before 18 | fun setUp() { 19 | val testLogger = TestLogger() 20 | /** Using resources instead of assets in this test environment */ 21 | val plugin = ResourcesDiscoveryPlugin() 22 | processor = GraphProcessor( 23 | discoveryPlugin = plugin, 24 | logger = testLogger 25 | ) 26 | } 27 | 28 | @Test 29 | fun `assert all annotations can be reference valid graphql files`() { 30 | val functions = MockAnnotationStubs::class.functions 31 | val graphs = functions.map { 32 | processor.getQuery(it.annotations.toTypedArray()) 33 | } 34 | assertEquals(9, graphs.size) 35 | } 36 | 37 | @Test 38 | fun `given an annotation for finding an issue, assure that a query can be resolved`() { 39 | val annotations = MockAnnotationStubs::findIssueById.annotations 40 | val expected = """ 41 | query FindIssueID( "\$"owner : String!, "\$"name : String!, "\$"issueId : Int!) { repository(owner: "\$"owner , name: "\$"name ) { issue(number: "\$"issueId ) { id } }} 42 | """.trimIndent() 43 | val actual = processor.getQuery(annotations.toTypedArray()) 44 | 45 | assertNotNull(expected, actual) 46 | } 47 | } -------------------------------------------------------------------------------- /library/src/test/kotlin/io/github/wax911/library/annotation/processor/fragment/Operation.kt: -------------------------------------------------------------------------------- 1 | package io.github.wax911.library.annotation.processor.fragment 2 | 3 | data class Operation(val typeStr: String, val nameStr: String) { 4 | companion object { 5 | fun enumeration() = listOf( 6 | Operation("query", "Query"), 7 | Operation("mutation", "Mutation"), 8 | Operation("subscription", "Subscription") 9 | ) 10 | } 11 | } -------------------------------------------------------------------------------- /library/src/test/kotlin/io/github/wax911/library/annotation/processor/plugin/contract/AbstractDiscoveryPluginTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wax911.library.annotation.processor.plugin.contract 2 | 3 | import io.github.wax911.library.helpers.ResourcesDiscoveryPlugin 4 | import io.github.wax911.library.helpers.logger.TestLogger 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Assert.assertTrue 7 | import org.junit.Before 8 | import org.junit.Test 9 | 10 | class AbstractDiscoveryPluginTest { 11 | 12 | private lateinit var maps: Map 13 | 14 | @Before 15 | fun setUp() { 16 | val testLogger = TestLogger() 17 | /** Using resources instead of assets in this test environment */ 18 | val plugin = ResourcesDiscoveryPlugin() 19 | maps = plugin.startDiscovery(testLogger) 20 | } 21 | 22 | @Test 23 | fun `assure can discover from given resource type`() { 24 | assertTrue(maps.isNotEmpty()) 25 | } 26 | 27 | @Test 28 | fun `assure discovered size is equal to actual files count`() { 29 | assertEquals(13, maps.size) 30 | } 31 | 32 | @Test 33 | fun `assure multi line contents are loaded properly`() { 34 | val expected = """ 35 | fragment UserCore on User { avatarUrl bio company id status { createdAt emoji message } login} 36 | """.trimIndent() 37 | 38 | val actual = maps["UserCore.graphql"] 39 | 40 | assertEquals(expected, actual) 41 | } 42 | } -------------------------------------------------------------------------------- /library/src/test/kotlin/io/github/wax911/library/helpers/ResourcesDiscoveryPlugin.kt: -------------------------------------------------------------------------------- 1 | package io.github.wax911.library.helpers 2 | 3 | import io.github.wax911.library.annotation.processor.plugin.contract.AbstractDiscoveryPlugin 4 | import io.github.wax911.library.logger.core.AbstractLogger 5 | import java.io.File 6 | import java.io.InputStream 7 | import java.net.URL 8 | 9 | class ResourcesDiscoveryPlugin( 10 | self: Class = ResourcesDiscoveryPlugin::class.java 11 | ) : AbstractDiscoveryPlugin>(self) { 12 | override val targetPath: String = "graphql" 13 | override val targetExtension: String = ".graphql" 14 | 15 | private val temporaryMap = HashMap() 16 | 17 | private fun getResourceFolderFiles(url: URL): List { 18 | val path = url.path 19 | val sequenceFiles = File(path).walk() 20 | return sequenceFiles.filter { 21 | val fileName = it.name 22 | fileName.endsWith(targetExtension) 23 | }.toList() 24 | } 25 | 26 | /** 27 | * Reads the file contents for a given [inputStream] 28 | */ 29 | override fun resolveContents( 30 | inputStream: InputStream, 31 | logger: AbstractLogger 32 | ) = buildString { 33 | inputStream.bufferedReader().useLines { sequence -> 34 | sequence.forEach(::append) 35 | } 36 | } 37 | 38 | /** 39 | * Invokes initial discovery using [source] to produce a map 40 | */ 41 | override fun startDiscovery(logger: AbstractLogger): Map { 42 | val urlPath = source.getResource(targetPath) 43 | if (urlPath != null) { 44 | val files = getResourceFolderFiles(urlPath) 45 | files.forEach { file -> 46 | val contents = resolveContents(file.inputStream(), logger) 47 | assert(contents.isNotEmpty()) { 48 | "Contents of a file that needs to be read should never be empty" 49 | } 50 | temporaryMap[file.name] = contents 51 | } 52 | } 53 | return temporaryMap 54 | } 55 | } -------------------------------------------------------------------------------- /library/src/test/kotlin/io/github/wax911/library/helpers/annotations/MockAnnotationStubs.kt: -------------------------------------------------------------------------------- 1 | package io.github.wax911.library.helpers.annotations 2 | 3 | import io.github.wax911.library.annotation.GraphQuery 4 | 5 | interface MockAnnotationStubs { 6 | @GraphQuery("StorageBucketFiles") fun getStorageBucketFiles() 7 | @GraphQuery("GetMarketPlaceApps") fun getMarketPlaceApps() 8 | @GraphQuery("FindIssueId") fun findIssueById() 9 | @GraphQuery("GetRepository") fun getRepository() 10 | @GraphQuery("GeneralSearch") fun searchFor() 11 | @GraphQuery("GetCurrentUser") fun getCurrentUser() 12 | } -------------------------------------------------------------------------------- /library/src/test/kotlin/io/github/wax911/library/helpers/logger/TestLogger.kt: -------------------------------------------------------------------------------- 1 | package io.github.wax911.library.helpers.logger 2 | 3 | import io.github.wax911.library.logger.contract.ILogger 4 | import io.github.wax911.library.logger.core.AbstractLogger 5 | 6 | class TestLogger : AbstractLogger(ILogger.Level.NONE) { 7 | override fun log( 8 | level: ILogger.Level, 9 | tag: String, 10 | message: String, 11 | throwable: Throwable? 12 | ) { 13 | /** ignored test environment */ 14 | } 15 | } -------------------------------------------------------------------------------- /library/src/test/kotlin/io/github/wax911/library/logger/core/AbstractLoggerTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wax911.library.logger.core 2 | 3 | import io.github.wax911.library.logger.contract.ILogger 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class AbstractLoggerTest { 8 | 9 | private val logger = object : AbstractLogger(ILogger.Level.INFO) { 10 | override fun log( 11 | level: ILogger.Level, 12 | tag: String, 13 | message: String, 14 | throwable: Throwable? 15 | ) { /** ignored */ } 16 | } 17 | 18 | @Test 19 | fun `assert given a log level equal to the current level returns true`() { 20 | val given = ILogger.Level.INFO 21 | val expected = true 22 | val actual = logger.isLoggable(given) 23 | assertEquals(expected, actual) 24 | } 25 | 26 | @Test 27 | fun `assert given a log level less than to the logger level returns false`() { 28 | val given = ILogger.Level.DEBUG 29 | val expected = false 30 | val actual = logger.isLoggable(given) 31 | assertEquals(expected, actual) 32 | } 33 | 34 | @Test 35 | fun `assert given a log level greater than to the logger level returns true`() { 36 | val given = ILogger.Level.ERROR 37 | val expected = true 38 | val actual = logger.isLoggable(given) 39 | assertEquals(expected, actual) 40 | } 41 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/fragments/bucket/BucketFile.graphql: -------------------------------------------------------------------------------- 1 | fragment BucketFile on File { 2 | contentType 3 | filename 4 | id 5 | url 6 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/fragments/market/MarketPlaceListingCore.graphql: -------------------------------------------------------------------------------- 1 | fragment MarketPlaceListingCore on MarketplaceListing { 2 | id 3 | primaryCategory { 4 | name 5 | } 6 | secondaryCategory { 7 | name 8 | } 9 | slug 10 | shortDescription 11 | isPaid 12 | isVerified 13 | logoBackgroundColor 14 | logoUrl 15 | name 16 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/fragments/paging/PageInfo.graphql: -------------------------------------------------------------------------------- 1 | fragment PageInfo on PageInfo { 2 | endCursor 3 | hasNextPage 4 | hasPreviousPage 5 | startCursor 6 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/fragments/repository/RepositoryCore.graphql: -------------------------------------------------------------------------------- 1 | fragment RepositoryCore on Repository { 2 | createdAt 3 | description 4 | forkCount 5 | homepageUrl 6 | id 7 | isFork 8 | isLocked 9 | isPrivate 10 | isTemplate 11 | name 12 | projectsUrl 13 | url 14 | viewerHasStarred 15 | watchers { 16 | totalCount 17 | } 18 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/fragments/user/UserCore.graphql: -------------------------------------------------------------------------------- 1 | fragment UserCore on User { 2 | avatarUrl 3 | bio 4 | company 5 | id 6 | status { 7 | createdAt 8 | emoji 9 | message 10 | } 11 | login 12 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/mutations/bucket/UploadToStorageBucket.graphql: -------------------------------------------------------------------------------- 1 | mutation UploadToStorageBucket($upload: Upload!) { 2 | uploadFile(fileData: $upload) { 3 | ...BucketFile 4 | } 5 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/mutations/repository/AddReactionToIssue.graphql: -------------------------------------------------------------------------------- 1 | mutation AddReactionToIssue($reactionInput: AddReactionInput!) { 2 | addReaction(input: $reactionInput) { 3 | reaction { 4 | content 5 | } 6 | subject { 7 | id 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/queries/bucket/StorageBucketFiles.graphql: -------------------------------------------------------------------------------- 1 | query StorageBucketFiles { 2 | files { 3 | ...BucketFile 4 | } 5 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/queries/market/GetMarketPlaceApps.graphql: -------------------------------------------------------------------------------- 1 | query GetMarketPlaceApps($first: Int, $after: String, $before: String) { 2 | marketplaceListings(first: $first, after: $after, before: $before) { 3 | edges { 4 | cursor 5 | node { 6 | ... MarketPlaceListingCore 7 | } 8 | } 9 | pageInfo { 10 | ... PageInfo 11 | } 12 | totalCount 13 | } 14 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/queries/repository/FindIssueId.graphql: -------------------------------------------------------------------------------- 1 | query FindIssueID($owner: String!, $name: String!, $issueId: Int!) { 2 | repository(owner:$owner, name:$name) { 3 | issue(number:$issueId) { 4 | id 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/queries/repository/GetRepository.graphql: -------------------------------------------------------------------------------- 1 | query GetRepository($name: String!, $owner: String!) { 2 | repository(name: $name, owner: $owner) { 3 | ... RepositoryCore 4 | } 5 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/queries/search/GeneralSearch.graphql: -------------------------------------------------------------------------------- 1 | query GeneralSearch($after: String, $before: String, $first: Int, $last: Int, $query: String!, $type: SearchType!) { 2 | search(after: $after,before: $before,first: $first,last: $last,query: $query,type: $type) { 3 | edges { 4 | cursor 5 | node { 6 | ... RepositoryCore 7 | ... UserCore 8 | ... MarketPlaceListingCore 9 | } 10 | textMatches { 11 | fragment 12 | highlights { 13 | beginIndice, 14 | endIndice, 15 | text 16 | } 17 | property 18 | } 19 | } 20 | pageInfo { 21 | ... PageInfo 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /library/src/test/resources/io/github/wax911/library/helpers/graphql/queries/user/GetCurrentUser.graphql: -------------------------------------------------------------------------------- 1 | query GetCurrentUser { 2 | viewer { 3 | ... UserCore 4 | } 5 | } -------------------------------------------------------------------------------- /scripts/configuration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function create_directories { 4 | mkdir -p ./app/.config/ 5 | cd ./app/.config/ 6 | } 7 | 8 | function create_files { 9 | touch secrets.properties 10 | echo "token=\"token\"" >> secrets.properties 11 | } 12 | 13 | 14 | create_directories 15 | create_files 16 | 17 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name= "retrofit-graphql" 2 | include(":library") 3 | 4 | if (!System.getenv().containsKey("CI")) { 5 | include(":app") 6 | } -------------------------------------------------------------------------------- /spotless/copyright.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright $YEAR AniTrend 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | --------------------------------------------------------------------------------