diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..817c42208 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,15 @@ +coverage: + range: 70..80 + round: down + precision: 2 + +comment: + layout: diff, files + +ignore: + - "**/fake" + - "**/commonTest" + - "**/androidTest" + - "**/iOSTest" + - "**/jsTest" + - "**/jvmTest" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..c9758e3bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Smartphone (please complete the following information):** + - Device: [e.g. Pixel 3] + - OS: [e.g. Android 10] + - Store Version [e.g. 4.0.0] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..d0c33e9ef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Feature Request] " +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/proposal.md b/.github/ISSUE_TEMPLATE/proposal.md new file mode 100644 index 000000000..98227a2df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/proposal.md @@ -0,0 +1,39 @@ +--- +name: Proposal +about: Propose an API change +title: "[Proposal] " +--- + +# Proposal: [Title] + +Author(s): [GitHub username] + +Last updated: [Date] + +## Abstract + +[A short summary of the proposal.] + +## Background + +[An introduction of the necessary background and the problem being solved by the proposed change.] + +## Proposal + +[A precise statement of the proposed change.] + +## Rationale + +[A discussion of alternate approaches and the trade offs, advantages, and disadvantages of the specified approach.] + +## Compatibility + +[A discussion of the change with regard to the current version of Store.] + +## Implementation + +[A description of the steps in the implementation, who will do them, and when.] + +## Open issues + +[A discussion of open issues relating to this proposal. This section may be omitted if there are none.] \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..f66d5b7c5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: gradle + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 1 + ignore: + update-types: ["version-update:semver-major"] diff --git a/.github/images/hero-light.svg b/.github/images/hero-light.svg new file mode 100644 index 000000000..6ba3359c4 --- /dev/null +++ b/.github/images/hero-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.github/images/kotlin-foundation.png b/.github/images/kotlin-foundation.png new file mode 100644 index 000000000..904066f24 Binary files /dev/null and b/.github/images/kotlin-foundation.png differ diff --git a/.github/images/mobile-native-foundation.png b/.github/images/mobile-native-foundation.png new file mode 100644 index 000000000..5b87ffb8e Binary files /dev/null and b/.github/images/mobile-native-foundation.png differ diff --git a/.github/workflows/add_issue_to_project.yml b/.github/workflows/add_issue_to_project.yml new file mode 100644 index 000000000..425c590dc --- /dev/null +++ b/.github/workflows/add_issue_to_project.yml @@ -0,0 +1,16 @@ +name: Add Issue To Project + +on: + issues: + types: + - opened + +jobs: + add-issue-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@main + with: + project-url: https://github.com/orgs/MobileNativeFoundation/projects/1 + github-token: ${{ secrets.ADD_ISSUE_TO_PROJECT }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..52e6c3eb3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build-and-test: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + api-level: + - 29 + steps: + + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref || github.ref }} + fetch-depth: 0 + persist-credentials: false + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '11' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Grant execute permission for Gradlew + run: chmod +x gradlew + + - name: Check Kotlin formatting + run: ./gradlew spotlessCheck + + - name: Run Kotlin static analysis + run: ./gradlew detekt + + - name: Build and Test with Coverage + run: ./gradlew clean build koverXmlReport --stacktrace + + - name: Upload Coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: build/reports/kover/coverage.xml + flags: unittests + name: codecov-umbrella + fail_ci_if_error: true + verbose: true + + publish: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'MobileNativeFoundation/Store' + runs-on: macos-latest + needs: build-and-test + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '11' + + - name: Grant execute permission for Gradlew + run: chmod +x gradlew + + - name: Upload Artifacts to Maven Central + run: ./gradlew publishAllPublicationsToMavenCentralRepository --no-daemon --no-parallel + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} + + - name: Retrieve Version + run: | + echo "VERSION_NAME=$(cat gradle.properties | grep -w "VERSION_NAME" | cut -d'=' -f2)" >> $GITHUB_ENV + + - name: Publish Release + run: ./gradlew closeAndReleaseRepository --no-daemon --no-parallel + if: "!endsWith(env.VERSION_NAME, '-SNAPSHOT')" + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/create_swift_package.yml b/.github/workflows/create_swift_package.yml new file mode 100644 index 000000000..dca0c1f99 --- /dev/null +++ b/.github/workflows/create_swift_package.yml @@ -0,0 +1,7 @@ +name: Create Swift Package + +on: + workflow_dispatch: +jobs: + publish: + uses: touchlab/KMMBridgeGithubWorkflow/.github/workflows/faktorybuildbranches.yml@v0.6 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6824847ca..397360e4e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ build/ # Local configuration file (sdk path, etc) local.properties -gradle.properties # Proguard folder generated by Eclipse proguard/ @@ -41,6 +40,19 @@ captures/ # Intellij *.iml .idea/ +.classpath +.project +.settings # Keystore files *.jks + +**/kover/html/ +*.podspec +.kotlin/ +yarn.lock + +# Ignore coverage reports +**/*/coverage.xml +**/*/build/kover/ +**/*/build/reports/kover/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c6b631d7e..000000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: android -jdk: -- oraclejdk8 -android: - components: - - tools - - platform-tools - - build-tools-26.0.1 - - android-25 - - extra-android-m2repository - licenses: - - 'android-sdk-license-.+' -script: -- ./gradlew check --stacktrace -after_success: - - gradle/deploy_snapshot.sh -branches: - except: - - gh-pages -notifications: - email: false -sudo: false -cache: - directories: - - $HOME/.m2 - - $HOME/.gradle -env: - global: - - secure: dKC1kpEFHhnCqt9+txDKxGcrHdFGUUM46KkKGvQXGDkvXFa2RcDiLCQ0qXfJvmdHTRAxvp7xpQeN1Vr2t+792YvWj90z9gPBDcPc3V+5D7MBXKB1PPPO4Kvq15Z8mRmNy64R7YIm35/MPz6TySi/VUmJ8QuoKlkVd3CsQM6GI77YztKyyA4Vrp078SSOCJLbZfQQ+KE/s3C9mIuTqHca8hBuNs8u9kZy8+znc+86NAnpt3aMEwICPHAUZD1AmPSL2WoOgtkVnHn6ShgmL0+vCZI0xesHHH256QCFY6nWA760hWMcD3VtG6WUGv8rjOIrDBAbl50Gp/6MU61OiSqstJKCVGS1ZjKP2pFLcKQTXvRCN5L2v8Q+kwRD9bT4Q06BxOj5JwG8kWBjy3uwlgSmlmh1iGXmlppt8jscpot5ErkevyBaq1w4zjnazR2NGXF04CeT6f8tfrfIEwPvlPNRLYWrA2ybyQMzlLAxQSNqc08FNef2q+djm+FKqN470nKp1bjI737fdrlAnVzSTEYGgn+2VCu1h/2aFldI6R9we3X3XgJo50+W6bQSU7Xj4LX1FjH4r8iZ1C9sfLQeenpwViFbLFb6uhNLd3OF6siTr6SaYYXRgVzStFtIuGT2LJl8a99cx7gXE3kMwo6Tu3Q1aOw1jVMiO5rxyBCwXzNLrBs= - - secure: 0MV6JaE+32cPY5qr+nC1UmshP7iwuwL9rbfhY2atJkyl+a31T0O+/u+76b8aNJTU95UZ4+3ZR6tXz/c0W+tEBT2A6JbH1vxHLuAVQPcYEjruTF4QgcMd/6Jm4bjIbaM5M3H6oUNcqNVyTBvpJm1Azc1KjwLWTtrhnnwa6bXTQvB/MHQ5NPMCp+4kolk7iACGP55WfS6thcFjZf+KaHWyN0WPOeSL5eneGIn+sS3ag72KXSxqgZgORJqbVpcOEPTu9Y71MNjcWLKLwZ0USYFNVVVgdY/nfDz61xLKydcQB9f7jvt+QgyncebarVlOQsisV3I/vnzmI2DDTAWz0eOT+4zFJCitQFfFoA+iUh0O8n++H47frEAkJTb01Nrbx0ZPc95iQ+JrKEkw5GgAAuBWJ8zgITFGHi1foBYTPjP8YhEMl3loepSXJmAZ2sdBnBXnwky5Tet8gHOaTATWaSAR15i1KKAjHmQ+pHscN6IQN2uZFDDAbZXjneKCCuUvCIwbO2tebTKRxP5idTgYkmUMN21aPtU3SZyewpBA69+NDwkp5y+1KQbYRVq+DpdR0mKtz4SMp+jLSRxSSl94wFADAaYsoPxr97pVmQQaIK8s5Q9LQBK24JgpE6Ed8fTQIZyf1SszLN98SxCkVFE4q8CS9vskHJ5lQH9/EkexV9L9e9g= diff --git a/CHANGELOG.md b/CHANGELOG.md index 0069d3cb1..d47cb26e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,237 @@ -Change Log -========== +# Changelog -The change log for Store version 1.x can be found [here](https://github.com/NYTimes/Store/blob/develop/CHANGELOG.md). +### Thank you to all our wonderful contributors and users -Version 3.0.0-beta *(2017-07-26)* ----------------------------- +## [5.0.0] (2023-09-14 ) +### Stable release of Store 5, major additions since Store 4 (no breaking changes) +* MutableStore +* Validator +* Fallback Mechanism +* KMP support +* Conflict Resolution for store writes +* Removal of experimental duration APIs +* StoreResult.NoNewData + +## [5.0.0-beta03] (2023-08-11) + +* Fix validator regression https://github.com/MobileNativeFoundation/Store/pull/573 + +## [5.0.0-beta02] (2023-07-21) + +* Fix breaking changes with Source of + Truth [#560](https://github.com/MobileNativeFoundation/Store/pull/560) + +## [5.0.0-beta01] (2023-05-19) + +* Delegate memory cache implementation and provide a hybrid cache with automatic list decomposition + as a separate + artifact [#548](https://github.com/MobileNativeFoundation/Store/pull/548) + +## [5.0.0-alpha06] (2023-05-08) + +* Separate MutableStoreBuilder from + StoreBuilder [#542](https://github.com/MobileNativeFoundation/Store/commit/e050a15afc21c22ffea10a6a7d5f1b436ee34a6a) +* Support + Rx2 [#531](https://github.com/MobileNativeFoundation/Store/commit/7d73f08cc07294d00b176325af792b51874dfeff) +* Introduce Fallback + Mechanisms [#545](https://github.com/MobileNativeFoundation/Store/commit/d1e46a9d02703c798738bc5fb645344fefb90dd4) + +## [5.0.0-alpha05] (2023-03-15) + +* Target iOS Simulator +* Target Linux +* Make Bookkeeper optional + +## [5.0.0-alpha04] (2023-02-24) + +* Introduce MutableStore +* Implement RealMutableStore with Store delegate +* Extract Store and MutableStore methods to use cases + +## [5.0.0-alpha03] (2022-12-18) + +This release adds support for Store on iOS, JVM, and JS. Concepts and usage are unchanged from +Store4. In a future release we will reintroduce support for local and remote writes with conflict +resolution based on Google's offline first guidance. + +* Target Android, iOS, JVM, JS +* Remove concept of Market +* Remove support for local and remote writes (temporary) + +## [5.0.0-alpha02] (2022-12-04) + +* Target iOS and JS +* Rename packages + +## [5.0.0-alpha1] (2022-12-04) + +* Introduce Market +* Support local and remote writes with conflict resolution based on Google's offline-first guidance +* Target Android and JVM + +## [4.0.5] (2021-03-30) + +* Update to Kotlin 1.6.10 + * Store `4.0.4-KT15` is the last version supporting Kotlin 1.5 + * Store `4.0.1` is the last version supporting Kotlin 1.4 + +## [4.0.4-KT15] (2021-12-08) + +* Bug fixes and documentation updates + +## [4.0.3-KT15] (2021-11-18) + +* Update to Kotlin 1.5.31 and Coroutines 1.5.2 +* Bug fixes and documentation updates + +## [4.0.2-KT15] (2021-05-06) + +**Kotlin 1.5 introduced breaking changes in the experimental Duration apis we used** +**4.0.2-KT15 is a duplicate of 4.0.1 but compiled for kotlin 1.5** +**Version 4.0.1 is the last version compatible with Kotlin 1.4** + +* Fire off kotlin 1.5 compatible snapshot (#273) + +## [4.0.1] (2021-05-06) + +* Fix issues when upgrading to kotlin 1.5 (Deprecated duration api) +* Add piggyback to all stores + +## [4.0.0] (2020-11-30) + +* Update coroutines to 1.4.0, kotlin to 1.4.10 [#242](https://github.com/dropbox/Store/pull/242) + +## [4.0.0-beta] (2020-09-21) + +**API change** + +* Remove need for generics with `Error` type (#220) + +**Bug Fixes and Stability Improvements** + +* Revert cache implementation to guava, rather than rolling our own (#200) +* Sample App improvements (#227) + +## [4.0.0-alpha07] (2020-08-19) + +**New Features** + +* Add `StoreResult.NoNewData` to represent when a fetcher didn't return data. (#194) +* Move `Fetcher`-factories into `Companion` of `Fetcher` interface (#168) + +**Bug Fixes and Stability Improvements** + +* Fix a leak of non-global coroutine contexts. (#199) +* Update to Kotlin 1.4.0 and Coroutines 1.3.9 (#195) +* Update to Coroutines 1.3.5 and remove `@FlowPreview` and `@ExperimentalCoroutinesApi` + annotations. (#166) + +## [4.0.0-alpha06] (2020-04-29) + +**Major API change!** (#123) + +This release introduces a major change to `StoreBuilder`'s API. This should be the LAST major API +change to store before +we'll move to beta. + +* The typealias `Fetcher` was added to standardize the input type for a `StoreBuilder` +* `SourceOfTruth` in now a top level interface and part of `Store`'s public API +* `StoreBuilder` can now only be created using a `Fetcher` and optionally a `SourceOfTruth` +* All the overloads for creating a `StoreBuilder` were moved to `Fetcher` and `SourceOfTruth` as + appropriate. +* Rx artifacts were updated accordingly to match main artifacts. + +## [4.0.0-alpha05] (2020-04-03) + +**Bug Fixes and Stability Improvements** + +* Contain @ExperimentalStdlibApi within relevant scope. (#154) +* Use AtomicFu to replace Java's AtomicBoolean and ReentrantLock (#147) +* migrate Multicast to Kotlin Test (#146) +* Remove Collections.unmodifiableMap (#145) +* Update AGP version (#143) +* Remove some unneeded java.util packages (#141) + +## [4.0.0-alpha04] (2020-04-03) + +**New Features** + +* Add `asMap` function to Cache for backward compat (#136) +* Migrate filesystem library to use kotlin.time APIs (#133) +* Rx get fresh bindings (#130) +* Migrate cache library to use kotlin.time APIs (#129) +* Update sample app (#117) + +**Bug Fixes and Stability Improvements** + +* Use Kotlin version of ArrayDeque in ChannelManager (#134) +* Kotlin 1.3.70 and other dependencies updates (#125) +* Make SharedFlowProducer APIs safe (#121) +* Ensure network starts after disk is established (#115) +* Update to Gradle 6.2 (#111) + +## [4.0.0-alpha03] (2020-02-13) + +**New Features** + +* Added Rx bindings, available as store-rx2 artifact (#93) +* Bug fixes (#90) +* Add ability to delete all entries in the store (#79) + +## [4.0.0-alpha02] (2020-01-29) + +**New Features** + +* Introduce piggyback only downstreams to multicaster and fix #59 (#75) +* Change flow collection util to drain the flow (#64) +* Readme improvements (#70, #72) +* Avoid illegal cast in RealStore.stream (#69) +* Added docs to MemoryPolicy.setMemorySize (#67) (#68) + +## [4.0.0-alpha01] (2020-01-08) + +**New Features** + +* Store has been rewritten using Kotlin Coroutines instead of RxJava + +## [3.1.0] (2018-06-07) + +**New Features** + +* (#319) Store can now be used in Java (non-Android) projects +* (#338) Room integration for Store + +**Bug Fixes and Stability Improvements** + +* (#315) Add missing reading of expire-after-policy when creating a NoopPersister +* (#311) Update Kotlin & AGP versions +* (#328) Fix memory policy default size +* (#329) Adding docs to README for setting 1.8 compatibility +* (#273) Adds comments to the sample app +* (#336) Fixes errors in README + +## [3.0.1] (2018-03-20) + +**Bug Fixes and Stability Improvements** + +* (#311) Update Kotlin & AGP versions +* (#314) Fix issues occured from RxJava1 dependency + +## [3.0.0] (2018-02-01) + +**New Features** + +* (#275) Add ParsingFetcher that wraps Raw type Parser and Fetcher + +**Bug Fixes and Stability Improvements** + +* (#267) Kotlin 1.1.4 for store-kotlin +* (#290) Remove @Experimental from store-kotlin API +* (#283) Update build tools to 26.0.2 +* (#259, #261, #272, #289, #303) README + documentation updates +* (#310) Sample app fixes + +## [3.0.0-beta] (2017-07-26) **New Features** @@ -21,29 +248,93 @@ Version 3.0.0-beta *(2017-07-26)* * (#246) Update to Moshi 1.5.0 * (#252) Fix stream for a single barcode -Version 3.0.0-alpha *(2017-05-23)* ----------------------------- +## [3.0.0-alpha] (2017-05-23) This is a first alpha release of Store ported to RxJava 2. **New Features** * (#155) Port to RxJava 2 -* (#220) Packages have been renamed to store3 to allow use of this artifact alongside the original Store +* (#220) Packages have been renamed to store3 to allow use of this artifact alongside the original + Store * (#185) Return Single/Maybe where appropriate * (#189) Add lambdas to Store and Filesystem modules * (#214) expireAfterAccess added to MemoryPolicy -* (#214) Deprecate setExpireAfter and getExpireAfter -- use new expireAfterWrite or expireAfterAccess, see #199 for -MemoryPolicy changes +* (#214) Deprecate setExpireAfter and getExpireAfter -- use new expireAfterWrite or + expireAfterAccess, see #199 for + MemoryPolicy changes * (#214) Add Raw to BufferedSource transformer - **Bug Fixes and Stability Improvements** * (#214) Fix networkBeforeStale on cold start with no connectivity * (#214) Add a missing source.close() call -* (#164) FileSystemPersister.persisterIsStale() should return false if record is missing or policy is unspecified +* (#164) FileSystemPersister.persisterIsStale() should return false if record is missing or policy + is unspecified * (#166) Remove apt dependency and use annotationProcessor instead * (#214) Standardize store.stream() to emit only new items * (#214) Fix typos -* (#214) Close source after write to filesystem \ No newline at end of file +* (#214) Close source after write to filesystem + +## [1.x] + +* The change log for Store version 1.x can be + found [here](https://github.com/NYTimes/Store/blob/develop/CHANGELOG.md). + +[Unreleased]: https://github.com/MobileNativeFoundation/Store/compare/v5.0.0-beta02...HEAD + +[5.0.0-beta02]: https://github.com/MobileNativeFoundation/Store/releases/tag/5.0.0-beta02 + +[5.0.0-beta01]: https://github.com/MobileNativeFoundation/Store/releases/tag/5.0.0-beta01 + +[5.0.0-alpha06]: https://github.com/MobileNativeFoundation/Store/releases/tag/5.0.0-alpha06 + +[5.0.0-alpha05]: https://github.com/MobileNativeFoundation/Store/releases/tag/5.0.0-alpha05 + +[5.0.0-alpha04]: https://github.com/MobileNativeFoundation/Store/releases/tag/5.0.0-alpha04 + +[5.0.0-alpha03]: https://github.com/MobileNativeFoundation/Store/releases/tag/5.0.0-alpha03 + +[5.0.0-alpha02]: https://github.com/MobileNativeFoundation/Store/releases/tag/5.0.0-alpha02 + +[5.0.0-alpha1]: https://github.com/MobileNativeFoundation/Store/releases/tag/5.0.0-alpha1 + +[4.0.5]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.5 + +[4.0.4-KT15]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.4-KT15 + +[4.0.3-KT15]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.3-KT15 + +[4.0.2-KT15]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.2-KT15 + +[4.0.1]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.1 + +[4.0.0]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.0 + +[4.0.0-beta]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.0-beta + +[4.0.0-alpha07]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.0-alpha07 + +[4.0.0-alpha06]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.0-alpha06 + +[4.0.0-alpha05]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.0-alpha05 + +[4.0.0-alpha04]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.0-alpha04 + +[4.0.0-alpha03]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.0-alpha03 + +[4.0.0-alpha02]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.0-alpha02 + +[4.0.0-alpha01]: https://github.com/MobileNativeFoundation/Store/releases/tag/4.0.0-alpha01 + +[3.1.0]: https://github.com/MobileNativeFoundation/Store/releases/tag/3.1.0 + +[3.0.1]: https://github.com/MobileNativeFoundation/Store/releases/tag/3.0.1 + +[3.0.0]: https://github.com/MobileNativeFoundation/Store/releases/tag/3.0.0 + +[3.0.0-beta]: https://github.com/MobileNativeFoundation/Store/releases/tag/3.0.0-beta + +[3.0.0-alpha]: https://github.com/MobileNativeFoundation/Store/releases/tag/3.0.0-alpha + +[1.x]: https://github.com/NYTimes/Store/blob/develop/CHANGELOG.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 46bfe401e..b0c254b9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,38 +1,28 @@ -Contributing to Store -======================= - -The New York Times team welcomes contributions of all kinds, from simple bug reports through documentation, test cases, -bugfixes, and features. - -DOs and DON'Ts --------------- - -* DO follow our coding style (as described below) -* DO give priority to the current style of the project or file you're changing even if it diverges from the general guidelines. -* DO include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken. -* DO keep the discussions focused. When a new or related topic comes up it's often better to create new issue than to side track the discussion. -* DO run all Gradle verification tasks (`./gradlew check`) before submitting a pull request - -* DO NOT send PRs for style changes. -* DON'T surprise us with big pull requests. Instead, file an issue and start a discussion so we can agree on a direction before you invest a large amount of time. -* DON'T commit code that you didn't write. If you find code that you think is a good fit, file an issue and start a discussion before proceeding. -* DON'T submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it. - - -Coding Style ------------- - -The coding style employed here is fairly conventional Java - indentations are four spaces, class -names are PascalCased, identifiers and methods are camelCased. - -Workflow --------- - -We love Github issues! Before working on any new features, please open an issue so that we can agree on the -direction, and hopefully avoid investing a lot of time on a feature that might need reworking. - -Small pull requests for things like typos, bugfixes, etc are always welcome. - -Please note that we will not accept pull requests for style changes. - - +# Contributing to Store +Thanks for considering contributing to Store. This document provides guidelines and information about how you can contribute. + +## Getting Started +- **Fork the Repository**: Start by forking the [MobileNativeFoundation/Store](https://github.com/MobileNativeFoundation/Store) repository. +- **Clone the Fork**: Clone your fork to your machine to start working on the changes. + +## Contribution Workflow +### Reporting Issues +- **Search Existing Issues**: Before creating a new issue, please do a search in existing issues to see if it has been reported or fixed. +- **Create a Detailed Issue**: If you find a bug or have a feature request, please create an issue with a clear title and a detailed description. +### Submitting Changes +- **Create a Branch**: Create a branch in your fork for your contribution. +- **Make Your Changes**: Make your changes and commit them to your branch. Make sure to write clear, concise commit messages. +- **Write Tests**: If you are adding new features or fixing bugs, write tests that cover your changes. +- **Run the Tests**: Run the project's existing tests to ensure nothing is broken. +- **Create a Pull Request**: Submit a PR to the main repository for review. Include a clear description of the changes and any relevant issue numbers. +### Code Review Process +- **Wait for Review**: Maintainers will review your PR and might request changes. +- **Make Requested Changes**: If changes are requested, make them and update your PR. +- **Merge**: Once your PR is approved, a maintainer will merge it into the main codebase. + +## Community Guidelines +- **Be Respectful**: Treat everyone with respect. We strive to create a welcoming and inclusive environment. +- **Follow the Code of Conduct**: Familiarize yourself with our [Code of Conduct](https://github.com/MobileNativeFoundation/Store/blob/main/CODE_OF_CONDUCT.md). + +## Getting Help +- **Join the Community**: If you have questions or need help, join our [Slack channel](https://kotlinlang.slack.com/archives/C06007Z01HU). diff --git a/Images/friendly_robot.png b/Images/friendly_robot.png new file mode 100644 index 000000000..424a3f78c Binary files /dev/null and b/Images/friendly_robot.png differ diff --git a/Images/friendly_robot_icon.png b/Images/friendly_robot_icon.png new file mode 100755 index 000000000..558096a81 Binary files /dev/null and b/Images/friendly_robot_icon.png differ diff --git a/Images/store-logo.png b/Images/store-logo.png deleted file mode 100644 index 226184ada..000000000 Binary files a/Images/store-logo.png and /dev/null differ diff --git a/LICENSE b/LICENSE index df6192d36..eef1e28f1 100644 --- a/LICENSE +++ b/LICENSE @@ -188,6 +188,8 @@ Copyright 2016-2017 The New York Times Company + Copyright (c) 2019 Dropbox, Inc. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/README.md b/README.md index d2e87e495..f67ae8b45 100644 --- a/README.md +++ b/README.md @@ -1,335 +1,38 @@ -[![Build Status](https://travis-ci.org/NYTimes/Store.svg?branch=feature/rx2)](https://travis-ci.org/NYTimes/Store) + -![Store Logo](https://raw.githubusercontent.com/NYTimes/Store/feature/rx2/Images/store-logo.png) +# Store5 -Store is an Android library for effortless, reactive data loading. +[![codecov](https://codecov.io/gh/MobileNativeFoundation/Store/branch/main/graph/badge.svg?token=0UCmG3QHPf)](https://codecov.io/gh/MobileNativeFoundation/Store) -### The Problems: +#### Documentation -+ Modern Android Apps need their data representations to be fluid and always available. -+ Users expect their UI experience to never be compromised (blocked) by new data loads. Whether an application is social, news, or business-to-business, users expect a seamless experience both online and offline. -+ International users expect minimal data downloads as many megabytes of downloaded data can quickly result in astronomical phone bills. +Comprehensive guides, tutorials, and API reference: [store.mobilenativefoundation.org](https://store.mobilenativefoundation.org). -A Store is a class that simplifies fetching, parsing, storage, and retrieval of data in your application. A Store is similar to the Repository pattern [[https://msdn.microsoft.com/en-us/library/ff649690.aspx](https://msdn.microsoft.com/en-us/library/ff649690.aspx)] while exposing a Reactive API built with RxJava that adheres to a unidirectional data flow. +#### Getting Started -Store provides a level of abstraction between UI elements and data operations. +1. Start with the [Quickstart](https://store.mobilenativefoundation.org/docs/quickstart) to build your first Store. +2. Dive into [Store Foundations](https://store.mobilenativefoundation.org/docs/concepts) to learn how Store works. +3. Check out [Handling CRUD](https://store.mobilenativefoundation.org/docs/use-cases/store5/setting-up-store-for-crud-operations) for an advanced guide on supporting create, read, update, and delete operations. -### Overview +#### Getting Help -A Store is responsible for managing a particular data request. When you create an implementation of a Store, you provide it with a `Fetcher`, a function that defines how data will be fetched over network. You can also define how your Store will cache data in-memory and on-disk, as well as how to parse it. Since Store returns your data as an Observable, threading is a breeze! Once a Store is built, it handles the logic around data flow, allowing your views to use the best data source and ensuring that the newest data is always available for later offline use. Stores can be customized to work with your own implementations or use our included middleware. +Join our community in the [#store](https://kotlinlang.slack.com/archives/C06007Z01HU) channel on the official Kotlin Slack. -Store leverages RxJava and multiple request throttling to prevent excessive calls to the network and disk cache. By utilizing Store, you eliminate the possibility of flooding your network with the same request while adding two layers of caching (memory and disk). - -### Fully Configured Store -Let's start by looking at what a fully configured Store looks like. We will then walk through simpler examples showing each piece: -```java -Store articleStore = StoreBuilder.parsedWithKey() - .fetcher(articleId -> api.getArticleAsBufferedSource(articleId)) //OkHttp responseBody.source() - .persister(FileSystemPersister.create(FileSystemFactory.create(context.getFilesDir()),pathResolver)) - .parser(GsonParserFactory.createSourceParser(gson, ArticleAsset.Article.class)) - .open(); - -``` - -With the above setup you have: -+ In Memory Caching for rotation -+ Disk caching for when users are offline -+ Parsing through streaming API to limit memory consumption -+ Rich API to ask for data whether you want cached/new or a stream of future data updates. - -And now for the details: - -### Creating a Store - -You create a Store using a builder. The only requirement is to include a `.Fetcher` that returns an Single and has a single method `fetch(key)` - - -``` java - Store store = StoreBuilder.<>key() - .fetcher(articleId -> api.getArticle(articleId)) //OkHttp responseBody.source() - .open(); -``` -Stores use generic keys as identifiers for data. A key can be any value object that properly implements toString and equals and hashCode. When your Fetcher function is called, it will be passed a particular Key value. Similarly, the key will be used as a primary identifier within caches (Make sure to have a proper hashCode!!) - -### Our Key implementation - Barcodes -For convenience we included our own key implementation called a BarCode. Barcode has two fields `String key and String type` -``` java -BarCode barcode = new BarCode("Article", "42"); -``` -When using a Barcode as your key, you can use a StoreBuilder convenience method -``` java - Store store = StoreBuilder.barcode() - .fetcher(articleBarcode -> api.getAsset(articleBarcode.getKey(),articleBarcode.getType())) - .open(); -``` - - - -### Public Interface - Get, Fetch, Stream, GetRefreshing - -```java -Single
article = store.get(barCode); -``` - -The first time you subscribe to `store.get(barCode)`, the response will be stored in an in-memory cache. All subsequent calls to `store.get(barCode)` with the same Key will retrieve the cached version of the data, minimizing unnecessary data calls. This prevents your app from fetching fresh data over the network (or from another external data source) in situations when doing so would unnecessarily waste bandwidth and battery. A great use case is any time your views are recreated after a rotation, they will be able to request the cached data from your Store. Having this data available can help you avoid the need to retain this in the view layer. - - -So far our Store’s data flow looks like this: -![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-1.jpg) - - -By default, 100 items will be cached in memory for 24 hours. You may pass in your own instance of a Guava Cache to override the default policy. - - -### Busting through the cache - -Alternatively you can call `store.fetch(barCode)` to get an Observable that skips the memory (and optional disk cache). +#### Getting Involved +Store has a vibrant community of contributors. We welcome contributions of all kinds. Please see our [Contributing Guidelines](CONTRIBUTING.md) for more information on how to get involved. -Fresh data call will look like: `store.fetch()` -![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-2.jpg) +#### Backed By +
+ + +
-In the New York Times app, overnight background updates use `fetch` to make sure that calls to `store.get()` will not have to hit the network during normal usage. Another good use case for `fetch` is when a user wants to pull to refresh. +#### License - -Calls to both `fetch()` and `get()` emit one value and then call `onCompleted()` or throw an error. - - -### Stream -For real-time updates, you may also call `store.stream()` which returns an Observable that emits each time a new item is added to the Store. You can think of stream as an Event Bus-like feature that allows you to know when any new network hits happen for a particular Store. You can leverage the Rx operator `filter()` to only subscribe to a subset of emissions. - -### Get Refreshing -There is another special way to subscribe to a Store: getRefreshing(key). Get Refreshing will subscribe to get() which returns a single response, but unlike Get, Get Refreshing will stay subscribed. Anytime you call store.clear(key) anyone subscribed to getRefreshing(key) will resubscribe and force a new network response. - - -### Inflight Debouncer - -To prevent duplicative requests for the same data, Store offers an inflight debouncer. If the same request is made within a minute of a previous identical request, the same response will be returned. This is useful for situations when your app needs to make many async calls for the same data at startup or for when users are obsessively pulling to refresh. As an example, The New York Times news app asynchronously calls `ConfigStore.get()` from 12 different places on startup. The first call blocks while all others wait for the data to arrive. We have seen a dramatic decrease in the app's the data usage after implementing this in flight logic. - - -### Adding a Parser - -Since it is rare for data to arrive from the network in the format that your views need, Stores can delegate to a parser by using a `StoreBuilder.parsedWithKey() - -```java -Store store = StoreBuilder.parsedWithKey() - .fetcher(articleId -> api.getArticle(articleId)) - .parser(source -> { - try (InputStreamReader reader = new InputStreamReader(source.inputStream())) { - return gson.fromJson(reader, Article.class); - } catch (IOException e) { - throw new RuntimeException(e); - } - }) - .open(); +```text +Copyright (c) 2024 Mobile Native Foundation. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. ``` - -Our updated data flow now looks like this: - -`store.get()` -> ![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-3.jpg) - - - -### Middleware - GsonSourceParser - -There are also separate middleware libraries with parsers to help in cases where your fetcher is a Reader, BufferedSource or String and your parser is Gson: -- GsonReaderParser -- GsonSourceParser -- GsonStringParser - -These can be accessed via a Factory class (GsonParserFactory). - -Our example can now be rewritten as: -```java -Store store = StoreBuilder.parsedWithKey() - .fetcher(articleId -> api.getArticle(articleId)) - .parser(GsonParserFactory.createSourceParser(gson, Article.class)) - .open(); -``` - -In some cases you may need to parse a top level JSONArray, in which case you can provide a TypeToken. -```java -Store,Integer> store = StoreBuilder.>parsedWithKey() - .fetcher(articleId -> api.getArticles()) - .parser(GsonParserFactory.createSourceParser(gson, new TypeToken>() {})) - .open(); - - -``` - -Similarly we have a middleware artifact for Moshi & Jackson too! - - -### Disk Caching - -Stores can enable disk caching by passing a Persister into the builder. Whenever a new network request is made, the Store will first write to the disk cache and then read from the disk cache. - - -Now our data flow looks like: -`store.get()` -> ![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-5.jpg) - - - - Ideally, data will be streamed from network to disk using either a BufferedSource or Reader as your network raw type (rather than String). - -```java -Store store = StoreBuilder.parsedWithKey() - .fetcher(articleId -> api.getArticles()) - .persister(new Persister() { - @Override - public Maybe read(Integer key) { - if (dataIsCached) { - return Observable.fromCallable(() -> userImplementedCache.get(key)); - } else { - return Observable.empty(); - } - } - - @Override - public Single write(BarCode barCode, BufferedSource source) { - userImplementedCache.save(key, source); - return Single.just(true); - } - }) - .parser(GsonParserFactory.createSourceParser(gson, Article.class)) - .open(); -``` - -Stores don’t care how you’re storing or retrieving your data from disk. As a result, you can use Stores with object storage or any database (Realm, SQLite, CouchDB, Firebase etc). The only requirement is that data must be the same type when stored and retrieved as it was when received from your Fetcher. Technically there is nothing stopping you from implementing an in memory cache for the “persister” implementation and instead have two levels of in memory caching--one with inflated and one with deflated models, allowing for sharing of the “persister” cache data between stores. - - -**Note**: When using a Parser and a disk cache, the Parser will be called AFTER fetching from disk and not between the network and disk. This allows your persister to work on the network stream directly. - - -If using SQLite we recommend working with SqlBrite. If you are not using SqlBrite, an Observable can be created rather simply with `Observable.fromCallable(() -> getDBValue())` - -### Middleware - SourcePersister & FileSystem - -We've found the fastest form of persistence is streaming network responses directly to disk. As a result, we have included a separate library with a reactive FileSystem which depends on Okio BufferedSources. We have also included a FileSystemPersister which will give you disk caching and works beautifully with GsonSourceParser. When using the FileSystemPersister you must pass in a `PathResolver` which will tell the file system how to name the paths to cache entries. - -Now back to our first example: - -```java -Store store = StoreBuilder.parsedWithKey() - .fetcher(articleId -> api.getArticles(articleId)) - .persister(FileSystemPersister.create(FileSystemFactory.create(context.getFilesDir()),pathResolver)) - .parser(GsonParserFactory.createSourceParser(gson, String.class)) - .open(); -``` - -As mentioned, the above builder is how we work with network operations at the New York Times. With the above setup you have: -+ Memory caching with Guava Cache -+ Disk caching with FileSystem (you can reuse the same file system implementation for all stores) -+ Parsing from a BufferedSource to a (Article in our case) with Gson -+ In-flight request management -+ Ability to get cached data or bust through your caches (get vs. fresh) -+ Ability to listen for any new emissions from network (stream) -+ Ability to be notified and resubscribed when caches are cleared (helpful for times when you need to do a post request and update another screen, such as with `getRefreshing`) - -We recommend using the above builder setup for most Stores. The SourcePersister implementation has a tiny memory footprint because it will stream bytes from network to disk and then from disk to parser. The streaming nature of Stores allows us to download dozens of 1mb+ json responses without worrying about OOM on low-memory devices. As mentioned above, Stores allow us to do things like calling `configStore.get()` a dozen times asynchronously before our Main Activity finishes loading without blocking the main thread or flooding our network. - -### RecordProvider -If you'd like your Store to know about disk data staleness, you can have your `Persister` implement `RecordProvider`. After doing so you can configure your Store to work in one of two ways: - -```java -store = StoreBuilder.barcode() - .fetcher(fetcher) - .persister(persister) - .refreshOnStale() - .open(); - -``` - -`refreshOnStale` will backfill the disk cache anytime a record is stale. The user will still get the stale record returned to them. - -Or alternatively: - -```java - store = StoreBuilder.barcode() - .fetcher(fetcher) - .persister(persister) - .networkBeforeStale() - .open(); -``` -`networkBeforeStale` - Store will try to get network source when disk data is stale. If the network source throws an error or is empty, stale disk data will be returned. - - -### Subclassing a Store - -We can also subclass a Store implementation (RealStore): - -```java -public class SampleStore extends RealStore { - public SampleStore(Fetcher fetcher, Persister persister) { - super(fetcher, persister); - } -} -``` - -Subclassing is useful when you’d like to inject Store dependencies or add a few helper methods to a store: - -```java -public class SampleStore extends RealStore { - @Inject - public SampleStore(Fetcher fetcher, Persister persister) { - super(fetcher, persister); - } -} -``` - - -### Artifacts - -**CurrentVersion = 3.0.0-beta** - -+ **Cache** Cache extracted from Guava (keeps method count to a minimum) - - ```groovy - compile 'com.nytimes.android:cache3:CurrentVersion' - ``` -+ **Store** This contains only Store classes and has a dependecy on RxJava + the above cache. - - ```groovy - compile 'com.nytimes.android:store3:CurrentVersion' - ``` -+ **Store-Kotlin** Store plus a couple of added Kotlin classes for more idiomatic usage. - - ```groovy - compile 'com.nytimes.android:store-kotlin3:CurrentVersion' - ``` -+ **Middleware** Sample Gson parsers, (feel free to create more and open PRs) - - ```groovy - compile 'com.nytimes.android:middleware3:CurrentVersion' - ``` -+ **Middleware-Jackson** Sample Jackon parsers, (feel free to create more and open PRs) - - ```groovy - compile 'com.nytimes.android:middleware-jackson3:CurrentVersion' - ``` -+ **Middleware-Moshi** Sample Moshi parsers, (feel free to create more and open PRs) - - ```groovy - compile 'com.nytimes.android:middleware-moshi3:CurrentVersion' - ``` -+ **File System** Persistence Library built using OKIO Source/Sink + Middleware for streaming from Network to FileSystem - - ```groovy - compile 'com.nytimes.android:filesystem3:CurrentVersion' - ``` - -### Sample Project - -See the app for example usage of Store. Alternatively, the Wiki contains a set of recipes for common use cases -+ Simple Example: Retrofit + Store -+ Complex Example: BufferedSource from Retrofit (Can be OKHTTP too) + our FileSystem + our GsonSourceParser - -### Talks -[DroidCon Italy](https://youtu.be/TvsOsgd0--c) -[Android Makers](https://www.youtube.com/watch?time_continue=170&v=G1MebI2k9aA) - -### Community projects - -+ https://github.com/stoyicker/master-slave-clean-store: An offline-first Master-Slave project with scroll-driven pagination using Store for the data layer. -+ https://github.com/benoberkfell/cat-rates: [Ben Oberkfell's 360AnDev talk, "Android Architecture for the Subway"](https://academy.realm.io/posts/360-andev-2017-ben-oberkfell-android-architecture-offline-first/) illustrates using Store for caching server responses diff --git a/RELEASING.md b/RELEASING.md index 736bfe4bf..4d42171d9 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,16 +1,28 @@ Releasing ======== - 1. Change the version in top level `build.gradle` to a non-SNAPSHOT verson. - 2. Update the `CHANGELOG.md` for the impending release. - 3. Update the `README.md` with the new version. - 4. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) - 5. `./gradlew clean uploadArchives`. - 6. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. - 7. `git tag -a X.Y.X -m "Version X.Y.Z"` (where X.Y.Z is the new version) - 8. Update the top level `build.gradle` to the next SNAPSHOT version. - 9. `git commit -am "Prepare next development version."` - 10. `git push && git push --tags` - 11. Update the sample module to point to the newly released version. (May take 2 hours) - -If step 5 or 6 fails, drop the Sonatype repo, fix the problem, commit, and start again at step 5. +1. Change the version in top level `gradle.properties` to a non-SNAPSHOT version. +2. Update the `cocoapods` version in `build.gradle.kts` in `:store`. +3. Modify `create_swift_package.yml` workflow. + * https://github.com/MobileNativeFoundation/Store/blob/e526400cdf51aa2f78b6b7e9e87f4a6845e6dcea/.github/workflows/create_swift_package.yml +4. Update the `CHANGELOG.md` for the impending release. +5. Update the `README.md` with the new version. +6. `git commit -sam "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) +7. `git tag -a X.Y.X -m "Version X.Y.Z"` (where X.Y.Z is the new version) + * Run `git tag` to verify it. +8. `git push && git push --tags` + * This should be pushed to your fork. +9. Create a PR with this commit and merge it. +10. Update the top level `build.gradle` to the next SNAPSHOT version. +11. Modify `create_swift_package.yml` workflow to only run manually. + * https://github.com/MobileNativeFoundation/Store/blob/de9ed1764408eeaafe5e58fe602205c875a8b0b0/.github/workflows/create_swift_package.yml +12. `git commit -am "Prepare next development version."` +13. Create a PR with this commit and merge it. +14. Login to Sonatype to promote the artifacts https://central.sonatype.org/pages/releasing-the-deployment.html + * This part is automated. If it fails in CI, follow the steps below. + * Click on Staging Repositories under Build Promotion + * Select all the Repositories that contain the content you want to release + * Click on Close and refresh until the Release button is active + * Click Release and submit +15. Update the sample module's `build.gradle` to point to the newly released version. (It may take ~2 hours for artifact to be available after release) + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 796b96d1c..000000000 --- a/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 6e0a3a6ad..000000000 --- a/app/build.gradle +++ /dev/null @@ -1,56 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'com.getkeepsafe.dexcount' - -android { - compileSdkVersion versions.compileSdk - buildToolsVersion versions.buildTools - defaultConfig { - applicationId "com.nytimes.android.store.sample" - minSdkVersion 19 - compileSdkVersion versions.compileSdk - targetSdkVersion versions.targetSdk - versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - lintOptions { - abortOnError false - disable 'InvalidPackage' - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - packagingOptions { - exclude 'META-INF/rxjava.properties' - } -} - -dependencies { - - testImplementation libraries.junit - - implementation libraries.supportRecyclerView - implementation libraries.supportAppCompat - implementation libraries.supportCardView - implementation libraries.supportDesign - implementation libraries.retrofit - implementation libraries.retrofitGsonConverter - implementation libraries.retrofitRx2 - implementation libraries.picasso - implementation libraries.guava - annotationProcessor libraries.immutablesValue // <-- for annotation processor - compileOnly libraries.immutablesValue // <-- for annotation API - compileOnly libraries.immutablesGson // for annotations - implementation 'com.nytimes.android:store3:3.0.0-beta' - implementation 'com.nytimes.android:cache3:3.0.0-beta' - implementation 'com.nytimes.android:middleware3:3.0.0-beta' - implementation 'com.nytimes.android:filesystem3:3.0.0-beta' - implementation libraries.rxAndroid2 -} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index a565a62f7..000000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/206847/Library/Android/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index af1966639..000000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.java b/app/src/main/java/com/nytimes/android/sample/SampleApp.java deleted file mode 100644 index 6f9150664..000000000 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.nytimes.android.sample; - -import android.app.Application; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.nytimes.android.external.fs3.SourcePersisterFactory; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.MemoryPolicy; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.middleware.GsonParserFactory; -import com.nytimes.android.sample.data.model.GsonAdaptersModel; -import com.nytimes.android.sample.data.model.RedditData; -import com.nytimes.android.sample.data.remote.Api; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import io.reactivex.Single; -import okhttp3.ResponseBody; -import okio.BufferedSource; -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import retrofit2.converter.gson.GsonConverterFactory; - -public class SampleApp extends Application { - - private Store nonPersistedStore; - private Store persistedStore; - private Persister persister; - - @Override - public void onCreate() { - super.onCreate(); - - initPersister(); - this.nonPersistedStore = provideRedditStore(); - this.persistedStore = providePersistedRedditStore(); - } - - private void initPersister() { - try { - persister = newPersister(); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - } - - public Store getNonPersistedStore() { - return this.nonPersistedStore; - } - - public Store getPersistedStore() { - return this.persistedStore; - } - - private Store provideRedditStore() { - return StoreBuilder.barcode() - .fetcher(barCode -> provideRetrofit().fetchSubreddit(barCode.getKey(), "10")) - .memoryPolicy( - MemoryPolicy - .builder() - .setExpireAfter(10) - .setExpireAfterTimeUnit(TimeUnit.SECONDS) - .build() - ) - .open(); - } - - private Store providePersistedRedditStore() { - return StoreBuilder.parsedWithKey() - .fetcher(this::fetcher) - .persister(persister) - .parser(GsonParserFactory.createSourceParser(provideGson(), RedditData.class)) - .open(); - } - - private Persister newPersister() throws IOException { - return SourcePersisterFactory.create(getApplicationContext().getCacheDir()); - } - - private Single fetcher(BarCode barCode) { - return provideRetrofit().fetchSubredditForPersister(barCode.getKey(), "10") - .map(ResponseBody::source); - } - - private Api provideRetrofit() { - return new Retrofit.Builder() - .baseUrl("http://reddit.com/") - .addConverterFactory(GsonConverterFactory.create(provideGson())) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .validateEagerly(BuildConfig.DEBUG) // Fail early: check Retrofit configuration at creation time in Debug build. - .build() - .create(Api.class); - } - - Gson provideGson() { - return new GsonBuilder() - .registerTypeAdapterFactory(new GsonAdaptersModel()) - .create(); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java deleted file mode 100644 index 6d41ce3d6..000000000 --- a/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.nytimes.android.sample.activity; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.widget.Toast; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.SampleApp; -import com.nytimes.android.sample.data.model.Children; -import com.nytimes.android.sample.data.model.Post; -import com.nytimes.android.sample.data.model.RedditData; -import com.nytimes.android.sample.reddit.PostAdapter; - -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -import static android.widget.Toast.makeText; - - -public class PersistingStoreActivity extends AppCompatActivity { - - private RecyclerView recyclerView; - private PostAdapter postAdapter; - private Store persistedStore; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_store); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - - postAdapter = new PostAdapter(); - recyclerView = (RecyclerView) findViewById(R.id.postRecyclerView); - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - layoutManager.setOrientation(LinearLayoutManager.VERTICAL); - recyclerView.setLayoutManager(layoutManager); - recyclerView.setAdapter(postAdapter); - } - - private void initStore() { - if (this.persistedStore == null) { - this.persistedStore = ((SampleApp) getApplicationContext()).getPersistedStore(); - } - } - - @SuppressWarnings("CheckReturnValue") - public void loadPosts() { - BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); - - this.persistedStore - .get(awwRequest) - .flatMapObservable(this::sanitizeData) - .toList() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::showPosts, throwable -> - Log.e(StoreActivity.class.getSimpleName(), throwable.getMessage(), - throwable)); - } - - private void showPosts(List posts) { - postAdapter.setPosts(posts); - makeText(PersistingStoreActivity.this, - "Loaded " + posts.size() + " posts", - Toast.LENGTH_SHORT) - .show(); - } - - private Observable sanitizeData(RedditData redditData) { - return Observable.fromIterable(redditData.data().children()) - .map(Children::data); - } - - @Override - protected void onResume() { - super.onResume(); - initStore(); - loadPosts(); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java deleted file mode 100644 index 4edcb6ff4..000000000 --- a/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.nytimes.android.sample.activity; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.widget.Toast; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.SampleApp; -import com.nytimes.android.sample.data.model.Children; -import com.nytimes.android.sample.data.model.Post; -import com.nytimes.android.sample.data.model.RedditData; -import com.nytimes.android.sample.reddit.PostAdapter; - -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.ObservableSource; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.annotations.NonNull; -import io.reactivex.functions.Function; -import io.reactivex.schedulers.Schedulers; - -import static android.widget.Toast.makeText; - - -public class StoreActivity extends AppCompatActivity { - - private RecyclerView recyclerView; - private PostAdapter postAdapter; - private Store nonPersistedStore; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_store); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - postAdapter = new PostAdapter(); - recyclerView = (RecyclerView) findViewById(R.id.postRecyclerView); - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - layoutManager.setOrientation(LinearLayoutManager.VERTICAL); - recyclerView.setLayoutManager(layoutManager); - recyclerView.setAdapter(postAdapter); - } - - private void initStore() { - if (this.nonPersistedStore == null) { - this.nonPersistedStore = ((SampleApp) getApplicationContext()).getNonPersistedStore(); - } - } - - @SuppressWarnings("CheckReturnValue") - public void loadPosts() { - BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); - - this.nonPersistedStore - .get(awwRequest) - .flatMapObservable(new Function>() { - @Override - public ObservableSource apply(@NonNull RedditData redditData) throws Exception { - return sanitizeData(redditData); - } - }) - .toList() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::showPosts, throwable -> { - Log.e(StoreActivity.class.getSimpleName(), throwable.getMessage(), throwable); - }); - } - - private void showPosts(List posts) { - postAdapter.setPosts(posts); - makeText(StoreActivity.this, - "Loaded " + posts.size() + " posts", - Toast.LENGTH_SHORT) - .show(); - } - - private Observable sanitizeData(RedditData redditData) { - return Observable.fromIterable(redditData.data().children()) - .map(Children::data); - } - - @Override - protected void onResume() { - super.onResume(); - initStore(); - loadPosts(); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Children.java b/app/src/main/java/com/nytimes/android/sample/data/model/Children.java deleted file mode 100644 index 74183d952..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Children.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class Children { - public abstract Post data(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Data.java b/app/src/main/java/com/nytimes/android/sample/data/model/Data.java deleted file mode 100644 index 4e0fd2cf1..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Data.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -import java.util.List; - -@Value.Immutable -public abstract class Data { - public abstract List children(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Image.java b/app/src/main/java/com/nytimes/android/sample/data/model/Image.java deleted file mode 100644 index 786406399..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Image.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class Image { - public abstract String url(); - public abstract int height(); - public abstract int width(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Images.java b/app/src/main/java/com/nytimes/android/sample/data/model/Images.java deleted file mode 100644 index 06ab2cbab..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Images.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class Images { - public abstract Image source(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Post.java b/app/src/main/java/com/nytimes/android/sample/data/model/Post.java deleted file mode 100644 index 9d408fca4..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Post.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import android.support.annotation.Nullable; - -import com.google.common.base.Optional; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class Post { - @Nullable - public abstract Preview preview(); - - public abstract String title(); - - public abstract String url(); - - @Nullable - public abstract Integer height(); - - @Nullable - public abstract Integer width(); - - @Value.Derived - public Optional nestedThumbnail() { - if (preview() == null || preview().images() == null || preview().images().get(0).source() == null) - return Optional.absent(); - return Optional.of(preview().images().get(0).source()); - } - -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Preview.java b/app/src/main/java/com/nytimes/android/sample/data/model/Preview.java deleted file mode 100644 index 95c7f952d..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Preview.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -import java.util.List; - -@Value.Immutable -@Value.Style(allParameters = true) -public abstract class Preview { - @Value.Parameter(false) - public abstract List images(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/RedditData.java b/app/src/main/java/com/nytimes/android/sample/data/model/RedditData.java deleted file mode 100644 index 686faf8de..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/RedditData.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RedditData { - public abstract Data data(); - public abstract String kind(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/package-info.java b/app/src/main/java/com/nytimes/android/sample/data/model/package-info.java deleted file mode 100644 index 9417bd0fe..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@Gson.TypeAdapters -package com.nytimes.android.sample.data.model; - -import org.immutables.gson.Gson; diff --git a/app/src/main/java/com/nytimes/android/sample/data/remote/Api.java b/app/src/main/java/com/nytimes/android/sample/data/remote/Api.java deleted file mode 100644 index 65e8d5a83..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/remote/Api.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.nytimes.android.sample.data.remote; - -import com.nytimes.android.sample.data.model.RedditData; - -import io.reactivex.Single; -import okhttp3.ResponseBody; -import retrofit2.http.GET; -import retrofit2.http.Path; -import retrofit2.http.Query; - -public interface Api { - - @GET("r/{subredditName}/new/.json") - Single fetchSubreddit(@Path("subredditName") String subredditName, - @Query("limit") String limit); - - @GET("r/{subredditName}/new/.json") - Single fetchSubredditForPersister(@Path("subredditName") String subredditName, - @Query("limit") String limit); -} diff --git a/app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.java b/app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.java deleted file mode 100644 index 93989228b..000000000 --- a/app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.nytimes.android.sample.reddit; - -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.data.model.Post; - -import java.util.ArrayList; -import java.util.List; - - - -public class PostAdapter extends RecyclerView.Adapter { - - private final List articles = new ArrayList<>(); - - @Override - public PostViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View itemView = LayoutInflater.from( - parent.getContext()).inflate(R.layout.article_item, parent, false); - return new PostViewHolder(itemView); - } - - @Override - public void onBindViewHolder(PostViewHolder holder, int position) { - holder.onBind(articles.get(position)); - } - - @Override - public int getItemCount() { - return articles.size(); - } - - public void setPosts(List articlesToAdd) { - articles.clear(); - articles.addAll(articlesToAdd); - notifyDataSetChanged(); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java b/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java deleted file mode 100644 index 72e3dd025..000000000 --- a/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.nytimes.android.sample.reddit; - -import android.app.Application; -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - - -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.data.model.Image; -import com.nytimes.android.sample.data.model.ImmutableImage; -import com.nytimes.android.sample.data.model.Post; -import com.nytimes.android.sample.util.BitmapTransform; -import com.nytimes.android.sample.util.DeviceUtils; -import com.squareup.picasso.Picasso; - - -public class PostViewHolder extends RecyclerView.ViewHolder { - - private int maxHeight; - private int maxWidth; - private TextView title; - private ImageView thumbnail; - private View topSpacer; - private final DeviceUtils deviceUtils; - - public PostViewHolder(View itemView) { - super(itemView); - deviceUtils = new DeviceUtils((Application) itemView.getContext().getApplicationContext()); - findViews(itemView); - setMaxDimensions(itemView); - } - - public void onBind(Post article) { - title.setText(article.title()); - if (article.nestedThumbnail().isPresent()) { - showImage(article); - } - } - - private void showImage(Post article) { - Image nestedImage = article.nestedThumbnail().get(); - Image image = ImmutableImage - .builder() - .height(nestedImage.height()) - .width(nestedImage.width()) - .url(nestedImage.url()) - .build(); - BitmapTransform bitmapTransform = new BitmapTransform(maxWidth, maxHeight, image); - - int targetWidth = bitmapTransform.targetWidth; - int targetHeight = bitmapTransform.targetHeight; - - setSpacer(targetWidth, targetHeight); - - setupThumbnail(targetWidth, targetHeight); - - Picasso.with(itemView.getContext()) - .load(image.url()) - .transform(bitmapTransform) - .resize(targetWidth, targetHeight) - .centerInside() - .placeholder(R.color.gray80) - .into(thumbnail); - } - - private void setSpacer(int targetWidth, int targetHeight) { - if (targetWidth >= targetHeight) { - topSpacer.setVisibility(View.GONE); - } else { - topSpacer.setVisibility(View.VISIBLE); - } - } - - private void setupThumbnail(int targetWidth, int targetHeight) { - thumbnail.setMaxWidth(targetWidth); - thumbnail.setMaxHeight(targetHeight); - thumbnail.setMinimumWidth(targetWidth); - thumbnail.setMinimumHeight(targetHeight); - thumbnail.requestLayout(); - } - - private void setMaxDimensions(View itemView) { - int screenWidth; - int screenHeight; - screenWidth = deviceUtils.getScreenWidth(); - screenHeight = deviceUtils.getScreenHeight(); - - if (screenWidth > screenHeight) { - screenHeight = deviceUtils.getScreenWidth(); - screenWidth = deviceUtils.getScreenHeight(); - } - - maxHeight = (int) (screenHeight * .55f); - int margin = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.post_horizontal_margin); - maxWidth = screenWidth - (2 * margin); - } - - private void findViews(View itemView) { - title = (TextView) itemView.findViewById(R.id.title); - thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); - topSpacer = itemView.findViewById(R.id.topSpacer); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java b/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java deleted file mode 100644 index 54b878b1b..000000000 --- a/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.nytimes.android.sample.util; - -import android.graphics.Bitmap; - -import com.nytimes.android.sample.data.model.Image; -import com.squareup.picasso.Transformation; - - -public class BitmapTransform implements Transformation -{ - int maxWidth, maxHeight; - Image key; - public int targetWidth; - public int targetHeight; - private final String picassoKey; - - public BitmapTransform(int maxWidth, int maxHeight, Image image) { - this.maxWidth = maxWidth; - this.maxHeight = maxHeight; - this.key = image; - this.picassoKey = key.url() + "_" + targetWidth + ":" + targetHeight; - - double aspectRatio; - if (image.width() >= image.height()) { - targetWidth = maxWidth; - aspectRatio = (double) image.height() / (double) image.width(); - targetHeight = (int) (targetWidth * aspectRatio); - } else { - targetHeight = maxHeight; - aspectRatio = (double) image.width() / (double) image.height(); - targetWidth = (int) (targetHeight * aspectRatio); - } - } - - @Override - public Bitmap transform(Bitmap source) { - Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, - targetHeight, true); - if (result != source) { - source.recycle(); - } - return result; - } - - @Override - public String key() { - return picassoKey; - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/util/DeviceUtils.java b/app/src/main/java/com/nytimes/android/sample/util/DeviceUtils.java deleted file mode 100644 index 7a9424d7c..000000000 --- a/app/src/main/java/com/nytimes/android/sample/util/DeviceUtils.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.nytimes.android.sample.util; - -import android.app.Application; -import android.content.Context; -import android.graphics.Point; -import android.view.Display; -import android.view.WindowManager; - -public class DeviceUtils { - - private WindowManager windowManager; - - public DeviceUtils(Application context) { - windowManager = - (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - } - - public int getScreenWidth() { - return getScreenSize().x; - } - - public int getScreenHeight() { - return getScreenSize().y; - } - - public Point getScreenSize() { - Display display = windowManager.getDefaultDisplay(); - Point result = new Point(); - display.getSize(result); - return result; - } -} diff --git a/app/src/main/res/layout/activity_store.xml b/app/src/main/res/layout/activity_store.xml deleted file mode 100644 index 9454f3d33..000000000 --- a/app/src/main/res/layout/activity_store.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/article_item.xml b/app/src/main/res/layout/article_item.xml deleted file mode 100644 index dffb1d1c5..000000000 --- a/app/src/main/res/layout/article_item.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cde69bccc..000000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c133a0cbd..000000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bfa42f0e7..000000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 324e72cdd..000000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index aee44e138..000000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml deleted file mode 100644 index 1bcf5fba3..000000000 --- a/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - #3F51B5 - #303F9F - #FF4081 - - #222222 - #000000 - #FFFFFF - #ffdfdfdf - - @android:color/transparent - #000 - #1A000000 - #26000000 - #66000000 - #80000000 - #BF000000 - #CC000000 - #CF000000 - #E6000000 - - - #1a1a1a - #E61a1a1a - #222 - #CF222222 - #333 - #505050 - #666 - #808080 - #999 - #b3b3b3 - #ccc - #e3e3e3 - #ebebeb - #f3f3f3 - #f7f7f7 - #fafafa - #a5a5a5 - #f6f6f6 - #cccccc - - #fff - #80ffffff - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml deleted file mode 100644 index 52fdd43c5..000000000 --- a/app/src/main/res/values/dimens.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - 16dp - 16dp - - 24sp - 22sp - 20sp - 18sp - 16sp - 14sp - 16dp - 16dp - 16dp - 15dp - 5dp - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index 1ad8c299e..000000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Sample - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml deleted file mode 100644 index fa3d07cf5..000000000 --- a/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - -