├── .gitmodules
├── BrickKit
├── app
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ ├── ic_launcher-web.png
│ │ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable-anydpi
│ │ │ │ ├── rounded_bg.xml
│ │ │ │ └── ic_arrow_forward.xml
│ │ │ ├── drawable
│ │ │ │ ├── arrow_down_black.xml
│ │ │ │ └── arrow_up_black.xml
│ │ │ └── layout
│ │ │ │ ├── fragment_brick.xml
│ │ │ │ ├── passive_brick.xml
│ │ │ │ ├── vertical_fragment_brick.xml
│ │ │ │ ├── text_brick.xml
│ │ │ │ ├── active_brick.xml
│ │ │ │ ├── text_brick_vm.xml
│ │ │ │ ├── active_brick_placeholder.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── controller_brick_vm.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── wayfair
│ │ │ │ └── brickkitdemo
│ │ │ │ ├── addremove
│ │ │ │ ├── datamodel
│ │ │ │ │ └── ControllerDataModel.kt
│ │ │ │ ├── viewmodel
│ │ │ │ │ └── ControllerViewModel.kt
│ │ │ │ └── AddRemoveBrickFragment.kt
│ │ │ │ ├── simplebrick
│ │ │ │ ├── viewmodel
│ │ │ │ │ └── TextViewModel.kt
│ │ │ │ ├── datamodel
│ │ │ │ │ └── TextDataModel.kt
│ │ │ │ └── SimpleBrickFragment.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── mainfragment
│ │ │ │ ├── bricks
│ │ │ │ │ ├── PassiveBrick.kt
│ │ │ │ │ └── ActiveBrick.kt
│ │ │ │ └── MainActivityFragment.kt
│ │ │ │ ├── fragmentbrick
│ │ │ │ ├── FragmentBrickFragment.kt
│ │ │ │ └── bricks
│ │ │ │ │ └── FragmentBrick.kt
│ │ │ │ ├── BrickFragment.kt
│ │ │ │ ├── bricks
│ │ │ │ └── TextBrick.kt
│ │ │ │ ├── infinitescroll
│ │ │ │ └── InfiniteScrollBrickFragment.kt
│ │ │ │ └── staggeredinfinitescroll
│ │ │ │ └── StaggeredInfiniteScrollBrickFragment.kt
│ │ │ └── AndroidManifest.xml
│ ├── detekt-baseline.xml
│ ├── proguard-rules.pro
│ └── build.gradle
├── bricks
│ ├── .gitignore
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── config.xml
│ │ │ │ │ └── dimens.xml
│ │ │ │ └── values-sw600dp
│ │ │ │ │ └── config.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── wayfair
│ │ │ │ └── brickkit
│ │ │ │ ├── OpenForTesting.kt
│ │ │ │ ├── padding
│ │ │ │ ├── ZeroBrickPadding.kt
│ │ │ │ ├── BrickPadding.kt
│ │ │ │ └── BrickPaddingFactory.kt
│ │ │ │ ├── animator
│ │ │ │ └── AvoidFlickerItemAnimator.kt
│ │ │ │ ├── size
│ │ │ │ ├── FullWidthBrickSize.kt
│ │ │ │ ├── HalfWidthBrickSize.kt
│ │ │ │ ├── ThirdWidthBrickSize.kt
│ │ │ │ ├── QuarterWidthBrickSize.kt
│ │ │ │ ├── FullPhoneHalfTabletBrickSize.kt
│ │ │ │ ├── FullPhoneThirdTabletBrickSize.kt
│ │ │ │ ├── FullPhoneFullHalfTabletBrickSize.kt
│ │ │ │ ├── FullPhoneFullQuarterTabletBrickSize.kt
│ │ │ │ ├── FullPhoneHalfThirdTabletBrickSize.kt
│ │ │ │ ├── HalfPhoneThirdTabletBrickSize.kt
│ │ │ │ ├── HalfPhoneHalfQuarterTabletBrickSize.kt
│ │ │ │ ├── HalfPhoneThirdQuarterTabletBrickSize.kt
│ │ │ │ ├── NumberOfColumnsBrickSize.kt
│ │ │ │ ├── PercentageBrickSize.kt
│ │ │ │ └── BrickSize.kt
│ │ │ │ ├── DataSetChangedListener.kt
│ │ │ │ ├── OnReachedItemAtPosition.kt
│ │ │ │ ├── BrickSpanSizeLookup.kt
│ │ │ │ ├── BrickDiffUtilCallback.kt
│ │ │ │ ├── viewholder
│ │ │ │ ├── BrickViewHolder.kt
│ │ │ │ └── factory
│ │ │ │ │ └── BrickViewHolderFactory.kt
│ │ │ │ ├── SafeAdapterListUpdateCallback.kt
│ │ │ │ ├── brick
│ │ │ │ ├── ViewModel.kt
│ │ │ │ ├── DataModel.kt
│ │ │ │ ├── BaseBrick.kt
│ │ │ │ └── ViewModelBrick.kt
│ │ │ │ ├── BrickRecyclerItemDecoration.kt
│ │ │ │ └── BrickRecyclerAdapter.kt
│ │ └── androidTest
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── wayfair
│ │ │ │ └── brickkit
│ │ │ │ ├── size
│ │ │ │ ├── FullWidthBrickSizeTest.kt
│ │ │ │ ├── HalfWidthBrickSizeTest.kt
│ │ │ │ ├── ThirdWidthBrickSizeTest.kt
│ │ │ │ ├── QuarterWidthBrickSizeTest.kt
│ │ │ │ ├── FullPhoneHalfTabletBrickSizeTest.kt
│ │ │ │ ├── FullPhoneThirdTabletBrickSizeTest.kt
│ │ │ │ ├── HalfPhoneThirdTabletBrickSizeTest.kt
│ │ │ │ ├── FullPhoneHalfThirdTabletBrickSizeTest.kt
│ │ │ │ ├── FullPhoneFullHalfTabletBrickSizeTest.kt
│ │ │ │ ├── FullPhoneFullQuarterTabletBrickSizeTest.kt
│ │ │ │ ├── HalfPhoneHalfQuarterTabletBrickSizeTest.kt
│ │ │ │ ├── HalfPhoneThirdQuarterTabletBrickSizeTest.kt
│ │ │ │ ├── NumberOfColumnsBrickSizeTest.kt
│ │ │ │ ├── PercentageBrickSizeTest.kt
│ │ │ │ ├── BrickSizeTest.kt
│ │ │ │ └── BrickSizeTestKtx.kt
│ │ │ │ ├── animator
│ │ │ │ └── AvoidFlickerItemAnimatorTest.kt
│ │ │ │ ├── padding
│ │ │ │ ├── ZeroBrickPaddingTest.kt
│ │ │ │ ├── BrickPaddingTest.kt
│ │ │ │ └── BrickPaddingFactoryTest.kt
│ │ │ │ ├── brick
│ │ │ │ ├── ViewModelTest.kt
│ │ │ │ ├── DataModelTest.kt
│ │ │ │ ├── BaseBrickTest.kt
│ │ │ │ └── ViewModelBrickTest.kt
│ │ │ │ ├── SafeAdapterListUpdateCallbackTest.kt
│ │ │ │ ├── BrickDiffUtilCallbackTest.kt
│ │ │ │ ├── BrickSpanSizeLookupTest.kt
│ │ │ │ ├── viewholder
│ │ │ │ └── factory
│ │ │ │ │ └── BrickViewHolderFactoryTest.kt
│ │ │ │ └── BrickRecyclerItemDecorationTest.kt
│ │ │ └── res
│ │ │ └── layout
│ │ │ ├── text_brick_vm_placeholder.xml
│ │ │ └── text_brick_vm.xml
│ ├── detekt-baseline.xml
│ └── build.gradle
├── settings.gradle
├── .editorconfig
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── .gitignore
├── config
│ ├── ktlint.gradle
│ ├── detekt.gradle
│ └── quality
│ │ └── checkstyle
│ │ └── checkstyle.xml
├── gradle.properties
├── build.gradle
├── gradlew.bat
├── scripts
│ └── publish-mavencentral.gradle
└── gradlew
├── .gitignore
├── Docs
└── SampleImage
│ ├── BrickKit.png
│ ├── Reverse.png
│ ├── SpanExample.png
│ ├── StickyFooter.png
│ ├── StickyHeader.png
│ └── SimpleFragment.png
├── CONTRIBUTING.md
├── renovate.json
├── .github
└── workflows
│ ├── codecov_baseline.yml
│ ├── ci.yml
│ └── publish.yml
├── README.md
├── CHANGELOG.md
└── LICENSE
/.gitmodules:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/BrickKit/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/BrickKit/bricks/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/BrickKit/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':bricks'
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | .idea/
3 | BrickKit/bricks/src/main/gen/
--------------------------------------------------------------------------------
/Docs/SampleImage/BrickKit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/Docs/SampleImage/BrickKit.png
--------------------------------------------------------------------------------
/Docs/SampleImage/Reverse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/Docs/SampleImage/Reverse.png
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Docs/SampleImage/SpanExample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/Docs/SampleImage/SpanExample.png
--------------------------------------------------------------------------------
/Docs/SampleImage/StickyFooter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/Docs/SampleImage/StickyFooter.png
--------------------------------------------------------------------------------
/Docs/SampleImage/StickyHeader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/Docs/SampleImage/StickyHeader.png
--------------------------------------------------------------------------------
/BrickKit/.editorconfig:
--------------------------------------------------------------------------------
1 |
2 | # ktlint configuration
3 | [*.{kt,kts}]
4 | max_line_length=150
5 | disabled_rules=import-ordering
6 |
--------------------------------------------------------------------------------
/Docs/SampleImage/SimpleFragment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/Docs/SampleImage/SimpleFragment.png
--------------------------------------------------------------------------------
/BrickKit/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/BrickKit/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/BrickKit/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/BrickKit/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/BrickKit/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/BrickKit/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/BrickKit/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/BrickKit/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayfair/brickkit-android/HEAD/BrickKit/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/BrickKit/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .idea/
5 | /.idea/workspace.xml
6 | /.idea/libraries
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 |
--------------------------------------------------------------------------------
/BrickKit/app/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/res/values/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4dp
4 | 8dp
5 |
6 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/res/values-sw600dp/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/OpenForTesting.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit
2 |
3 | @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
4 | annotation class OpenForTesting
5 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/padding/ZeroBrickPadding.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.padding
5 |
6 | class ZeroBrickPadding : BrickPadding(0, 0, 0, 0, 0, 0, 0, 0)
7 |
--------------------------------------------------------------------------------
/BrickKit/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/BrickKit/config/ktlint.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'org.jlleitschuh.gradle.ktlint'
2 |
3 | ktlint {
4 | android = true
5 | outputToConsole = true
6 | reporters {
7 | reporter "checkstyle"
8 | reporter "plain_group_by_file"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/drawable-anydpi/rounded_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #3F51B5
5 | #303F9F
6 | #FF4081
7 |
8 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/animator/AvoidFlickerItemAnimator.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit.animator
2 |
3 | import androidx.recyclerview.widget.DefaultItemAnimator
4 |
5 | internal class AvoidFlickerItemAnimator : DefaultItemAnimator() {
6 | init {
7 | changeDuration = 0
8 | supportsChangeAnimations = false
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/FullWidthBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class FullWidthBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | FullWidthBrickSize().verifyGetSpans(240, 240, 240, 240)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/HalfWidthBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class HalfWidthBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | HalfWidthBrickSize().verifyGetSpans(120, 120, 120, 120)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/ThirdWidthBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class ThirdWidthBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | ThirdWidthBrickSize().verifyGetSpans(80, 80, 80, 80)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/QuarterWidthBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class QuarterWidthBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | QuarterWidthBrickSize().verifyGetSpans(60, 60, 60, 60)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/FullPhoneHalfTabletBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class FullPhoneHalfTabletBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | FullPhoneHalfTabletBrickSize().verifyGetSpans(240, 240, 120, 120)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/FullPhoneThirdTabletBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class FullPhoneThirdTabletBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | FullPhoneThirdTabletBrickSize().verifyGetSpans(240, 240, 80, 80)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/HalfPhoneThirdTabletBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class HalfPhoneThirdTabletBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | HalfPhoneThirdTabletBrickSize().verifyGetSpans(120, 120, 80, 80)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We welcome your contributions!
4 |
5 | ## TL;DR
6 |
7 | 1. Fork it
8 | 2. Create your feature branch (`git checkout -b my-new-feature`)
9 | 3. Commit your changes (`git commit -am 'Add some feature'`)
10 | 4. Push to the branch (`git push origin my-new-feature`)
11 | 5. Create a new Pull Request
12 |
13 | ## Guidelines
14 |
15 | ### Pre-commit
16 |
17 | - Create an issue
18 | - Tests
19 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/FullPhoneHalfThirdTabletBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class FullPhoneHalfThirdTabletBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | FullPhoneHalfThirdTabletBrickSize().verifyGetSpans(240, 240, 120, 80)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/FullWidthBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class FullWidthBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT,
10 | BrickDataManager.SPAN_COUNT,
11 | BrickDataManager.SPAN_COUNT,
12 | BrickDataManager.SPAN_COUNT
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/FullPhoneFullHalfTabletBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class FullPhoneFullHalfTabletBrickSizeTest {
9 |
10 | @Test
11 | fun testGetSpans() {
12 | FullPhoneFullHalfTabletBrickSize().verifyGetSpans(240, 240, 240, 120)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/FullPhoneFullQuarterTabletBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class FullPhoneFullQuarterTabletBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | FullPhoneFullQuarterTabletBrickSize().verifyGetSpans(240, 240, 240, 60)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/HalfPhoneHalfQuarterTabletBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class HalfPhoneHalfQuarterTabletBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | HalfPhoneHalfQuarterTabletBrickSize().verifyGetSpans(120, 120, 120, 60)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/DataSetChangedListener.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit
5 |
6 | /**
7 | * Listener which can be used to listen for data set changes.
8 | */
9 | interface DataSetChangedListener {
10 | /**
11 | * Callback that will be triggered when data set changes occur.
12 | */
13 | fun onDataSetChanged()
14 | }
15 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/HalfPhoneThirdQuarterTabletBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class HalfPhoneThirdQuarterTabletBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | HalfPhoneThirdQuarterTabletBrickSize().verifyGetSpans(120, 120, 80, 60)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/HalfWidthBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class HalfWidthBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT / 2,
10 | BrickDataManager.SPAN_COUNT / 2,
11 | BrickDataManager.SPAN_COUNT / 2,
12 | BrickDataManager.SPAN_COUNT / 2
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/ThirdWidthBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class ThirdWidthBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT / 3,
10 | BrickDataManager.SPAN_COUNT / 3,
11 | BrickDataManager.SPAN_COUNT / 3,
12 | BrickDataManager.SPAN_COUNT / 3
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/QuarterWidthBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class QuarterWidthBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT / 4,
10 | BrickDataManager.SPAN_COUNT / 4,
11 | BrickDataManager.SPAN_COUNT / 4,
12 | BrickDataManager.SPAN_COUNT / 4
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/FullPhoneHalfTabletBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class FullPhoneHalfTabletBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT,
10 | BrickDataManager.SPAN_COUNT,
11 | BrickDataManager.SPAN_COUNT / 2,
12 | BrickDataManager.SPAN_COUNT / 2
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/FullPhoneThirdTabletBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class FullPhoneThirdTabletBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT,
10 | BrickDataManager.SPAN_COUNT,
11 | BrickDataManager.SPAN_COUNT / 3,
12 | BrickDataManager.SPAN_COUNT / 3
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/FullPhoneFullHalfTabletBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class FullPhoneFullHalfTabletBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT,
10 | BrickDataManager.SPAN_COUNT,
11 | BrickDataManager.SPAN_COUNT,
12 | BrickDataManager.SPAN_COUNT / 2
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/FullPhoneFullQuarterTabletBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class FullPhoneFullQuarterTabletBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT,
10 | BrickDataManager.SPAN_COUNT,
11 | BrickDataManager.SPAN_COUNT,
12 | BrickDataManager.SPAN_COUNT / 4
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/FullPhoneHalfThirdTabletBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class FullPhoneHalfThirdTabletBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT,
10 | BrickDataManager.SPAN_COUNT,
11 | BrickDataManager.SPAN_COUNT / 2,
12 | BrickDataManager.SPAN_COUNT / 3
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/HalfPhoneThirdTabletBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class HalfPhoneThirdTabletBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT / 2,
10 | BrickDataManager.SPAN_COUNT / 2,
11 | BrickDataManager.SPAN_COUNT / 3,
12 | BrickDataManager.SPAN_COUNT / 3
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/drawable/arrow_down_black.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/drawable/arrow_up_black.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/BrickKit/bricks/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LongParameterList:BrickPadding.kt$BrickPadding$( val innerLeftPadding: Int, val innerTopPadding: Int, val innerRightPadding: Int, val innerBottomPadding: Int, val outerLeftPadding: Int, val outerTopPadding: Int, val outerRightPadding: Int, val outerBottomPadding: Int )
6 |
7 |
8 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/NumberOfColumnsBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class NumberOfColumnsBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | NumberOfColumnsBrickSize(6).verifyGetSpans(40, 40, 40, 40)
12 | NumberOfColumnsBrickSize(2).verifyGetSpans(120, 120, 120, 120)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/HalfPhoneHalfQuarterTabletBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class HalfPhoneHalfQuarterTabletBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT / 2,
10 | BrickDataManager.SPAN_COUNT / 2,
11 | BrickDataManager.SPAN_COUNT / 2,
12 | BrickDataManager.SPAN_COUNT / 4
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/HalfPhoneThirdQuarterTabletBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class HalfPhoneThirdQuarterTabletBrickSize : BrickSize(
9 | BrickDataManager.SPAN_COUNT / 2,
10 | BrickDataManager.SPAN_COUNT / 2,
11 | BrickDataManager.SPAN_COUNT / 3,
12 | BrickDataManager.SPAN_COUNT / 4
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/OnReachedItemAtPosition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit
5 |
6 | /**
7 | * Callback interface used to notify when binding items.
8 | */
9 | interface OnReachedItemAtPosition {
10 | /**
11 | * Callback when binding items.
12 | *
13 | * @param position position of item that was bound
14 | */
15 | fun bindingItemAtPosition(position: Int)
16 | }
17 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/drawable-anydpi/ic_arrow_forward.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/layout/fragment_brick.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/animator/AvoidFlickerItemAnimatorTest.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit.animator
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Assert.assertFalse
5 | import org.junit.Test
6 |
7 | class AvoidFlickerItemAnimatorTest {
8 | @Test
9 | fun testConstructor() {
10 | AvoidFlickerItemAnimator().apply {
11 | assertFalse(supportsChangeAnimations)
12 | assertEquals(0, changeDuration)
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/layout/passive_brick.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/PercentageBrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import org.junit.Test
7 |
8 | class PercentageBrickSizeTest {
9 | @Test
10 | fun testGetSpans() {
11 | PercentageBrickSize(.2f).verifyGetSpans(48, 48, 48, 48)
12 | PercentageBrickSize(.4f).verifyGetSpans(96, 96, 96, 96)
13 | PercentageBrickSize(.5f).verifyGetSpans(120, 120, 120, 120)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/NumberOfColumnsBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class NumberOfColumnsBrickSize(numberOfColumns: Int) : BrickSize(
9 | BrickDataManager.SPAN_COUNT / numberOfColumns,
10 | BrickDataManager.SPAN_COUNT / numberOfColumns,
11 | BrickDataManager.SPAN_COUNT / numberOfColumns,
12 | BrickDataManager.SPAN_COUNT / numberOfColumns
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/PercentageBrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 |
8 | class PercentageBrickSize(percentage: Float) : BrickSize(
9 | (BrickDataManager.SPAN_COUNT * percentage).toInt(),
10 | (BrickDataManager.SPAN_COUNT * percentage).toInt(),
11 | (BrickDataManager.SPAN_COUNT * percentage).toInt(),
12 | (BrickDataManager.SPAN_COUNT * percentage).toInt()
13 | )
14 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/addremove/datamodel/ControllerDataModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.addremove.datamodel
5 |
6 | import com.wayfair.brickkit.brick.DataModel
7 |
8 | /**
9 | * Data model for a controller brick.
10 | */
11 | class ControllerDataModel(initialValue: Int) : DataModel() {
12 |
13 | var value: Int = initialValue
14 | set(value) {
15 | field = value
16 | notifyChange()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/simplebrick/viewmodel/TextViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkitdemo.simplebrick.viewmodel
2 |
3 | import androidx.databinding.Bindable
4 | import com.wayfair.brickkit.brick.ViewModel
5 | import com.wayfair.brickkitdemo.simplebrick.datamodel.TextDataModel
6 |
7 | /**
8 | * A view model for a text brick.
9 | */
10 | class TextViewModel(dataModel: TextDataModel) : ViewModel(dataModel) {
11 | /**
12 | * Method that data binding uses to set the view's text.
13 | *
14 | * @return the text
15 | */
16 | @get:Bindable
17 | val text: String
18 | get() = dataModel.text
19 | }
20 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 0dp
9 |
11 | 8dp
12 |
13 | 16dp
14 |
15 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/layout/vertical_fragment_brick.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | BrickKit
4 | -
5 | +
6 | Simple Brick View
7 | Add/Remove Brick View
8 | Infinite Scroll Brick View
9 | Staggered Infinite Scroll Brick View
10 | Fragment Brick View
11 |
12 |
13 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/simplebrick/datamodel/TextDataModel.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkitdemo.simplebrick.datamodel
2 |
3 | import com.wayfair.brickkit.brick.DataModel
4 |
5 | /**
6 | * Data model for text bricks.
7 | */
8 | class TextDataModel(initialText: String) : DataModel() {
9 |
10 | var text: String = initialText
11 | private set(value) {
12 | field = value
13 | notifyChange()
14 | }
15 |
16 | /**
17 | * Append the string to the existing text.
18 | *
19 | * @param newText the new string to be appended
20 | */
21 | fun appendText(newText: String) {
22 | text += newText
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/BrickKit/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/mdemaso/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "description": "Renovate configuration recommended by the Wayfair OSPO",
4 | "labels": [
5 | "renovate/{{depName}}"
6 | ],
7 | "extends": [
8 | "config:base",
9 | ":dependencyDashboard",
10 | ":rebaseStalePrs"
11 | ],
12 | "schedule": [
13 | "before 3am every weekday"
14 | ],
15 | "enabledManagers": [
16 | "github-actions"
17 | ],
18 | "packageRules": [
19 | {
20 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
21 | "groupName": "Minor Updates",
22 | "automerge": false
23 | },
24 | {
25 | "matchManagers": ["github-actions"],
26 | "groupName": "GitHub Actions"
27 | }
28 | ]
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/BrickSpanSizeLookup.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit
5 |
6 | import android.content.Context
7 | import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
8 |
9 | /**
10 | * [androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup] which grabs the span size
11 | * from the brick at the given position.
12 | */
13 | internal class BrickSpanSizeLookup(
14 | private val context: Context,
15 | private val brickDataManager: BrickDataManager
16 | ) : SpanSizeLookup() {
17 |
18 | override fun getSpanSize(position: Int): Int =
19 | brickDataManager.recyclerViewItems.getOrNull(position)?.spanSize?.getSpans(context) ?: 1
20 | }
21 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/res/layout/text_brick_vm_placeholder.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.github/workflows/codecov_baseline.yml:
--------------------------------------------------------------------------------
1 | name: CodeCov Baseline Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'dev'
7 | jobs:
8 | codecov-baseline-publish:
9 | name: Publish codecov baseline for dev
10 | runs-on: macos-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v3
14 |
15 | - name: set up JDK 11
16 | uses: actions/setup-java@v3
17 | with:
18 | java-version: 11.0.1+13
19 | distribution: 'zulu'
20 | cache: gradle
21 |
22 | - name: Run Tests
23 | uses: reactivecircus/android-emulator-runner@v2
24 | with:
25 | api-level: 29
26 | script: cd BrickKit && ./gradlew connectedCheck
27 |
28 | - name: Upload to Codecov
29 | run: bash <(curl -s https://codecov.io/bash)
30 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/padding/ZeroBrickPaddingTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.padding
5 |
6 | import org.junit.Assert.assertEquals
7 | import org.junit.Test
8 |
9 | class ZeroBrickPaddingTest {
10 | @Test
11 | fun testZeroBrickPadding() {
12 | ZeroBrickPadding().apply {
13 | assertEquals(0, innerLeftPadding)
14 | assertEquals(0, innerTopPadding)
15 | assertEquals(0, innerRightPadding)
16 | assertEquals(0, innerBottomPadding)
17 | assertEquals(0, outerLeftPadding)
18 | assertEquals(0, outerTopPadding)
19 | assertEquals(0, outerRightPadding)
20 | assertEquals(0, outerBottomPadding)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/BrickSizeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import com.wayfair.brickkit.BrickDataManager
7 | import org.junit.Test
8 |
9 | class BrickSizeTest {
10 | @Test
11 | fun testGetSpans() {
12 | val brickSize = object : BrickSize(PORTRAIT_PHONE, LANDSCAPE_PHONE, PORTRAIT_TABLET, LANDSCAPE_TABLET) {}
13 |
14 | brickSize.verifyGetSpans(BrickDataManager.SPAN_COUNT, LANDSCAPE_PHONE, PORTRAIT_TABLET, LANDSCAPE_TABLET)
15 | }
16 |
17 | companion object {
18 | private const val LANDSCAPE_TABLET = 60
19 | private const val PORTRAIT_TABLET = 120
20 | private const val LANDSCAPE_PHONE = 180
21 | private const val PORTRAIT_PHONE = 250
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo
5 |
6 | import android.os.Bundle
7 | import androidx.appcompat.app.AppCompatActivity
8 | import com.wayfair.brickkitdemo.mainfragment.MainActivityFragment
9 |
10 | /**
11 | * Main activity for the app.
12 | */
13 | class MainActivity : AppCompatActivity() {
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setContentView(R.layout.activity_main)
18 | setSupportActionBar(findViewById(R.id.toolbar))
19 |
20 | if (savedInstanceState == null) {
21 | supportFragmentManager.beginTransaction().replace(R.id.content, MainActivityFragment()).commit()
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/BrickKit/config/detekt.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | maven {
4 | url "https://plugins.gradle.org/m2/"
5 | }
6 | }
7 |
8 | dependencies {
9 | classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.15.0"
10 | }
11 | }
12 |
13 | apply plugin: 'io.gitlab.arturbosch.detekt'
14 |
15 | detekt {
16 | toolVersion = "1.15.0"
17 |
18 | config = files(configFile('quality/detekt/detekt-config.yml'))
19 | baseline = file('detekt-baseline.xml')
20 |
21 | reports {
22 | xml {
23 | enabled = true
24 | destination = file("build/reports/detekt/detekt.xml")
25 | }
26 | html {
27 | enabled = true
28 | destination = file("build/reports/detekt/detekt.html")
29 | }
30 | txt.enabled = false
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/layout/text_brick.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/BrickKit/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | android.useAndroidX=true
20 | android.enableJetifier=true
21 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/padding/BrickPadding.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.padding
5 |
6 | import com.wayfair.brickkit.OpenForTesting
7 |
8 | /**
9 | * This class defines an abstract implementation of brick padding.
10 | *
11 | * It is used to determine the amount of the padding to apply to this brick based off of the location in the screen.
12 | * Different padding is allowed on sides that are on the outside edge of the screen or any inner edges.
13 | */
14 | @OpenForTesting
15 | open class BrickPadding internal constructor(
16 | val innerLeftPadding: Int,
17 | val innerTopPadding: Int,
18 | val innerRightPadding: Int,
19 | val innerBottomPadding: Int,
20 | val outerLeftPadding: Int,
21 | val outerTopPadding: Int,
22 | val outerRightPadding: Int,
23 | val outerBottomPadding: Int
24 | )
25 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/BrickDiffUtilCallback.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit
5 |
6 | import androidx.recyclerview.widget.DiffUtil
7 | import com.wayfair.brickkit.brick.BaseBrick
8 |
9 | /**
10 | * A DiffUtil for Bricks.
11 | */
12 | internal class BrickDiffUtilCallback internal constructor(
13 | private val oldList: List,
14 | private val newList: List
15 | ) : DiffUtil.Callback() {
16 |
17 | override fun getOldListSize(): Int = oldList.size
18 |
19 | override fun getNewListSize(): Int = newList.size
20 |
21 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
22 | oldList[oldItemPosition].layout == newList[newItemPosition].layout
23 |
24 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
25 | oldList[oldItemPosition] == newList[newItemPosition]
26 | }
27 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/viewholder/BrickViewHolder.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.viewholder
5 |
6 | import android.view.View
7 | import androidx.recyclerview.widget.RecyclerView
8 |
9 | /**
10 | * Base ViewHolder for bricks. It extends [androidx.recyclerview.widget.RecyclerView.ViewHolder] with
11 | * one additional method that is called when the view holder is detached from the [RecyclerView] so
12 | * that views can be released.
13 | */
14 | open class BrickViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
15 | /**
16 | * Called when [RecyclerView.Adapter.onViewAttachedToWindow] is invoked.
17 | */
18 | open fun onViewAttachedToWindow() = Unit
19 |
20 | /**
21 | * Method called when the view is detached from the [RecyclerView]. All views that have resources
22 | * should release them here (e.g. ImageView).
23 | */
24 | open fun releaseViewsOnDetach() = Unit
25 | }
26 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/addremove/viewmodel/ControllerViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.addremove.viewmodel
5 |
6 | import android.view.View
7 | import androidx.databinding.Bindable
8 | import com.wayfair.brickkit.brick.ViewModel
9 | import com.wayfair.brickkitdemo.addremove.datamodel.ControllerDataModel
10 |
11 | /**
12 | * A sample brick.
13 | */
14 | class ControllerViewModel(
15 | dataModel: ControllerDataModel,
16 | private val interactions: Interactions
17 | ) : ViewModel(dataModel) {
18 |
19 | @get:Bindable
20 | val value: String
21 | get() = dataModel.value.toString()
22 |
23 | val addClickListener = View.OnClickListener { interactions.onAddClicked() }
24 | val removeClickListener = View.OnClickListener { interactions.onRemoveClicked() }
25 |
26 | interface Interactions {
27 | fun onAddClicked()
28 | fun onRemoveClicked()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/layout/active_brick.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/mainfragment/bricks/PassiveBrick.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.mainfragment.bricks
5 |
6 | import android.content.res.Resources
7 | import android.view.View
8 | import com.wayfair.brickkit.brick.BaseBrick
9 | import com.wayfair.brickkit.padding.BrickPaddingFactory
10 | import com.wayfair.brickkit.size.BrickSize
11 | import com.wayfair.brickkit.viewholder.BrickViewHolder
12 | import com.wayfair.brickkitdemo.R
13 |
14 | /**
15 | * Passive brick used [com.wayfair.brickkitdemo.mainfragment.MainActivityFragment] to fill screen.
16 | */
17 | class PassiveBrick(
18 | spanSize: BrickSize,
19 | resources: Resources
20 | ) : BaseBrick(spanSize, BrickPaddingFactory(resources).getInnerOuterBrickPadding(R.dimen.four_dp, R.dimen.no_dp)) {
21 |
22 | override fun onBindData(holder: BrickViewHolder) = Unit
23 |
24 | override val layout = R.layout.passive_brick
25 |
26 | override fun createViewHolder(itemView: View): BrickViewHolder = BrickViewHolder(itemView)
27 | }
28 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/SafeAdapterListUpdateCallback.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit
2 |
3 | import androidx.recyclerview.widget.ListUpdateCallback
4 |
5 | /**
6 | * A safe implementation of [androidx.recyclerview.widget.ListUpdateCallback],
7 | * which allows updates to the adapter during scrolling by mapping the calls
8 | * to safe versions.
9 | */
10 | internal class SafeAdapterListUpdateCallback(
11 | private val adapter: BrickRecyclerAdapter
12 | ) : ListUpdateCallback {
13 |
14 | override fun onInserted(position: Int, count: Int) {
15 | adapter.safeNotifyItemRangeInserted(position, count)
16 | }
17 |
18 | override fun onRemoved(position: Int, count: Int) {
19 | adapter.safeNotifyItemRangeRemoved(position, count)
20 | }
21 |
22 | override fun onMoved(fromPosition: Int, toPosition: Int) {
23 | adapter.safeNotifyItemMoved(fromPosition, toPosition)
24 | }
25 |
26 | override fun onChanged(position: Int, count: Int, payload: Any?) {
27 | adapter.safeNotifyItemRangeChanged(position, count, payload)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/fragmentbrick/FragmentBrickFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.fragmentbrick
5 |
6 | import android.os.Bundle
7 | import com.wayfair.brickkitdemo.BrickFragment
8 | import com.wayfair.brickkitdemo.R
9 | import com.wayfair.brickkitdemo.fragmentbrick.bricks.FragmentBrick
10 | import com.wayfair.brickkitdemo.simplebrick.SimpleBrickFragment
11 |
12 | /**
13 | * Example of fragment containing [FragmentBrick]'s containing [SimpleBrickFragment]'s.
14 | */
15 | class FragmentBrickFragment : BrickFragment() {
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 |
19 | FragmentBrick(childFragmentManager, SimpleBrickFragment.newInstance(1), R.color.colorAccent).addLastTo(dataManager)
20 | FragmentBrick(childFragmentManager, SimpleBrickFragment.newInstance(2), R.color.colorPrimary).addLastTo(dataManager)
21 | FragmentBrick(childFragmentManager, SimpleBrickFragment.newInstance(3), R.color.colorPrimaryDark).addLastTo(dataManager)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/layout/text_brick_vm.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/res/layout/text_brick_vm.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
10 |
11 |
12 |
15 |
16 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/layout/active_brick_placeholder.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/BrickFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo
5 |
6 | import android.os.Bundle
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.annotation.CallSuper
11 | import androidx.fragment.app.Fragment
12 | import androidx.recyclerview.widget.DefaultItemAnimator
13 | import androidx.recyclerview.widget.RecyclerView
14 | import com.wayfair.brickkit.BrickDataManager
15 |
16 | /**
17 | * Fragment which provides a simple interface for adding bricks / behaviors.
18 | */
19 | open class BrickFragment : Fragment() {
20 | protected val dataManager = BrickDataManager()
21 |
22 | @CallSuper
23 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
24 | inflater.inflate(R.layout.vertical_fragment_brick, container, false).apply {
25 | findViewById(R.id.recycler_view).apply {
26 | (itemAnimator as DefaultItemAnimator?)?.supportsChangeAnimations = false
27 | dataManager.setRecyclerView(this)
28 | }
29 | }
30 |
31 | override fun onDestroyView() {
32 | dataManager.onDestroyView()
33 | super.onDestroyView()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/BrickKit/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | configFile = { name -> file("config/$name") }
4 | }
5 |
6 | repositories {
7 | google()
8 | gradlePluginPortal()
9 | mavenCentral()
10 | maven {
11 | url "https://plugins.gradle.org/m2/"
12 | }
13 | }
14 | dependencies {
15 | classpath 'com.android.tools.build:gradle:7.3.1'
16 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20'
17 | classpath 'org.jetbrains.kotlin:kotlin-allopen:1.7.20'
18 |
19 | classpath 'io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.19.0'
20 | classpath 'org.jlleitschuh.gradle:ktlint-gradle:10.3.0'
21 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
22 | classpath 'io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.22.0'
23 | }
24 | }
25 |
26 | apply plugin: 'io.codearte.nexus-staging'
27 |
28 | allprojects {
29 | repositories {
30 | mavenCentral()
31 | google()
32 | maven {
33 | url 'https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven'
34 | }
35 | }
36 |
37 | }
38 |
39 | subprojects {
40 | apply from: configFile('detekt.gradle')
41 | apply from: configFile('ktlint.gradle')
42 | }
43 |
44 | task clean(type: Delete) {
45 | delete rootProject.buildDir
46 | }
47 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/brick/ViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit.brick
2 |
3 | import org.mockito.kotlin.mock
4 | import org.mockito.kotlin.verify
5 | import org.mockito.kotlin.whenever
6 | import org.junit.Assert.assertFalse
7 | import org.junit.Assert.assertTrue
8 | import org.junit.Before
9 | import org.junit.Test
10 |
11 | class ViewModelTest {
12 | private val dataModel: DataModel = mock()
13 | private lateinit var viewModel: ViewModel
14 |
15 | @Before
16 | fun setup() {
17 | viewModel = object : ViewModel(dataModel) { }
18 |
19 | verify(dataModel).addUpdateListener(viewModel)
20 | }
21 |
22 | @Test
23 | fun testNotifyChange() {
24 | val updateListener = mock()
25 |
26 | viewModel.addUpdateListener(updateListener)
27 |
28 | viewModel.notifyChange()
29 |
30 | verify(updateListener).onChange()
31 | }
32 |
33 | @Test
34 | fun testIsDataModelReady_dataModelReady() {
35 | whenever(dataModel.isReady).thenReturn(true)
36 |
37 | assertTrue(viewModel.isDataModelReady)
38 | }
39 |
40 | @Test
41 | fun testIsDataModelReady_dataModelNotReady() {
42 | whenever(dataModel.isReady).thenReturn(false)
43 |
44 | assertFalse(viewModel.isDataModelReady)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/bricks/TextBrick.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.bricks
5 |
6 | import android.view.View
7 | import android.widget.TextView
8 | import com.wayfair.brickkit.brick.BaseBrick
9 | import com.wayfair.brickkit.padding.BrickPaddingFactory
10 | import com.wayfair.brickkit.size.BrickSize
11 | import com.wayfair.brickkit.viewholder.BrickViewHolder
12 | import com.wayfair.brickkitdemo.R
13 |
14 | /**
15 | * Simple Brick with a single text view.
16 | */
17 | class TextBrick(
18 | spanSize: BrickSize,
19 | brickPaddingFactory: BrickPaddingFactory,
20 | private val text: CharSequence
21 | ) : BaseBrick(spanSize, brickPaddingFactory.getInnerOuterBrickPadding(R.dimen.four_dp, R.dimen.eight_dp)) {
22 |
23 | override fun onBindData(holder: BrickViewHolder) {
24 | val viewHolder = holder as TextViewHolder
25 | viewHolder.textView.text = text
26 | }
27 |
28 | override val layout = R.layout.text_brick
29 |
30 | override fun createViewHolder(itemView: View): BrickViewHolder = TextViewHolder(itemView)
31 |
32 | /**
33 | * [BrickViewHolder] for TextBrick.
34 | */
35 | private class TextViewHolder(itemView: View) : BrickViewHolder(itemView) {
36 | var textView: TextView = itemView.findViewById(R.id.text_view)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/brick/DataModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit.brick
2 |
3 | import org.junit.Assert.assertTrue
4 | import org.junit.Assert.fail
5 | import org.junit.Test
6 | import java.util.concurrent.CountDownLatch
7 | import java.util.concurrent.TimeUnit
8 |
9 | class DataModelTest {
10 |
11 | private val dataModel: DataModel = object : DataModel() { }
12 |
13 | @Test
14 | fun testIsReady() {
15 | assertTrue(dataModel.isReady)
16 | }
17 |
18 | @Test
19 | fun testNotifyChange() {
20 | val countDownLatch = CountDownLatch(2)
21 | val listener1 = object : DataModel.DataModelUpdateListener {
22 | override fun notifyChange() {
23 | countDownLatch.countDown()
24 | }
25 | }
26 |
27 | val listener2 = object : DataModel.DataModelUpdateListener {
28 | override fun notifyChange() {
29 | countDownLatch.countDown()
30 | }
31 | }
32 |
33 | val removedListener = object : DataModel.DataModelUpdateListener {
34 | override fun notifyChange() {
35 | fail()
36 | }
37 | }
38 |
39 | dataModel.addUpdateListener(listener1)
40 | dataModel.addUpdateListener(listener2)
41 | dataModel.addUpdateListener(removedListener)
42 | dataModel.removeUpdateListener(removedListener)
43 |
44 | dataModel.notifyChange()
45 |
46 | countDownLatch.await(5, TimeUnit.SECONDS)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/BrickKit/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-kapt'
4 |
5 | android {
6 | compileSdkVersion 33
7 |
8 | defaultConfig {
9 | applicationId 'com.wayfair.brickkitdemo'
10 | minSdkVersion 23
11 | targetSdkVersion 33
12 | versionCode 1
13 | versionName "1.0"
14 | vectorDrawables.useSupportLibrary = true
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | buildFeatures.dataBinding = true
24 |
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_11
27 | targetCompatibility JavaVersion.VERSION_11
28 | }
29 |
30 | kotlinOptions {
31 | jvmTarget = 11
32 | }
33 |
34 | namespace 'com.wayfair.brickkitdemo'
35 |
36 | lint {
37 | abortOnError true
38 | disable 'GradleDependency', 'OldTargetApi'
39 | warningsAsErrors true
40 | }
41 | }
42 |
43 | dependencies {
44 | implementation 'androidx.core:core-ktx:1.9.0'
45 | implementation 'androidx.fragment:fragment-ktx:1.5.4'
46 | implementation 'androidx.appcompat:appcompat:1.5.1'
47 | implementation 'androidx.recyclerview:recyclerview:1.2.1'
48 | implementation 'com.google.android.material:material:1.7.0'
49 | implementation 'com.facebook.shimmer:shimmer:0.5.0@aar'
50 | implementation project(path: ':bricks')
51 | }
52 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: CI
4 |
5 | on:
6 | pull_request:
7 | branches:
8 | - '*'
9 | jobs:
10 | renovate:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - name: 🧼 lint renovate config # Validates changes to renovate.json config file
15 | uses: suzuki-shunsuke/github-action-renovate-config-validator@v0.1.2
16 | with:
17 | config_file_path: 'renovate.json'
18 | ci-build:
19 | name: Run Build
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v3
24 |
25 | - name: set up JDK 11
26 | uses: actions/setup-java@v3
27 | with:
28 | java-version: 11.0.1+13
29 | distribution: 'zulu'
30 | cache: gradle
31 |
32 | - name: Build
33 | working-directory: ./BrickKit
34 | run: ./gradlew build --no-daemon
35 | ci-test:
36 | name: Run UI Tests
37 | runs-on: macos-latest
38 | steps:
39 | - name: Checkout
40 | uses: actions/checkout@v3
41 |
42 | - name: set up JDK 11
43 | uses: actions/setup-java@v3
44 | with:
45 | java-version: 11.0.1+13
46 | distribution: 'zulu'
47 | cache: gradle
48 |
49 | - name: Run Tests
50 | uses: reactivecircus/android-emulator-runner@v2
51 | with:
52 | api-level: 29
53 | script: cd BrickKit && ./gradlew connectedCheck
54 |
55 | - name: Upload to Codecov
56 | run: bash <(curl -s https://codecov.io/bash)
57 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/size/BrickSizeTestKtx.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import android.content.Context
7 | import android.content.res.Configuration
8 | import android.content.res.Resources
9 | import org.mockito.kotlin.mock
10 | import org.mockito.kotlin.whenever
11 | import com.wayfair.brickkit.R
12 | import org.junit.Assert
13 |
14 | fun BrickSize.verifyGetSpans(portraitPhone: Int, landscapePhone: Int, portraitTablet: Int, landscapeTablet: Int) {
15 | val context = mock()
16 | val resources = mock()
17 | val configuration = Configuration()
18 |
19 | whenever(resources.configuration).thenReturn(configuration)
20 | whenever(context.resources).thenReturn(resources)
21 |
22 | whenever(resources.getBoolean(R.bool.tablet)).thenReturn(false)
23 | configuration.orientation = Configuration.ORIENTATION_PORTRAIT
24 | Assert.assertEquals(portraitPhone, getSpans(context))
25 |
26 | whenever(resources.getBoolean(R.bool.tablet)).thenReturn(false)
27 | configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
28 | Assert.assertEquals(landscapePhone, getSpans(context))
29 |
30 | whenever(resources.getBoolean(R.bool.tablet)).thenReturn(true)
31 | configuration.orientation = Configuration.ORIENTATION_PORTRAIT
32 | Assert.assertEquals(portraitTablet, getSpans(context))
33 |
34 | whenever(resources.getBoolean(R.bool.tablet)).thenReturn(true)
35 | configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
36 | Assert.assertEquals(landscapeTablet, getSpans(context))
37 | }
38 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/ViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit.brick
2 |
3 | import androidx.databinding.BaseObservable
4 | import com.wayfair.brickkit.brick.DataModel.DataModelUpdateListener
5 | import java.io.Serializable
6 |
7 | /**
8 | * The object used to bind information in a [ViewModelBrick].
9 | */
10 | abstract class ViewModel protected constructor(
11 | open val dataModel: DM
12 | ) : BaseObservable(), DataModelUpdateListener, Serializable {
13 |
14 | @Transient
15 | private var updateListeners = mutableSetOf()
16 |
17 | init {
18 | dataModel.addUpdateListener(this)
19 | }
20 |
21 | /**
22 | * Add an [ViewModelUpdateListener] to the list of items watching for changes.
23 | *
24 | * @param updateListener the object that is watching
25 | */
26 | open fun addUpdateListener(updateListener: ViewModelUpdateListener) {
27 | updateListeners.add(updateListener)
28 | }
29 |
30 | override fun notifyChange() {
31 | super.notifyChange()
32 | updateListeners.forEach { updateListener -> updateListener.onChange() }
33 | }
34 |
35 | /**
36 | * Determines if the [DataModel] is ready, meaning the ViewModel is ready.
37 | *
38 | * @return if the data model is ready, IE fully populated
39 | */
40 | open val isDataModelReady: Boolean
41 | get() = dataModel.isReady
42 |
43 | /**
44 | * Interface for listening to changes.
45 | */
46 | interface ViewModelUpdateListener {
47 | /**
48 | * Called when notify change is called.
49 | */
50 | fun onChange()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
15 |
16 |
21 |
22 |
28 |
29 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/SafeAdapterListUpdateCallbackTest.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import org.junit.Before
5 | import org.junit.runner.RunWith
6 | import org.mockito.kotlin.mock
7 | import org.mockito.kotlin.verify
8 | import org.junit.Test
9 |
10 | @RunWith(AndroidJUnit4::class)
11 | class SafeAdapterListUpdateCallbackTest {
12 |
13 | private val brickRecyclerAdapter: BrickRecyclerAdapter = mock()
14 | private lateinit var adapter: SafeAdapterListUpdateCallback
15 |
16 | @Before
17 | fun setup() {
18 | adapter = SafeAdapterListUpdateCallback(brickRecyclerAdapter)
19 | }
20 |
21 | @Test
22 | fun testOnInserted() {
23 | val position = 3
24 | val count = 1
25 | adapter.onInserted(position, count)
26 | verify(brickRecyclerAdapter).safeNotifyItemRangeInserted(position, count)
27 | }
28 |
29 | @Test
30 | fun testOnRemoved() {
31 | val position = 4
32 | val count = 1
33 | adapter.onRemoved(position, count)
34 | verify(brickRecyclerAdapter).safeNotifyItemRangeRemoved(position, count)
35 | }
36 |
37 | @Test
38 | fun testOnMoved() {
39 | val position = 5
40 | val count = 9
41 | adapter.onMoved(position, count)
42 | verify(brickRecyclerAdapter).safeNotifyItemMoved(position, count)
43 | }
44 |
45 | @Test
46 | fun testOnChanged() {
47 | val position = 2
48 | val count = 6
49 | val any: Any = mock()
50 | adapter.onChanged(position, count, any)
51 | verify(brickRecyclerAdapter).safeNotifyItemRangeChanged(position, count, any)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/BrickDiffUtilCallbackTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit
5 |
6 | import com.wayfair.brickkit.brick.BaseBrick
7 | import org.junit.Assert.assertEquals
8 | import org.junit.Assert.assertFalse
9 | import org.junit.Assert.assertTrue
10 | import org.junit.Before
11 | import org.junit.Test
12 | import org.mockito.kotlin.mock
13 | import org.mockito.kotlin.whenever
14 |
15 | class BrickDiffUtilCallbackTest {
16 | private lateinit var callback: BrickDiffUtilCallback
17 |
18 | @Before
19 | fun setup() {
20 | val brick1 = mock()
21 | whenever(brick1.layout).thenReturn(LAYOUT_1)
22 |
23 | val brick2 = mock()
24 | whenever(brick2.layout).thenReturn(LAYOUT_2)
25 |
26 | callback = BrickDiffUtilCallback(listOf(brick1), listOf(brick1, brick2))
27 | }
28 |
29 | @Test
30 | fun testGetOldListSize() {
31 | assertEquals(1, callback.oldListSize)
32 | }
33 |
34 | @Test
35 | fun testGetNewListSize() {
36 | assertEquals(2, callback.newListSize)
37 | }
38 |
39 | @Test
40 | fun testAreItemsTheSame_same() {
41 | assertTrue(callback.areItemsTheSame(0, 0))
42 | }
43 |
44 | @Test
45 | fun testAreItemsTheSame_different() {
46 | assertFalse(callback.areItemsTheSame(0, 1))
47 | }
48 |
49 | @Test
50 | fun testAreContentsTheSame_same() {
51 | assertTrue(callback.areContentsTheSame(0, 0))
52 | }
53 |
54 | @Test
55 | fun testAreContentsTheSame_different() {
56 | assertFalse(callback.areContentsTheSame(0, 1))
57 | }
58 |
59 | companion object {
60 | private const val LAYOUT_1 = 1
61 | private const val LAYOUT_2 = 2
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | types: [released]
6 |
7 | jobs:
8 | publish:
9 | name: Release build and publish
10 | runs-on: ubuntu-latest
11 | environment: publishing-environment
12 |
13 | steps:
14 | - name: Check out code
15 | uses: actions/checkout@v3
16 | - name: set up JDK 11
17 | uses: actions/setup-java@v3
18 | with:
19 | java-version: 11.0.1+13
20 | distribution: 'zulu'
21 | cache: gradle
22 | - name: Prepare environment
23 | env:
24 | GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }}
25 | SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }}
26 | run: |
27 | git fetch --unshallow
28 | sudo bash -c "echo '$GPG_KEY_CONTENTS' | base64 -d > '$SIGNING_SECRET_KEY_RING_FILE'"
29 | - name: Release build
30 | working-directory: ./BrickKit
31 | # assembleRelease for all modules, excluding non-library modules: sample, ui-components sample, docs
32 | run: ./gradlew assembleRelease
33 | - name: Source jar
34 | working-directory: ./BrickKit
35 | run: ./gradlew androidSourcesJar
36 | - name: Publish to MavenCentral
37 | working-directory: ./BrickKit
38 | env:
39 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
40 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
41 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
42 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
43 | SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }}
44 | SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
45 | run: ./gradlew publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseRepository
46 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/BrickRecyclerItemDecoration.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit
5 |
6 | import android.graphics.Rect
7 | import android.view.View
8 | import androidx.recyclerview.widget.RecyclerView
9 | import androidx.recyclerview.widget.RecyclerView.ItemDecoration
10 |
11 | /**
12 | * [androidx.recyclerview.widget.RecyclerView.ItemDecoration] which applies padding to bricks
13 | * based off of their given [com.wayfair.brickkit.padding.BrickPadding] and location in on the screen.
14 | *
15 | * Dynamic padding takes into consideration the number of bricks in a group
16 | * and the span size to appropriately set the offsets when the section has
17 | * more than one brick. Using the traditional padding mechanism
18 | * duplicates the padding between bricks where only half is desired.
19 | */
20 | internal class BrickRecyclerItemDecoration(
21 | private val brickDataManager: BrickDataManager
22 | ) : ItemDecoration() {
23 |
24 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
25 | val brick = brickDataManager.recyclerViewItems.getOrNull(parent.getChildAdapterPosition(view))
26 |
27 | if (brick != null && brickDataManager == (parent.adapter as? BrickRecyclerAdapter)?.brickDataManager) {
28 | val padding = brick.padding
29 |
30 | outRect.bottom = if (brick.isInLastRow) padding.outerBottomPadding else padding.innerBottomPadding
31 | outRect.top = if (brick.isInFirstRow) padding.outerTopPadding else padding.innerTopPadding
32 | outRect.left = if (brick.isOnLeftWall) padding.outerLeftPadding else padding.innerLeftPadding
33 | outRect.right = if (brick.isOnRightWall) padding.outerRightPadding else padding.innerRightPadding
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/infinitescroll/InfiniteScrollBrickFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.infinitescroll
5 |
6 | import android.os.Bundle
7 | import com.wayfair.brickkit.OnReachedItemAtPosition
8 | import com.wayfair.brickkit.padding.BrickPaddingFactory
9 | import com.wayfair.brickkit.size.FullPhoneFullHalfTabletBrickSize
10 | import com.wayfair.brickkitdemo.BrickFragment
11 | import com.wayfair.brickkitdemo.bricks.TextBrick
12 |
13 | /**
14 | * Example fragment which loads more bricks when scrolling to the bottom.
15 | *
16 | * This fragment takes advantage of the [OnReachedItemAtPosition] which calls back when
17 | * items are bound in the adapter.
18 | */
19 | class InfiniteScrollBrickFragment : BrickFragment() {
20 | private var page = 0
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 |
25 | addNewBricks()
26 | }
27 |
28 | override fun onResume() {
29 | super.onResume()
30 |
31 | dataManager.setOnReachedItemAtPosition(
32 | object : OnReachedItemAtPosition {
33 | override fun bindingItemAtPosition(position: Int) {
34 | if (position == dataManager.recyclerViewItems.lastIndex) {
35 | page++
36 | addNewBricks()
37 | }
38 | }
39 | }
40 | )
41 | }
42 |
43 | override fun onPause() {
44 | super.onPause()
45 |
46 | dataManager.setOnReachedItemAtPosition(null)
47 | }
48 |
49 | private fun addNewBricks() {
50 | dataManager.addLast(
51 | (0 until PAGE_SIZE).map { i -> TextBrick(FullPhoneFullHalfTabletBrickSize(), BrickPaddingFactory(resources), "Brick: $page $i") }
52 | )
53 | }
54 |
55 | companion object {
56 | private const val PAGE_SIZE = 100
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/mainfragment/bricks/ActiveBrick.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.mainfragment.bricks
5 |
6 | import android.content.res.Resources
7 | import android.view.View
8 | import android.widget.TextView
9 | import androidx.annotation.StringRes
10 | import com.wayfair.brickkit.brick.BaseBrick
11 | import com.wayfair.brickkit.padding.BrickPaddingFactory
12 | import com.wayfair.brickkit.size.PercentageBrickSize
13 | import com.wayfair.brickkit.viewholder.BrickViewHolder
14 | import com.wayfair.brickkitdemo.R
15 |
16 | /**
17 | * Used in [com.wayfair.brickkitdemo.mainfragment.MainActivityFragment] to link to other fragments.
18 | */
19 | class ActiveBrick(
20 | resources: Resources,
21 | @StringRes stringRes: Int,
22 | private val onTouch: () -> Unit
23 | ) : BaseBrick(PercentageBrickSize(.4f), BrickPaddingFactory(resources).getInnerOuterBrickPadding(R.dimen.four_dp, R.dimen.no_dp)) {
24 |
25 | var text: String = resources.getString(stringRes)
26 | set(value) {
27 | field = value
28 | refreshItem()
29 | }
30 |
31 | override fun onBindData(holder: BrickViewHolder) {
32 | val viewHolder = holder as ActiveBrickHolder
33 | viewHolder.textView.text = text
34 | viewHolder.itemView.setOnClickListener { onTouch.invoke() }
35 | }
36 |
37 | override val layout: Int = R.layout.active_brick
38 |
39 | override val placeholderLayout = R.layout.active_brick_placeholder
40 |
41 | override fun createViewHolder(itemView: View): BrickViewHolder = ActiveBrickHolder(itemView)
42 |
43 | override val isDataReady
44 | get() = text.isNotEmpty()
45 |
46 | /**
47 | * [BrickViewHolder] for ActiveBrick.
48 | */
49 | private class ActiveBrickHolder constructor(itemView: View) : BrickViewHolder(itemView) {
50 | val textView: TextView = itemView.findViewById(R.id.label)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/padding/BrickPaddingTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.padding
5 |
6 | import org.junit.Assert.assertEquals
7 | import org.junit.Test
8 |
9 | class BrickPaddingTest {
10 | private val brickPadding = BrickPadding(
11 | INNER_LEFT,
12 | INNER_TOP,
13 | INNER_RIGHT,
14 | INNER_BOTTOM,
15 | OUTER_LEFT,
16 | OUTER_TOP,
17 | OUTER_RIGHT,
18 | OUTER_BOTTOM
19 | )
20 |
21 | @Test
22 | fun testGetInnerLeftPadding() {
23 | assertEquals(INNER_LEFT, brickPadding.innerLeftPadding)
24 | }
25 |
26 | @Test
27 | fun testGetInnerTopPadding() {
28 | assertEquals(INNER_TOP, brickPadding.innerTopPadding)
29 | }
30 |
31 | @Test
32 | fun testGetInnerRightPadding() {
33 | assertEquals(INNER_RIGHT, brickPadding.innerRightPadding)
34 | }
35 |
36 | @Test
37 | fun testGetInnerBottomPadding() {
38 | assertEquals(INNER_BOTTOM, brickPadding.innerBottomPadding)
39 | }
40 |
41 | @Test
42 | fun testGetOuterLeftPadding() {
43 | assertEquals(OUTER_LEFT, brickPadding.outerLeftPadding)
44 | }
45 |
46 | @Test
47 | fun testGetOuterTopPadding() {
48 | assertEquals(OUTER_TOP, brickPadding.outerTopPadding)
49 | }
50 |
51 | @Test
52 | fun testGetOuterRightPadding() {
53 | assertEquals(OUTER_RIGHT, brickPadding.outerRightPadding)
54 | }
55 |
56 | @Test
57 | fun testGetOuterBottomPadding() {
58 | assertEquals(OUTER_BOTTOM, brickPadding.outerBottomPadding)
59 | }
60 |
61 | companion object {
62 | private const val INNER_LEFT = 1
63 | private const val INNER_TOP = 2
64 | private const val INNER_RIGHT = 3
65 | private const val INNER_BOTTOM = 4
66 | private const val OUTER_LEFT = 5
67 | private const val OUTER_TOP = 6
68 | private const val OUTER_RIGHT = 7
69 | private const val OUTER_BOTTOM = 8
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/size/BrickSize.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.size
5 |
6 | import android.content.Context
7 | import android.content.res.Configuration
8 | import android.util.Log
9 | import com.wayfair.brickkit.BrickDataManager
10 | import com.wayfair.brickkit.OpenForTesting
11 | import com.wayfair.brickkit.R
12 |
13 | /**
14 | * This class defines an abstract implementation of brick size.
15 | *
16 | * It is used to determine the amount of the screen to use when laying out the brick. The amount
17 | * of screen that is used is relative to the max span count grabbed from the BrickDataManager.
18 | *
19 | * An example would be a brick whose size was 2 that was being laid out in a BrickDataManager whose max span count
20 | * is 5. The brick would take 40% (2 / 5) of the screen width.
21 | */
22 | @OpenForTesting
23 | abstract class BrickSize internal constructor(
24 | private val portraitPhone: Int,
25 | private val landscapePhone: Int,
26 | private val portraitTablet: Int,
27 | private val landscapeTablet: Int
28 | ) {
29 | /**
30 | * Calculates the spans for this brick based off the device type and orientation.
31 | *
32 | * @param context the context to use to get resources
33 | * @return the number of spans this brick will take up
34 | */
35 | fun getSpans(context: Context): Int {
36 | val isTablet = context.resources.getBoolean(R.bool.tablet)
37 | val isLandscape = context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
38 |
39 | val spans = if (isTablet) {
40 | if (isLandscape) landscapeTablet else portraitTablet
41 | } else {
42 | if (isLandscape) landscapePhone else portraitPhone
43 | }
44 |
45 | if (spans > BrickDataManager.SPAN_COUNT) {
46 | Log.i(javaClass.simpleName, "Span needs to be less than or equal to: " + BrickDataManager.SPAN_COUNT)
47 | return BrickDataManager.SPAN_COUNT
48 | }
49 | return spans
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/BrickSpanSizeLookupTest.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit
5 |
6 | import androidx.test.core.app.ApplicationProvider
7 | import androidx.test.ext.junit.runners.AndroidJUnit4
8 | import org.mockito.kotlin.any
9 | import org.mockito.kotlin.mock
10 | import org.mockito.kotlin.whenever
11 | import com.wayfair.brickkit.brick.BaseBrick
12 | import com.wayfair.brickkit.size.BrickSize
13 | import org.junit.Assert.assertEquals
14 | import org.junit.Before
15 | import org.junit.Test
16 | import org.junit.runner.RunWith
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | class BrickSpanSizeLookupTest {
20 |
21 | private var manager: BrickDataManager = mock()
22 | private lateinit var bricks: MutableList
23 | private lateinit var brickSpanSizeLookup: BrickSpanSizeLookup
24 |
25 | @Before
26 | fun setup() {
27 | bricks = mutableListOf()
28 | whenever(manager.recyclerViewItems).thenReturn(bricks)
29 |
30 | brickSpanSizeLookup = BrickSpanSizeLookup(ApplicationProvider.getApplicationContext(), manager)
31 | }
32 |
33 | @Test
34 | fun testDefaultSpanSize_nullItem() {
35 | assertEquals(DEFAULT_SPANS, brickSpanSizeLookup.getSpanSize(0))
36 | }
37 |
38 | @Test
39 | fun testDefaultSpanSize_nullSpanSize() {
40 | val brick = mock()
41 | whenever(brick.spanSize).thenReturn(null)
42 |
43 | bricks.add(brick)
44 |
45 | assertEquals(DEFAULT_SPANS, brickSpanSizeLookup.getSpanSize(0))
46 | }
47 |
48 | @Test
49 | fun testBrickSpanSize() {
50 | val brickSize = mock()
51 | whenever(brickSize.getSpans(any())).thenReturn(SPANS)
52 | val brick = mock()
53 | whenever(brick.spanSize).thenReturn(brickSize)
54 |
55 | bricks.add(brick)
56 |
57 | assertEquals(SPANS, brickSpanSizeLookup.getSpanSize(0))
58 | }
59 |
60 | companion object {
61 | private const val DEFAULT_SPANS = 1
62 | private const val SPANS = 5
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/DataModel.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit.brick
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import java.io.Serializable
6 |
7 | /**
8 | * An abstract class that all Data Models should be based on. This is use tightly
9 | * with [ViewModel]s and will automatically notify update listeners
10 | * when [notifyChange] is called.
11 | */
12 | abstract class DataModel : Serializable {
13 | @Transient
14 | private var updateListeners: MutableSet? = null
15 | get() {
16 | if (field == null) {
17 | field = mutableSetOf()
18 | }
19 | return field
20 | }
21 |
22 | /**
23 | * Add a [DataModelUpdateListener] to the list of listeners.
24 | *
25 | * @param updateListener the listener to add
26 | */
27 | open fun addUpdateListener(updateListener: DataModelUpdateListener) {
28 | updateListeners!!.add(updateListener)
29 | }
30 |
31 | /**
32 | * Remove a [DataModelUpdateListener] to the list of listeners.
33 | *
34 | * @param updateListener the listener to remove
35 | */
36 | open fun removeUpdateListener(updateListener: DataModelUpdateListener) {
37 | updateListeners!!.remove(updateListener)
38 | }
39 |
40 | /**
41 | * This function is called when you are ready to notify listeners that the data has changed.
42 | */
43 | open fun notifyChange() {
44 | val handler = Handler(Looper.getMainLooper())
45 |
46 | updateListeners!!.forEach { updateListener ->
47 | handler.post { updateListener.notifyChange() }
48 | }
49 | }
50 |
51 | /**
52 | * Determines if the DataModel is ready to be processed by a [ViewModel].
53 | *
54 | * @return true if the data is ready, defaults to true
55 | */
56 | open val isReady: Boolean = true
57 |
58 | /**
59 | * The interface required to be implemented in order to listen for changes on [DataModel]s.
60 | */
61 | interface DataModelUpdateListener {
62 |
63 | /**
64 | * Should handle updating whatever is listening to a [DataModel].
65 | */
66 | fun notifyChange()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/fragmentbrick/bricks/FragmentBrick.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.fragmentbrick.bricks
5 |
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.FrameLayout
10 | import androidx.annotation.ColorRes
11 | import androidx.fragment.app.Fragment
12 | import androidx.fragment.app.FragmentManager
13 | import com.wayfair.brickkit.brick.BaseBrick
14 | import com.wayfair.brickkit.padding.ZeroBrickPadding
15 | import com.wayfair.brickkit.size.HalfWidthBrickSize
16 | import com.wayfair.brickkit.viewholder.BrickViewHolder
17 | import com.wayfair.brickkitdemo.R
18 |
19 | /**
20 | * Brick whose content is a fragment.
21 | */
22 | class FragmentBrick(
23 | private val fragmentManager: FragmentManager,
24 | private val fragment: Fragment,
25 | @ColorRes private val backgroundColor: Int
26 | ) : BaseBrick(HalfWidthBrickSize(), ZeroBrickPadding()) {
27 |
28 | override fun onBindData(holder: BrickViewHolder) {
29 | val viewHolder = holder as FragmentBrickViewHolder
30 |
31 | val view: View? = if (!fragment.isAdded) {
32 | fragmentManager.beginTransaction().add(fragment, null).commit()
33 | fragmentManager.executePendingTransactions()
34 | fragment.onAttach(holder.itemView.context)
35 | fragment.onCreateView(LayoutInflater.from(holder.itemView.context), viewHolder.frameLayout, null)
36 | } else {
37 | fragment.view
38 | }
39 |
40 | (view?.parent as? ViewGroup)?.removeView(view)
41 |
42 | viewHolder.itemView.setBackgroundColor(holder.itemView.resources.getColor(backgroundColor, null))
43 | viewHolder.frameLayout.addView(view)
44 | }
45 |
46 | override val layout = R.layout.fragment_brick
47 |
48 | override fun createViewHolder(itemView: View): BrickViewHolder = FragmentBrickViewHolder(itemView)
49 |
50 | /**
51 | * [BrickViewHolder] for FragmentBrick.
52 | */
53 | private class FragmentBrickViewHolder constructor(itemView: View) : BrickViewHolder(itemView) {
54 | val frameLayout: FrameLayout = itemView.findViewById(R.id.fragment_container)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/res/layout/controller_brick_vm.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
27 |
28 |
40 |
41 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/addremove/AddRemoveBrickFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.addremove
5 |
6 | import android.os.Bundle
7 | import com.wayfair.brickkit.brick.ViewModelBrick
8 | import com.wayfair.brickkit.padding.BrickPaddingFactory
9 | import com.wayfair.brickkit.size.FullWidthBrickSize
10 | import com.wayfair.brickkitdemo.BR
11 | import com.wayfair.brickkitdemo.BrickFragment
12 | import com.wayfair.brickkitdemo.R
13 | import com.wayfair.brickkitdemo.addremove.datamodel.ControllerDataModel
14 | import com.wayfair.brickkitdemo.addremove.viewmodel.ControllerViewModel
15 | import com.wayfair.brickkitdemo.addremove.viewmodel.ControllerViewModel.Interactions
16 | import com.wayfair.brickkitdemo.bricks.TextBrick
17 |
18 | /**
19 | * Demo fragment that allows you to add bricks to the end and remove bricks from the beginning.
20 | */
21 | class AddRemoveBrickFragment : BrickFragment() {
22 |
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | super.onCreate(savedInstanceState)
25 |
26 | val brickPaddingFactory = BrickPaddingFactory(resources)
27 | val dataModel = ControllerDataModel(NUMBER_OF_BRICKS)
28 |
29 | ViewModelBrick.Builder(R.layout.controller_brick_vm)
30 | .setSpanSize(FullWidthBrickSize())
31 | .setPadding(brickPaddingFactory.getInnerOuterBrickPadding(R.dimen.four_dp, R.dimen.eight_dp))
32 | .addViewModel(
33 | BR.viewModel,
34 | ControllerViewModel(
35 | dataModel,
36 | object : Interactions {
37 | override fun onRemoveClicked() {
38 | dataManager.recyclerViewItems.firstOrNull { brick -> brick is TextBrick }?.let { dataManager.removeItem(it) }
39 | }
40 |
41 | override fun onAddClicked() {
42 | dataManager.addLast(TextBrick(FullWidthBrickSize(), brickPaddingFactory, "Brick: ${dataModel.value++}"))
43 | }
44 | }
45 | )
46 | )
47 | .build()
48 | .addLastTo(dataManager)
49 |
50 | dataManager.addLast(
51 | (0 until NUMBER_OF_BRICKS).map { i -> TextBrick(FullWidthBrickSize(), brickPaddingFactory, "Brick: $i") }
52 | )
53 | }
54 |
55 | companion object {
56 | private const val NUMBER_OF_BRICKS = 20
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/BrickKit/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/viewholder/factory/BrickViewHolderFactoryTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.viewholder.factory
5 |
6 | import android.view.ViewGroup
7 | import android.widget.FrameLayout
8 | import androidx.test.core.app.ApplicationProvider
9 | import androidx.test.ext.junit.runners.AndroidJUnit4
10 | import org.mockito.kotlin.mock
11 | import com.wayfair.brickkit.BrickRecyclerAdapter
12 | import com.wayfair.brickkit.brick.BaseBrick
13 | import com.wayfair.brickkit.test.R
14 | import com.wayfair.brickkit.viewholder.factory.BrickViewHolderFactory.EmptyBrickViewHolder
15 | import org.junit.Assert.assertTrue
16 | import org.junit.Before
17 | import org.junit.Test
18 | import org.junit.runner.RunWith
19 |
20 | @RunWith(AndroidJUnit4::class)
21 | class BrickViewHolderFactoryTest {
22 | private val factory = BrickViewHolderFactory()
23 | private lateinit var bricks: List
24 | private lateinit var parentView: ViewGroup
25 |
26 | @Before
27 | fun setup() {
28 | val brick = mock()
29 | bricks = listOf(brick)
30 | parentView = FrameLayout(ApplicationProvider.getApplicationContext())
31 | }
32 |
33 | @Test
34 | fun testCreateBrickViewHolder_defaultResId() {
35 | assertTrue(factory.createBrickViewHolder(parentView, BrickRecyclerAdapter.DEFAULT_LAYOUT_RES_ID, bricks) is EmptyBrickViewHolder)
36 | }
37 |
38 | @Test
39 | fun testCreateBrickViewHolder_provideNeither() {
40 | assertTrue(factory.createBrickViewHolder(parentView, VALID_NON_MATCHED_RES_ID, bricks) is EmptyBrickViewHolder)
41 | }
42 |
43 | @Test
44 | fun testCreateBrickViewHolder_resourceNotFoundException() {
45 | assertTrue(factory.createBrickViewHolder(parentView, EXCEPTIONAL_RES_ID, bricks) is EmptyBrickViewHolder)
46 | }
47 |
48 | @Test
49 | fun testCreateBrickViewHolder_provideBrickWithLayout() {
50 | assertTrue(factory.createBrickViewHolder(parentView, VALID_RES_ID, bricks) is EmptyBrickViewHolder)
51 | }
52 |
53 | @Test
54 | fun testCreateBrickViewHolder_provideBrickWithPlaceholderLayout() {
55 | assertTrue(factory.createBrickViewHolder(parentView, PLACEHOLDER_RES_ID, bricks) is EmptyBrickViewHolder)
56 | }
57 |
58 | companion object {
59 | private val VALID_RES_ID = R.layout.text_brick_vm
60 | private val PLACEHOLDER_RES_ID = R.layout.text_brick_vm_placeholder
61 | private const val VALID_NON_MATCHED_RES_ID = 1
62 | private const val EXCEPTIONAL_RES_ID = 2
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/simplebrick/SimpleBrickFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.simplebrick
5 |
6 | import android.os.Bundle
7 | import com.wayfair.brickkit.brick.ViewModelBrick
8 | import com.wayfair.brickkit.padding.BrickPaddingFactory
9 | import com.wayfair.brickkit.size.FullPhoneFullHalfTabletBrickSize
10 | import com.wayfair.brickkitdemo.BR
11 | import com.wayfair.brickkitdemo.BrickFragment
12 | import com.wayfair.brickkitdemo.R
13 | import com.wayfair.brickkitdemo.simplebrick.datamodel.TextDataModel
14 | import com.wayfair.brickkitdemo.simplebrick.viewmodel.TextViewModel
15 | import java.util.concurrent.ScheduledThreadPoolExecutor
16 | import java.util.concurrent.TimeUnit
17 |
18 | /**
19 | * Example fragment which shows text bricks and updates each second.
20 | */
21 | class SimpleBrickFragment : BrickFragment() {
22 | private val executor = ScheduledThreadPoolExecutor(1)
23 |
24 | private val dataModels by lazy {
25 | (0 until (arguments?.getInt(NUMBER_OF_BRICKS) ?: DEFAULT_BRICK_COUNT)).map { i ->
26 | TextDataModel("Brick: $i")
27 | }
28 | }
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 |
33 | dataModels.forEach { dataModel ->
34 | ViewModelBrick.Builder(R.layout.text_brick_vm)
35 | .setPadding(BrickPaddingFactory(resources).getInnerOuterBrickPadding(R.dimen.four_dp, R.dimen.eight_dp))
36 | .setSpanSize(FullPhoneFullHalfTabletBrickSize())
37 | .addViewModel(BR.viewModel, TextViewModel(dataModel))
38 | .build()
39 | .addLastTo(dataManager)
40 | }
41 | }
42 |
43 | override fun onResume() {
44 | super.onResume()
45 |
46 | dataModels.forEach { dataModel ->
47 | executor.scheduleAtFixedRate({ dataModel.appendText(" Hello") }, 0, 1, TimeUnit.SECONDS)
48 | }
49 | }
50 |
51 | override fun onPause() {
52 | super.onPause()
53 |
54 | executor.purge()
55 | }
56 |
57 | companion object {
58 | private const val NUMBER_OF_BRICKS = "number_of_bricks"
59 | private const val DEFAULT_BRICK_COUNT = 100
60 |
61 | /**
62 | * Create a new instance of a SimpleBrickFragment.
63 | *
64 | * @param numberOfBricks The number of bricks you want in the fragment
65 | * @return The SimpleBrickFragment you created
66 | */
67 | fun newInstance(numberOfBricks: Int = DEFAULT_BRICK_COUNT) = SimpleBrickFragment().apply {
68 | arguments = Bundle().apply {
69 | putInt(NUMBER_OF_BRICKS, numberOfBricks)
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/viewholder/factory/BrickViewHolderFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.viewholder.factory
5 |
6 | import android.content.res.Resources
7 | import android.util.Log
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import com.wayfair.brickkit.BrickRecyclerAdapter
12 | import com.wayfair.brickkit.brick.BaseBrick
13 | import com.wayfair.brickkit.viewholder.BrickViewHolder
14 |
15 | /**
16 | * A factory used to create various types of [BrickViewHolder] objects.
17 | *
18 | * This class takes it's roots from the Factory Design Pattern. Instead of using an interface, it
19 | * is using the [BrickViewHolder] abstract class. For more information on the pattern,
20 | * refer to:
21 | * [Factory Design Pattern](https://www.tutorialspoint.com/design_pattern/factory_pattern.htm).
22 | */
23 | internal class BrickViewHolderFactory {
24 | /**
25 | * Creates [BrickViewHolder] objects based on the {@param #data}.
26 | *
27 | * @param parent of the view, passed into the [BrickViewHolder]'s constructor.
28 | * @param viewType of the view to create. It can be a @LayoutRes id.
29 | * @param items [BaseBrick]s to search within
30 | *
31 | * @return the created [BrickViewHolder]
32 | */
33 | fun createBrickViewHolder(parent: ViewGroup, viewType: Int, items: List): BrickViewHolder {
34 | var viewHolder: BrickViewHolder? = null
35 | try {
36 | if (viewType > BrickRecyclerAdapter.DEFAULT_LAYOUT_RES_ID) {
37 | val brick = brickWithLayout(items, viewType) ?: brickWithPlaceholderLayout(items, viewType)
38 | viewHolder = brick?.createViewHolder(LayoutInflater.from(parent.context).inflate(viewType, parent, false))
39 | }
40 | } catch (nfe: Resources.NotFoundException) {
41 | Log.w(TAG, "Unable to find the resource. parent=$parent, viewType=$viewType", nfe)
42 | }
43 |
44 | return viewHolder ?: EmptyBrickViewHolder(View(parent.context))
45 | }
46 |
47 | private fun brickWithLayout(items: List, layoutResId: Int): BaseBrick? {
48 | return items.firstOrNull { brick -> brick.layout == layoutResId }
49 | }
50 |
51 | private fun brickWithPlaceholderLayout(items: List, layoutResId: Int): BaseBrick? {
52 | return items.firstOrNull { brick -> !brick.isDataReady && brick.placeholderLayout == layoutResId }
53 | }
54 |
55 | /**
56 | * A empty versions of a [BrickViewHolder], for use in scenarios, such as when a brick
57 | * object reference is null and a [BrickViewHolder] instance can't be created.
58 | */
59 | internal class EmptyBrickViewHolder(itemView: View) : BrickViewHolder(itemView)
60 |
61 | companion object {
62 | private const val TAG = "BrickViewHolderFactory"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/mainfragment/MainActivityFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.mainfragment
5 |
6 | import android.os.Bundle
7 | import android.os.Handler
8 | import android.os.Looper
9 | import androidx.fragment.app.Fragment
10 | import com.wayfair.brickkit.brick.BaseBrick
11 | import com.wayfair.brickkit.size.BrickSize
12 | import com.wayfair.brickkit.size.PercentageBrickSize
13 | import com.wayfair.brickkitdemo.addremove.AddRemoveBrickFragment
14 | import com.wayfair.brickkitdemo.BrickFragment
15 | import com.wayfair.brickkitdemo.infinitescroll.InfiniteScrollBrickFragment
16 | import com.wayfair.brickkitdemo.R
17 | import com.wayfair.brickkitdemo.simplebrick.SimpleBrickFragment
18 | import com.wayfair.brickkitdemo.staggeredinfinitescroll.StaggeredInfiniteScrollBrickFragment
19 | import com.wayfair.brickkitdemo.fragmentbrick.FragmentBrickFragment
20 | import com.wayfair.brickkitdemo.mainfragment.bricks.ActiveBrick
21 | import com.wayfair.brickkitdemo.mainfragment.bricks.PassiveBrick
22 | import java.util.concurrent.TimeUnit
23 |
24 | /**
25 | * Main fragment that links to the other child fragments. Additionally this shows an example of placeholders.
26 | */
27 | class MainActivityFragment : BrickFragment() {
28 | private var rowNumber = 0
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 |
33 | val placeholderBrick = ActiveBrick(resources, R.string.empty) { changeFragment(SimpleBrickFragment.newInstance()) }
34 | .also {
35 | // Fake waiting as in a API request to show off placeholder when data is not ready
36 | Handler(Looper.getMainLooper()).postDelayed(
37 | { it.text = getString(R.string.simple_brick_title) },
38 | TimeUnit.SECONDS.toMillis(2)
39 | )
40 | }
41 |
42 | addRow(PassiveBrick(TWO_FIFTH, resources))
43 | addRow(placeholderBrick)
44 | addRow(ActiveBrick(resources, R.string.add_remove_brick_title) { changeFragment(AddRemoveBrickFragment()) })
45 | addRow(ActiveBrick(resources, R.string.infinite_scroll_brick_title) { changeFragment(InfiniteScrollBrickFragment()) })
46 | addRow(ActiveBrick(resources, R.string.staggered_infinite_scroll_brick_title) { changeFragment(StaggeredInfiniteScrollBrickFragment()) })
47 | addRow(ActiveBrick(resources, R.string.fragment_brick_title) { changeFragment(FragmentBrickFragment()) })
48 | addRow(PassiveBrick(TWO_FIFTH, resources))
49 | }
50 |
51 | private fun addRow(middleBrick: BaseBrick) {
52 | dataManager.addLast(PassiveBrick(if (rowNumber % 2 == 0) ONE_FIFTH else TWO_FIFTH, resources))
53 | dataManager.addLast(middleBrick)
54 | dataManager.addLast(PassiveBrick(if (rowNumber % 2 == 0) TWO_FIFTH else ONE_FIFTH, resources))
55 | rowNumber += 1
56 | }
57 |
58 | private fun changeFragment(fragment: Fragment) {
59 | parentFragmentManager.beginTransaction().replace(R.id.content, fragment).addToBackStack(null).commit()
60 | }
61 |
62 | companion object {
63 | private val ONE_FIFTH: BrickSize = PercentageBrickSize(.2f)
64 | private val TWO_FIFTH: BrickSize = PercentageBrickSize(.4f)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/BrickKit/app/src/main/java/com/wayfair/brickkitdemo/staggeredinfinitescroll/StaggeredInfiniteScrollBrickFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkitdemo.staggeredinfinitescroll
5 |
6 | import android.os.Bundle
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.recyclerview.widget.RecyclerView
11 | import androidx.recyclerview.widget.StaggeredGridLayoutManager
12 | import com.wayfair.brickkit.OnReachedItemAtPosition
13 | import com.wayfair.brickkit.padding.BrickPaddingFactory
14 | import com.wayfair.brickkit.size.FullWidthBrickSize
15 | import com.wayfair.brickkit.size.HalfWidthBrickSize
16 | import com.wayfair.brickkitdemo.BrickFragment
17 | import com.wayfair.brickkitdemo.R
18 | import com.wayfair.brickkitdemo.bricks.TextBrick
19 |
20 | /**
21 | * Example fragment which loads more bricks when scrolling to the bottom.
22 | *
23 | * This fragment takes advantage of the [OnReachedItemAtPosition] which calls back when
24 | * items are bound in the adapter.
25 | */
26 | class StaggeredInfiniteScrollBrickFragment : BrickFragment() {
27 | private var page = 0
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 |
32 | addNewBricks()
33 | }
34 |
35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
36 | super.onCreateView(inflater, container, savedInstanceState)?.apply {
37 | findViewById(R.id.recycler_view).layoutManager =
38 | StaggeredGridLayoutManager(COLUMN_COUNT, StaggeredGridLayoutManager.VERTICAL).apply {
39 | isItemPrefetchEnabled = false
40 | }
41 | }
42 |
43 | override fun onResume() {
44 | super.onResume()
45 |
46 | dataManager.setOnReachedItemAtPosition(
47 | object : OnReachedItemAtPosition {
48 | override fun bindingItemAtPosition(position: Int) {
49 | if (position == dataManager.recyclerViewItems.lastIndex) {
50 | page++
51 | addNewBricks()
52 | }
53 | }
54 | }
55 | )
56 | }
57 |
58 | override fun onPause() {
59 | super.onPause()
60 |
61 | dataManager.setOnReachedItemAtPosition(null)
62 | }
63 |
64 | private fun addNewBricks() {
65 | dataManager.addLast(
66 | (0 until PAGE_SIZE).map { i ->
67 | TextBrick(
68 | if (i % FULL_WIDTH_MODULO == 0) FullWidthBrickSize() else HalfWidthBrickSize(),
69 | BrickPaddingFactory(resources),
70 | when {
71 | i % FULL_WIDTH_MODULO == 0 -> "Brick: $page fullsize $i"
72 | i % MULTI_LINE_MODULO == 0 -> "Brick: $page multi\nline $i"
73 | else -> "Brick: $page $i"
74 | }
75 | )
76 | }
77 | )
78 | }
79 |
80 | companion object {
81 | private const val COLUMN_COUNT = 2
82 | private const val PAGE_SIZE = 100
83 | private const val FULL_WIDTH_MODULO = 19
84 | private const val MULTI_LINE_MODULO = 4
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/BrickKit/bricks/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'checkstyle'
4 | id 'maven-publish'
5 | }
6 |
7 | apply plugin: 'kotlin-android'
8 | apply plugin: 'kotlin-kapt'
9 | apply plugin: 'kotlin-allopen'
10 |
11 | ext {
12 | PUBLISH_GROUP_ID = "com.wayfair"
13 | PUBLISH_VERSION = getCurrentVersion()
14 | PUBLISH_ARTIFACT_ID = 'brickkit-android'
15 | }
16 |
17 | apply from: "${project.rootDir}/scripts/publish-mavencentral.gradle"
18 |
19 | android {
20 | compileSdkVersion 33
21 |
22 | defaultConfig {
23 | minSdkVersion 23
24 | multiDexEnabled true
25 |
26 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
27 | }
28 |
29 | buildFeatures.dataBinding = true
30 |
31 | buildTypes {
32 | debug {
33 | testCoverageEnabled true
34 | }
35 | }
36 |
37 | compileOptions {
38 | sourceCompatibility JavaVersion.VERSION_11
39 | targetCompatibility JavaVersion.VERSION_11
40 | }
41 |
42 | kotlinOptions {
43 | jvmTarget = 11
44 | }
45 |
46 | namespace 'com.wayfair.brickkit'
47 |
48 | lint {
49 | abortOnError true
50 | warningsAsErrors true
51 | }
52 | }
53 |
54 | dependencies {
55 | implementation 'androidx.core:core-ktx:1.9.0'
56 | implementation 'androidx.legacy:legacy-support-core-utils:1.0.0'
57 | implementation 'androidx.legacy:legacy-support-core-ui:1.0.0'
58 | implementation 'androidx.appcompat:appcompat:1.5.1'
59 | implementation 'androidx.recyclerview:recyclerview:1.2.1'
60 | implementation 'com.google.android.material:material:1.7.0'
61 | implementation 'androidx.gridlayout:gridlayout:1.0.0'
62 |
63 | testImplementation 'junit:junit:4.13.2'
64 |
65 | kaptAndroidTest 'androidx.databinding:databinding-compiler:7.3.1'
66 |
67 | androidTestImplementation 'org.mockito:mockito-core:4.8.1'
68 | androidTestImplementation("org.mockito.kotlin:mockito-kotlin:4.0.0") {
69 | exclude group: "org.mockito", module: "mockito-core"
70 | }
71 | androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.3'
72 | androidTestImplementation('androidx.test.espresso:espresso-core:3.4.0') {
73 | exclude group: 'androidx.annotation', module: 'annotation'
74 | }
75 | androidTestImplementation 'androidx.test:core:1.4.0'
76 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
77 | }
78 |
79 | check.dependsOn("checkstyle")
80 |
81 | gradle.projectsEvaluated {
82 | createDebugAndroidTestCoverageReport.dependsOn("connectedDebugAndroidTest")
83 | }
84 |
85 | allOpen {
86 | annotation 'com.wayfair.brickkit.OpenForTesting'
87 | }
88 |
89 | task checkstyle(type: Checkstyle) {
90 | configFile file("${project.rootDir}/config/quality/checkstyle/checkstyle.xml")
91 | configProperties.checkstyleSuppressionsPath = file("${project.rootDir}/config/quality/checkstyle/suppressions.xml").absolutePath
92 | source 'src/main'
93 | include '**/*.java'
94 | classpath = files() as FileCollection
95 | }
96 |
97 | static def getCurrentVersion() {
98 | return "git describe --abbrev=0 --tag".execute().text.trim()
99 | }
100 |
101 | // This is mandatory
102 | group = PUBLISH_GROUP_ID
103 | version = PUBLISH_VERSION
104 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/brick/BaseBrickTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.brick
5 |
6 | import android.view.View
7 | import com.wayfair.brickkit.BrickDataManager
8 | import com.wayfair.brickkit.viewholder.BrickViewHolder
9 | import org.junit.Assert.assertEquals
10 | import org.junit.Assert.assertFalse
11 | import org.junit.Assert.assertNull
12 | import org.junit.Assert.assertTrue
13 | import org.junit.Test
14 | import org.mockito.kotlin.mock
15 | import org.mockito.kotlin.never
16 | import org.mockito.kotlin.verify
17 | import org.mockito.kotlin.verifyNoInteractions
18 |
19 | class BaseBrickTest {
20 | private val brickDataManager: BrickDataManager = mock()
21 |
22 | private val brick: BaseBrick = object : BaseBrick(mock(), mock()) {
23 | override fun onBindData(holder: BrickViewHolder) {}
24 | override val layout = LAYOUT
25 | override fun createViewHolder(itemView: View): BrickViewHolder = mock()
26 | }
27 |
28 | @Test
29 | fun testHidden() {
30 | brick.setDataManager(brickDataManager)
31 | brick.setDataManager(null)
32 |
33 | assertFalse(brick.isHidden)
34 |
35 | brick.isHidden = true
36 | assertTrue(brick.isHidden)
37 |
38 | brick.isHidden = false
39 | assertFalse(brick.isHidden)
40 |
41 | verifyNoInteractions(brickDataManager)
42 | }
43 |
44 | @Test
45 | fun testHidden_falseToTrue() {
46 | brick.isHidden = false
47 | brick.setDataManager(brickDataManager)
48 |
49 | brick.isHidden = true
50 | assertTrue(brick.isHidden)
51 | verify(brickDataManager).hideItem(brick)
52 | }
53 |
54 | @Test
55 | fun testHidden_falseToFalse() {
56 | brick.isHidden = false
57 | brick.setDataManager(brickDataManager)
58 |
59 | brick.isHidden = true
60 | assertTrue(brick.isHidden)
61 | verify(brickDataManager, never()).showItem(brick)
62 | }
63 |
64 | @Test
65 | fun testTag_nullDataManager() {
66 | assertNull(brick.tag)
67 |
68 | brick.tag = TAG_1
69 |
70 | brick.setDataManager(brickDataManager)
71 |
72 | brick.tag = TAG_1
73 |
74 | verifyNoInteractions(brickDataManager)
75 | }
76 |
77 | @Test
78 | fun testLayout() {
79 | assertEquals(LAYOUT, brick.layout)
80 | }
81 |
82 | @Test(expected = UnsupportedOperationException::class)
83 | fun testPlaceholderLayout() {
84 | brick.placeholderLayout
85 | }
86 |
87 | @Test
88 | fun testRefreshItem() {
89 | brick.refreshItem()
90 | brick.setDataManager(brickDataManager)
91 | brick.refreshItem()
92 |
93 | verify(brickDataManager).refreshItem(brick)
94 | }
95 |
96 | @Test
97 | fun testAddLastTo() {
98 | brick.addLastTo(brickDataManager)
99 |
100 | verify(brickDataManager).addLast(brick)
101 | }
102 |
103 | @Test
104 | fun testAddFirstTo() {
105 | brick.addFirstTo(brickDataManager)
106 |
107 | verify(brickDataManager).addFirst(brick)
108 | }
109 |
110 | @Test
111 | fun testIsDataReady() {
112 | assertTrue(brick.isDataReady)
113 | }
114 |
115 | @Test
116 | fun testSmoothScroll() {
117 | brick.setDataManager(brickDataManager)
118 | brick.smoothScroll()
119 |
120 | verify(brickDataManager).smoothScrollToBrick(brick)
121 | }
122 |
123 | @Test
124 | fun testSmoothScroll_nullDataManager() {
125 | brick.setDataManager(brickDataManager)
126 | brick.setDataManager(null)
127 |
128 | brick.smoothScroll()
129 |
130 | verifyNoInteractions(brickDataManager)
131 | }
132 |
133 | companion object {
134 | private const val LAYOUT = 1234
135 | private const val TAG_1 = "tag 1"
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/padding/BrickPaddingFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.padding
5 |
6 | import android.content.res.Resources
7 | import androidx.annotation.DimenRes
8 | import com.wayfair.brickkit.OpenForTesting
9 | import com.wayfair.brickkit.R
10 |
11 | @OpenForTesting
12 | class BrickPaddingFactory(private val resources: Resources) {
13 |
14 | fun getSimpleBrickPadding(@DimenRes dimen: Int): BrickPadding {
15 | val padding = resources.getDimension(dimen).toInt()
16 | return BrickPadding(padding, padding, padding, padding, padding, padding, padding, padding)
17 | }
18 |
19 | fun getInnerOuterBrickPadding(
20 | @DimenRes innerPaddingDimensionRes: Int = R.dimen.no_dp,
21 | @DimenRes outerPaddingDimensionRes: Int = R.dimen.no_dp
22 | ): BrickPadding {
23 | val innerPadding = resources.getDimension(innerPaddingDimensionRes).toInt()
24 | val outerPadding = resources.getDimension(outerPaddingDimensionRes).toInt()
25 | return BrickPadding(
26 | innerPadding,
27 | innerPadding,
28 | innerPadding,
29 | innerPadding,
30 | outerPadding,
31 | outerPadding,
32 | outerPadding,
33 | outerPadding
34 | )
35 | }
36 |
37 | fun getRectBrickPadding(
38 | @DimenRes left: Int = R.dimen.no_dp,
39 | @DimenRes top: Int = R.dimen.no_dp,
40 | @DimenRes right: Int = R.dimen.no_dp,
41 | @DimenRes bottom: Int = R.dimen.no_dp
42 | ): BrickPadding {
43 | val leftPadding = resources.getDimension(left).toInt()
44 | val topPadding = resources.getDimension(top).toInt()
45 | val rightPadding = resources.getDimension(right).toInt()
46 | val bottomPadding = resources.getDimension(bottom).toInt()
47 | return BrickPadding(
48 | leftPadding,
49 | topPadding,
50 | rightPadding,
51 | bottomPadding,
52 | leftPadding,
53 | topPadding,
54 | rightPadding,
55 | bottomPadding
56 | )
57 | }
58 |
59 | fun getInnerOuterRectBrickPadding(
60 | @DimenRes innerLeft: Int = R.dimen.no_dp,
61 | @DimenRes innerTop: Int = R.dimen.no_dp,
62 | @DimenRes innerRight: Int = R.dimen.no_dp,
63 | @DimenRes innerBottom: Int = R.dimen.no_dp,
64 | @DimenRes outerLeft: Int = R.dimen.no_dp,
65 | @DimenRes outerTop: Int = R.dimen.no_dp,
66 | @DimenRes outerRight: Int = R.dimen.no_dp,
67 | @DimenRes outerBottom: Int = R.dimen.no_dp
68 | ): BrickPadding {
69 | return BrickPadding(
70 | resources.getDimension(innerLeft).toInt(),
71 | resources.getDimension(innerTop).toInt(),
72 | resources.getDimension(innerRight).toInt(),
73 | resources.getDimension(innerBottom).toInt(),
74 | resources.getDimension(outerLeft).toInt(),
75 | resources.getDimension(outerTop).toInt(),
76 | resources.getDimension(outerRight).toInt(),
77 | resources.getDimension(outerBottom).toInt()
78 | )
79 | }
80 |
81 | /**
82 | * Applies the "standard" view inset padding as the outerPadding.
83 | * The dimens that are passed in will be used as the innerPadding.
84 | * Recommended as the default brick padding.
85 | */
86 | @JvmOverloads
87 | fun getViewInsetPadding(
88 | @DimenRes paddingLeft: Int = R.dimen.standard_margin_named_default_half,
89 | @DimenRes paddingTop: Int = R.dimen.standard_margin_named_default_half,
90 | @DimenRes paddingRight: Int = R.dimen.standard_margin_named_default_half,
91 | @DimenRes paddingBottom: Int = R.dimen.standard_margin_named_default_half
92 | ) = getInnerOuterRectBrickPadding(
93 | paddingLeft,
94 | paddingTop,
95 | paddingRight,
96 | paddingBottom,
97 | R.dimen.standard_margin_named_view_inset,
98 | R.dimen.standard_margin_named_view_inset,
99 | R.dimen.standard_margin_named_view_inset,
100 | R.dimen.standard_margin_named_view_inset
101 | )
102 | }
103 |
--------------------------------------------------------------------------------
/BrickKit/config/quality/checkstyle/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/BaseBrick.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.brick
5 |
6 | import android.view.View
7 | import androidx.annotation.LayoutRes
8 | import com.wayfair.brickkit.BrickDataManager
9 | import com.wayfair.brickkit.padding.BrickPadding
10 | import com.wayfair.brickkit.size.BrickSize
11 | import com.wayfair.brickkit.viewholder.BrickViewHolder
12 | import java.lang.UnsupportedOperationException
13 |
14 | /**
15 | * Abstract class which defines Bricks.
16 | */
17 | abstract class BaseBrick constructor(
18 | open val spanSize: BrickSize,
19 | internal open val padding: BrickPadding
20 | ) {
21 | /**
22 | * The brick's tag. This is similar to a [View.getTag].
23 | */
24 | var tag: Any? = null
25 |
26 | /**
27 | * Whether the brick should be hidden.
28 | */
29 | var isHidden = false
30 | set(hidden) {
31 | val wasHidden = isHidden
32 |
33 | field = hidden
34 |
35 | if (wasHidden != hidden) {
36 | if (!wasHidden) {
37 | dataManager?.hideItem(this)
38 | } else {
39 | dataManager?.showItem(this)
40 | }
41 | }
42 | }
43 | /**
44 | * Whether the brick should act as it is in the first row.
45 | */
46 | internal open var isInFirstRow = false
47 |
48 | /**
49 | * Whether the brick should act as it is in the last row.
50 | */
51 | internal open var isInLastRow = false
52 |
53 | /**
54 | * Whether the brick should act as it is on the left wall.
55 | */
56 | internal open var isOnLeftWall = false
57 |
58 | /**
59 | * Whether the brick should act as it is on the right wall.
60 | */
61 | internal open var isOnRightWall = false
62 |
63 | private var dataManager: BrickDataManager? = null
64 |
65 | /**
66 | * Set the current DataManager this brick is connected to.
67 | * @param brickDataManager The current [BrickDataManager] for this brick
68 | */
69 | internal fun setDataManager(brickDataManager: BrickDataManager?) {
70 | dataManager = brickDataManager
71 | }
72 |
73 | /**
74 | * Called to display the information in this brick to the specified ViewHolder.
75 | *
76 | * @param holder BrickViewHolder which should be updated.
77 | */
78 | abstract fun onBindData(holder: BrickViewHolder)
79 |
80 | /**
81 | * Get layout resource id for this brick.
82 | *
83 | * @return the layout resource id for this brick
84 | */
85 | @get:LayoutRes
86 | abstract val layout: Int
87 |
88 | /**
89 | * Current brick must override this in order for the adapter to inflate
90 | * either placeholder or brick layout. If not overridden we always assume data is ready.
91 | *
92 | * @return True if data is ready
93 | */
94 | open val isDataReady: Boolean
95 | get() = true
96 |
97 | /**
98 | * Get layout resource id for this brick's placeholder.
99 | *
100 | * @return The layout resource id for this brick's placeholder
101 | */
102 | @get:LayoutRes
103 | open val placeholderLayout: Int
104 | get() = throw UnsupportedOperationException(
105 | "${javaClass.simpleName}.placeholderLayout must be overridden within brick extending BaseBrick"
106 | )
107 |
108 | /**
109 | * Creates an instance of the [BrickViewHolder] for this class.
110 | *
111 | * @param itemView view to pass into the [BrickViewHolder]
112 | * @return the [BrickViewHolder]
113 | */
114 | abstract fun createViewHolder(itemView: View): BrickViewHolder
115 |
116 | /**
117 | * If this brick as attached to a DataManager, refresh this brick in that DataManager.
118 | */
119 | open fun refreshItem() {
120 | dataManager?.refreshItem(this)
121 | }
122 |
123 | open fun smoothScroll() {
124 | dataManager?.smoothScrollToBrick(this)
125 | }
126 |
127 | /**
128 | * Adds this brick to the end of the supplied [BrickDataManager].
129 | *
130 | * @param dataManager The [BrickDataManager] to add to
131 | */
132 | fun addLastTo(dataManager: BrickDataManager) {
133 | dataManager.addLast(this)
134 | }
135 |
136 | /**
137 | * Adds this brick to the end of the supplied [BrickDataManager].
138 | *
139 | * @param dataManager The [BrickDataManager] to add to
140 | */
141 | fun addFirstTo(dataManager: BrickDataManager) {
142 | dataManager.addFirst(this)
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/BrickRecyclerItemDecorationTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit
5 |
6 | import android.graphics.Rect
7 | import android.view.View
8 | import androidx.recyclerview.widget.RecyclerView
9 | import org.mockito.kotlin.mock
10 | import org.mockito.kotlin.whenever
11 | import com.wayfair.brickkit.brick.BaseBrick
12 | import com.wayfair.brickkit.padding.BrickPadding
13 | import org.junit.Assert.assertEquals
14 | import org.junit.Before
15 | import org.junit.Test
16 |
17 | class BrickRecyclerItemDecorationTest {
18 | private val parent: RecyclerView = mock()
19 | private val outRect: Rect = Rect()
20 | private val view: View = mock()
21 | private val state: RecyclerView.State = mock()
22 | private val bricks = mutableListOf()
23 | private val brick: BaseBrick = mock()
24 | private lateinit var itemDecoration: BrickRecyclerItemDecoration
25 |
26 | @Before
27 | fun setup() {
28 | whenever(parent.getChildAdapterPosition(view)).thenReturn(0)
29 |
30 | val brickDataManager = mock()
31 | whenever(brickDataManager.recyclerViewItems).thenReturn(bricks)
32 |
33 | val adapter = mock()
34 | whenever(adapter.brickDataManager).thenReturn(brickDataManager)
35 | whenever(parent.adapter).thenReturn(adapter)
36 |
37 | itemDecoration = BrickRecyclerItemDecoration(brickDataManager)
38 |
39 | val brickPadding = mock()
40 | whenever(brickPadding.innerLeftPadding).thenReturn(INNER_LEFT)
41 | whenever(brickPadding.innerTopPadding).thenReturn(INNER_TOP)
42 | whenever(brickPadding.innerRightPadding).thenReturn(INNER_RIGHT)
43 | whenever(brickPadding.innerBottomPadding).thenReturn(INNER_BOTTOM)
44 | whenever(brickPadding.outerLeftPadding).thenReturn(OUTER_LEFT)
45 | whenever(brickPadding.outerTopPadding).thenReturn(OUTER_TOP)
46 | whenever(brickPadding.outerRightPadding).thenReturn(OUTER_RIGHT)
47 | whenever(brickPadding.outerBottomPadding).thenReturn(OUTER_BOTTOM)
48 | whenever(brick.padding).thenReturn(brickPadding)
49 | whenever(parent.getChildAdapterPosition(view)).thenReturn(0)
50 | bricks.add(brick)
51 | }
52 |
53 | @Test
54 | fun testInvalidNullItem() {
55 | bricks.clear()
56 |
57 | itemDecoration.getItemOffsets(outRect, view, parent, state)
58 |
59 | assertEquals(0, outRect.left.toLong())
60 | assertEquals(0, outRect.top.toLong())
61 | assertEquals(0, outRect.right.toLong())
62 | assertEquals(0, outRect.bottom.toLong())
63 | }
64 |
65 | @Test
66 | fun testInvalidWrongDataManager() {
67 | val adapter = mock()
68 | whenever(parent.adapter).thenReturn(adapter)
69 | whenever(adapter.brickDataManager).thenReturn(mock())
70 |
71 | itemDecoration.getItemOffsets(outRect, view, parent, state)
72 |
73 | assertEquals(0, outRect.left.toLong())
74 | assertEquals(0, outRect.top.toLong())
75 | assertEquals(0, outRect.right.toLong())
76 | assertEquals(0, outRect.bottom.toLong())
77 | }
78 |
79 | @Test
80 | fun testInvalidWrongAdapterType() {
81 | whenever(parent.adapter).thenReturn(mock())
82 |
83 | itemDecoration.getItemOffsets(outRect, view, parent, state)
84 |
85 | assertEquals(0, outRect.left.toLong())
86 | assertEquals(0, outRect.top.toLong())
87 | assertEquals(0, outRect.right.toLong())
88 | assertEquals(0, outRect.bottom.toLong())
89 | }
90 |
91 | @Test
92 | fun testAllOuter() {
93 | whenever(brick.isInFirstRow).thenReturn(true)
94 | whenever(brick.isInLastRow).thenReturn(true)
95 | whenever(brick.isOnLeftWall).thenReturn(true)
96 | whenever(brick.isOnRightWall).thenReturn(true)
97 |
98 | itemDecoration.getItemOffsets(outRect, view, parent, state)
99 |
100 | assertEquals(OUTER_LEFT.toLong(), outRect.left.toLong())
101 | assertEquals(OUTER_TOP.toLong(), outRect.top.toLong())
102 | assertEquals(OUTER_RIGHT.toLong(), outRect.right.toLong())
103 | assertEquals(OUTER_BOTTOM.toLong(), outRect.bottom.toLong())
104 | }
105 |
106 | @Test
107 | fun testAllInner() {
108 | whenever(brick.isInFirstRow).thenReturn(false)
109 | whenever(brick.isInLastRow).thenReturn(false)
110 | whenever(brick.isOnLeftWall).thenReturn(false)
111 | whenever(brick.isOnRightWall).thenReturn(false)
112 |
113 | itemDecoration.getItemOffsets(outRect, view, parent, state)
114 |
115 | assertEquals(INNER_LEFT.toLong(), outRect.left.toLong())
116 | assertEquals(INNER_TOP.toLong(), outRect.top.toLong())
117 | assertEquals(INNER_RIGHT.toLong(), outRect.right.toLong())
118 | assertEquals(INNER_BOTTOM.toLong(), outRect.bottom.toLong())
119 | }
120 |
121 | companion object {
122 | private const val INNER_LEFT = 1
123 | private const val INNER_TOP = 2
124 | private const val INNER_RIGHT = 3
125 | private const val INNER_BOTTOM = 4
126 | private const val OUTER_LEFT = 5
127 | private const val OUTER_TOP = 6
128 | private const val OUTER_RIGHT = 7
129 | private const val OUTER_BOTTOM = 8
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/BrickKit/scripts/publish-mavencentral.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | task androidSourcesJar(type: Jar) {
5 | archiveClassifier.set('sources')
6 | if (project.plugins.findPlugin("com.android.library")) {
7 | from android.sourceSets.main.java.srcDirs
8 | from android.sourceSets.main.kotlin.srcDirs
9 | }
10 | }
11 |
12 | artifacts {
13 | archives androidSourcesJar
14 | }
15 |
16 | group = PUBLISH_GROUP_ID
17 | version = PUBLISH_VERSION
18 |
19 | ext["signing.keyId"] = ''
20 | ext["signing.password"] = ''
21 | ext["signing.secretKeyRingFile"] = ''
22 | ext["ossrhUsername"] = ''
23 | ext["ossrhPassword"] = ''
24 | ext["sonatypeStagingProfileId"] = ''
25 |
26 | File secretPropsFile = project.rootProject.file('local.properties')
27 | if (secretPropsFile.exists()) {
28 | Properties p = new Properties()
29 | p.load(new FileInputStream(secretPropsFile))
30 | p.each { name, value ->
31 | ext[name] = value
32 | }
33 | } else {
34 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID')
35 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD')
36 | ext["signing.secretKeyRingFile"] = System.getenv('SIGNING_SECRET_KEY_RING_FILE')
37 | ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME')
38 | ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD')
39 | ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID')
40 | }
41 |
42 | afterEvaluate {
43 | publishing {
44 | publications {
45 | release(MavenPublication) {
46 | groupId PUBLISH_GROUP_ID
47 | artifactId PUBLISH_ARTIFACT_ID
48 | version PUBLISH_VERSION
49 | if (project.plugins.findPlugin("com.android.library")) {
50 | from components.release
51 | } else {
52 | artifact("$buildDir/libs/${project.getName()}-${version}.jar")
53 | }
54 |
55 | artifact androidSourcesJar
56 |
57 | pom {
58 | name = PUBLISH_ARTIFACT_ID
59 | description = 'Brickkit-Android RecyclerView based Layout Manager'
60 | url = 'https://github.com/wayfair/brickkit-android'
61 | licenses {
62 | license {
63 | name = 'License'
64 | url = 'https://github.com/wayfair/brickkit-android/blob/main/LICENSE'
65 | }
66 | }
67 | developers {
68 | developer {
69 | id = 'thusson13'
70 | name = 'Tom Husson'
71 | email = 'thusson@wayfair.com'
72 | }
73 | developer {
74 | id = 'Greg-at-Wayfair'
75 | name = 'Gregory Rasmussen'
76 | email = 'grasmussen@wayfair.com'
77 | }
78 | developer {
79 | id = 'rnorvielwf'
80 | name = 'Randall Norviel'
81 | email = 'rnorviel@wayfair.com'
82 | }
83 | developer {
84 | id = 'kenyee'
85 | name = 'Ken Yee'
86 | email = 'kyee@wayfair.com'
87 | }
88 | developer {
89 | id = 'mindwalkr'
90 | name = 'Richard Steventon'
91 | email = 'rsteventon1@wayfair.com'
92 | }
93 | developer {
94 | id = 'patbeagan1'
95 | name = 'Patrick Beagan'
96 | email = 'pbeagan@wayfair.com'
97 | }
98 | developer {
99 | id = 'sergenes'
100 | name = 'Sergey Nes'
101 | email = 'serge.nes@gmail.com'
102 | }
103 | developer {
104 | id = 'sneskoromny'
105 | name = 'Sergey Nes'
106 | email = 'sneskoromny@wayfair.com'
107 | }
108 | }
109 | scm {
110 | connection = 'scm:git:github.com/wayfair/brickkit-android.git'
111 | developerConnection = 'scm:git:ssh://github.com/wayfair/brickkit-android.git'
112 | url = 'https://github.com/wayfair/brickkit-android.git/tree/main'
113 | }
114 | }
115 | }
116 | }
117 | repositories {
118 | maven {
119 | name = "sonatype"
120 |
121 | def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
122 | def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
123 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
124 |
125 | credentials {
126 | username ossrhUsername
127 | password ossrhPassword
128 | }
129 | }
130 | }
131 | }
132 | }
133 |
134 | nexusStaging {
135 | packageGroup = PUBLISH_GROUP_ID
136 | stagingProfileId = sonatypeStagingProfileId
137 | username = ossrhUsername
138 | password = ossrhPassword
139 | }
140 |
141 | signing {
142 | sign publishing.publications
143 | }
--------------------------------------------------------------------------------
/BrickKit/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/ViewModelBrick.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit.brick
2 |
3 | import android.util.SparseArray
4 | import android.view.View
5 | import androidx.annotation.LayoutRes
6 | import androidx.core.util.isNotEmpty
7 | import androidx.databinding.DataBindingUtil
8 | import androidx.databinding.ViewDataBinding
9 | import com.wayfair.brickkit.padding.BrickPadding
10 | import com.wayfair.brickkit.padding.ZeroBrickPadding
11 | import com.wayfair.brickkit.size.BrickSize
12 | import com.wayfair.brickkit.size.FullWidthBrickSize
13 | import com.wayfair.brickkit.viewholder.BrickViewHolder
14 |
15 | /**
16 | * This class is used as a Generic Brick that can take in any XML Layout and use DataBinding to
17 | * insert information from a [ViewModel].
18 | */
19 | class ViewModelBrick private constructor(
20 | override val layout: Int,
21 | override val placeholderLayout: Int,
22 | val viewModels: SparseArray>,
23 | spanSize: BrickSize,
24 | padding: BrickPadding
25 | ) : BaseBrick(spanSize, padding), ViewModel.ViewModelUpdateListener {
26 |
27 | init {
28 | (0 until viewModels.size()).forEach { i -> viewModels.valueAt(i).addUpdateListener(this) }
29 | }
30 |
31 | /**
32 | * Gets the appropriate [ViewModel] for the given binding id.
33 | *
34 | * @param bindId the binding id
35 | * @return a [ViewModel] for the binding id
36 | */
37 | fun getViewModel(bindId: Int): ViewModel<*>? = viewModels[bindId]
38 |
39 | /**
40 | * Add a view model to the Brick.
41 | *
42 | * @param bindingId the binding ID of the view model
43 | * @param viewModel the view model
44 | */
45 | fun addViewModel(bindingId: Int, viewModel: ViewModel<*>) {
46 | viewModel.addUpdateListener(this)
47 | viewModels.put(bindingId, viewModel)
48 | onChange()
49 | }
50 |
51 | /**
52 | * {@inheritDoc}
53 | */
54 | override fun onBindData(holder: BrickViewHolder) {
55 | val binding = (holder as ViewModelBrickViewHolder).viewDataBinding
56 |
57 | (0 until viewModels.size()).forEach { i ->
58 | binding.setVariable(viewModels.keyAt(i), viewModels.valueAt(i))
59 | }
60 |
61 | binding.executePendingBindings()
62 | }
63 |
64 | /**
65 | * {@inheritDoc}
66 | */
67 | override fun createViewHolder(itemView: View): BrickViewHolder = ViewModelBrickViewHolder(DataBindingUtil.bind(itemView)!!)
68 |
69 | /**
70 | * {@inheritDoc}
71 | */
72 | override fun onChange() {
73 | isHidden = !isDataReady
74 | refreshItem()
75 | }
76 |
77 | /**
78 | * {@inheritDoc}
79 | */
80 | override val isDataReady: Boolean
81 | get() {
82 | var isDataReady = viewModels.isNotEmpty()
83 |
84 | var i = 0
85 | while (isDataReady && i < viewModels.size()) {
86 | isDataReady = viewModels.valueAt(i++).isDataModelReady
87 | }
88 |
89 | return isDataReady
90 | }
91 |
92 | override fun hashCode(): Int = super.hashCode()
93 |
94 | override fun equals(other: Any?): Boolean {
95 | var areContentsTheSame = true
96 | if (other is ViewModelBrick) {
97 | (0 until viewModels.size()).forEach { i ->
98 | (0 until other.viewModels.size()).forEach { j ->
99 | if (viewModels.keyAt(i) == other.viewModels.keyAt(j) && viewModels.valueAt(i) != other.viewModels.valueAt(j)) {
100 | areContentsTheSame = false
101 | }
102 | }
103 | }
104 | } else {
105 | areContentsTheSame = false
106 | }
107 | return areContentsTheSame
108 | }
109 |
110 | /**
111 | * A builder class for [ViewModelBrick], this makes it clearer what is required and what you are actually doing when creating
112 | * [ViewModelBrick]s.
113 | */
114 | class Builder(@LayoutRes private val layoutId: Int) {
115 | private var placeholderLayoutId = 0
116 | private var viewModels = SparseArray>()
117 | private var spanSize: BrickSize = FullWidthBrickSize()
118 | private var padding: BrickPadding = ZeroBrickPadding()
119 |
120 | /**
121 | * Set the placeholder for this brick.
122 | *
123 | * @param placeholderLayoutId the placeholder layout id to be used
124 | * @return the builder
125 | */
126 | fun setPlaceholder(@LayoutRes placeholderLayoutId: Int): Builder {
127 | this.placeholderLayoutId = placeholderLayoutId
128 | return this
129 | }
130 |
131 | /**
132 | * Add a [ViewModel] with a binding Id for the layout already defined.
133 | *
134 | * @param bindingId the binding Id of the view model
135 | * @param viewModel the view model to be bound, extends [ViewModel]
136 | * @return the builder
137 | */
138 | fun addViewModel(bindingId: Int, viewModel: ViewModel<*>?): Builder {
139 | if (viewModel != null) {
140 | viewModels.put(bindingId, viewModel)
141 | }
142 | return this
143 | }
144 |
145 | /**
146 | * Set the [BrickSize].
147 | *
148 | * @param spanSize the [BrickSize]
149 | * @return the builder
150 | */
151 | fun setSpanSize(spanSize: BrickSize): Builder {
152 | this.spanSize = spanSize
153 | return this
154 | }
155 |
156 | /**
157 | * Set the [BrickPadding].
158 | *
159 | * @param padding the [BrickPadding]
160 | * @return the builder
161 | */
162 | fun setPadding(padding: BrickPadding): Builder {
163 | this.padding = padding
164 | return this
165 | }
166 |
167 | /**
168 | * Assemble the [ViewModelBrick].
169 | *
170 | * @return the complete [ViewModelBrick]
171 | */
172 | fun build(): ViewModelBrick = ViewModelBrick(layoutId, placeholderLayoutId, viewModels, spanSize, padding)
173 | }
174 |
175 | /**
176 | * A special [BrickViewHolder] that can handle binding [ViewModel]s to layouts.
177 | */
178 | private class ViewModelBrickViewHolder(val viewDataBinding: ViewDataBinding) : BrickViewHolder(viewDataBinding.root)
179 | }
180 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | [](https://codecov.io/gh/wayfair/brickkit-android)
3 | [  ](https://maven-badges.herokuapp.com/maven-central/com.wayfair/brickkit-android)
4 |
5 | ## What is BrickKit
6 |
7 | With BrickKit, you can manage complex, dynamic, device type specific, and orientation aware layouts with the same code. It's easy to reuse and extend bricks which highly reduces code redundancy and makes UI testing easier. BrickKit is based on RecyclerView meaning it works well with many (think thousands) items inside. It allows you to take advantage of the performance built into RecyclerView without needing to maintain your own RecyclerView.Adapter, instead letting you focus on other things.
8 |
9 |
10 | ## How to import BrickKit as a library
11 |
12 | The latest version is available on the Maven Central.
13 | Add a MavenCentral to your repositories
14 | ```
15 | mavenCentral()
16 | ```
17 | And add it as a Gradle implementation dependency [Latest](https://maven-badges.herokuapp.com/maven-central/com.wayfair/brickkit-android)
18 | ```
19 | implementation "com.wayfair:brickkit-android:1.0.1"
20 | ```
21 |
22 | ## Features of BrickKit
23 |
24 | ### Setting up BrickKit in your screen
25 |
26 | Getting started with BrickKit is as simple as creating a BrickDataManager and setting the RecyclerView you want it to work on. After that you can start adding Bricks to it).
27 |
28 | ```kotlin
29 | open class BrickFragment : Fragment() {
30 | // Create your BrickDataManager
31 | private val dataManager = BrickDataManager()
32 |
33 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
34 | return inflater.inflate(R.layout.vertical_fragment_brick, container, false).apply {
35 | findViewById(R.id.recycler_view).apply {
36 | // Set the RecyclerView you want to use
37 | dataManager.setRecyeclerView(this)
38 | }
39 | }
40 | }
41 |
42 | override fun onResume() {
43 | super.onResume()
44 |
45 | // Have your feature build up your UI
46 | val bricks = (0 until 100).map { i -> TextBrick(HalfWidthBrickSize(), BrickPaddingFactory(resources), "Brick: $i") }.toList()
47 |
48 | // Update BrickDataManager with your Bricks
49 | dataManager.updateBricks(bricks)
50 | }
51 |
52 | override fun onDestroyView() {
53 | // Clean up references when your view is destroyed
54 | dataManager.onDestroyView()
55 | super.onDestroyView()
56 | }
57 | }
58 | ```
59 |
60 | ## Manage your bricks with BrickDataManager
61 |
62 | The 'BrickDataManager' manages the RecyclerView's adapter and manipulates the bricks. The preferred usage of BrickDataManager is with `updateBricks`, which is built off of DiffUtils and allows you to build your UI in one go (either when building your initial UI or when updating it when new elements should be added or removed).
63 |
64 | | Methods Used Frequently | Description |
65 | |----------|:-------------:|
66 | | setRecyclerView | Sets the RecyclerView that BrickDataManager will operate on |
67 | | setHorizontalRecyclerView | Same as setRecyclerView but for use with horizontally scrolling RecyclerViews |
68 | | updateBricks | Will replace the current UI with the new set of bricks (built using DiffUtils) |
69 | | addLast | Inserts brick/Collection of bricks after all other bricks. |
70 | | addFirst | Inserts brick before all other bricks. |
71 | | isEmpty | Returns whether or not the BrickDataManager has had any items added |
72 | | setOnReachedItemAtPosition | Sets a listener that will provide binding location updates (useful when implementing infinite scroll).
73 |
74 |
75 | ### Bricks with different spans and padding
76 |
77 | Building layouts differently for different screen orientations and device type is easy and handled by BrickKit. Simply pass one of the in one of the various `BrickSize` subclasses into the brick you are creating. There are a number of `BrickSize` subclasses that provide either a consistent size between phone and tablets as well as others like `HalfPhoneQuarterTabletBrickSize` which will provide different sizes based on device type. Brickkit provides the ability to intelligently add padding to your items so you don't need to worry about where they end up on screen. Using `BrickPaddingFactory` you just need to pass in the dimen resources you want to apply and the reset will be taken care of for you.
78 |
79 | 
80 |
81 | ```kotlin
82 | override fun onCreateView(savedInstanceState: Bundle?) {
83 | (0 until 2).forEach { i ->
84 | TextBrick(
85 | HalfWidthBrickSize(),
86 | brickPaddingFactory.getInnerOuterPadding(R.dimen.inner_padding, R.dimen.outer_padding),
87 | "Brick: $i"
88 | ).addLastTo(dataManager)
89 | }
90 | (0 until 3).forEach { i ->
91 | TextBrick(
92 | ThirdWidthBrickSize(),
93 | brickPaddingFactory.getInnerOuterPadding(R.dimen.inner_padding, R.dimen.outer_padding),
94 | "Brick: $i"
95 | ).addLastTo(dataManager)
96 | }
97 | (0 until 4).forEach { i ->
98 | TextBrick(
99 | QuarterWidthBrickSize(),
100 | brickPaddingFactory.getInnerOuterPadding(R.dimen.inner_padding, R.dimen.outer_padding),
101 | "Brick: $i"
102 | ).addLastTo(dataManager)
103 | }
104 | }
105 | ```
106 |
107 | | BrickPaddingFactory Methods | Description |
108 | |----------|:----------:|
109 | | getSimpleBrickPadding | Applies the same padding to all sides of the brick |
110 | | getInnerOuterBrickPadding | Applies outer padding to brick sides that touch the edges of the recycler view, inner padding to all inner sides. This is helpful when trying to avoid "double" padding between items |
111 | | getRectBrickPadding | Applies different paddings to each side |
112 | | getInnerOuterRectBrickPadding | Combines the concepts of `getInnerOuterBrickPadding` and `getRectBrickPadding` |
113 | | getViewInsetPadding | Similar to getInnerOuterRectBrickPadding but with the outer padding set to 16dp |
114 |
115 |
116 | ## How to run BrickKit demo project
117 |
118 | ```
119 | 1. git clone 'git@github.com:wayfair/brickkit-android.git'
120 | 2. Open Android Studio -> New -> Import Project (with the BrickKit folder not the base)
121 | 3. Run BrickKit
122 | ```
123 |
124 | ## Credits
125 |
126 | BrickKit is owned and maintained by [Wayfair](https://www.wayfair.com).
127 |
128 | ## Contributing
129 |
130 | See [CONTRIBUTING.md](CONTRIBUTING.md).
131 |
132 |
133 | ## License
134 |
135 | BrickKit is released under the Apache license. See [LICENSE](LICENSE) for details.
136 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/padding/BrickPaddingFactoryTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2020 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit.padding
5 |
6 | import android.content.res.Resources
7 | import org.mockito.kotlin.mock
8 | import org.mockito.kotlin.whenever
9 | import com.wayfair.brickkit.R
10 | import org.junit.Assert.assertEquals
11 | import org.junit.Before
12 | import org.junit.Test
13 |
14 | class BrickPaddingFactoryTest {
15 | private lateinit var brickPaddingFactory: BrickPaddingFactory
16 |
17 | @Before
18 | fun setup() {
19 | val resources = mock()
20 | whenever(resources.getDimension(R.dimen.no_dp)).thenReturn(0f)
21 | whenever(resources.getDimension(ONE_DP)).thenReturn(1f)
22 | whenever(resources.getDimension(TWO_DP)).thenReturn(2f)
23 | whenever(resources.getDimension(THREE_DP)).thenReturn(3f)
24 | whenever(resources.getDimension(FOUR_DP)).thenReturn(4f)
25 | whenever(resources.getDimension(FIVE_DP)).thenReturn(5f)
26 | whenever(resources.getDimension(SIX_DP)).thenReturn(6f)
27 | whenever(resources.getDimension(SEVEN_DP)).thenReturn(7f)
28 | whenever(resources.getDimension(EIGHT_DP)).thenReturn(8f)
29 | whenever(resources.getDimension(R.dimen.standard_margin_named_default_half)).thenReturn(11f)
30 | whenever(resources.getDimension(R.dimen.standard_margin_named_view_inset)).thenReturn(12f)
31 | brickPaddingFactory = BrickPaddingFactory(resources)
32 | }
33 |
34 | @Test
35 | fun testGetSimpleBrickPadding() {
36 | brickPaddingFactory.getSimpleBrickPadding(ONE_DP).apply {
37 | assertEquals(1, innerLeftPadding)
38 | assertEquals(1, innerTopPadding)
39 | assertEquals(1, innerRightPadding)
40 | assertEquals(1, innerBottomPadding)
41 | assertEquals(1, outerLeftPadding)
42 | assertEquals(1, outerTopPadding)
43 | assertEquals(1, outerRightPadding)
44 | assertEquals(1, outerBottomPadding)
45 | }
46 | }
47 |
48 | @Test
49 | fun testGetInnerOuterBrickPadding_defaults() {
50 | brickPaddingFactory.getInnerOuterBrickPadding().apply {
51 | assertEquals(0, innerLeftPadding)
52 | assertEquals(0, innerTopPadding)
53 | assertEquals(0, innerRightPadding)
54 | assertEquals(0, innerBottomPadding)
55 | assertEquals(0, outerLeftPadding)
56 | assertEquals(0, outerTopPadding)
57 | assertEquals(0, outerRightPadding)
58 | assertEquals(0, outerBottomPadding)
59 | }
60 | }
61 |
62 | @Test
63 | fun testGetInnerOuterBrickPadding() {
64 | brickPaddingFactory.getInnerOuterBrickPadding(ONE_DP, TWO_DP).apply {
65 | assertEquals(1, innerLeftPadding)
66 | assertEquals(1, innerTopPadding)
67 | assertEquals(1, innerRightPadding)
68 | assertEquals(1, innerBottomPadding)
69 | assertEquals(2, outerLeftPadding)
70 | assertEquals(2, outerTopPadding)
71 | assertEquals(2, outerRightPadding)
72 | assertEquals(2, outerBottomPadding)
73 | }
74 | }
75 |
76 | @Test
77 | fun testGetRectBrickPadding_defaults() {
78 | brickPaddingFactory.getRectBrickPadding().apply {
79 | assertEquals(0, innerLeftPadding)
80 | assertEquals(0, innerTopPadding)
81 | assertEquals(0, innerRightPadding)
82 | assertEquals(0, innerBottomPadding)
83 | assertEquals(0, outerLeftPadding)
84 | assertEquals(0, outerTopPadding)
85 | assertEquals(0, outerRightPadding)
86 | assertEquals(0, outerBottomPadding)
87 | }
88 | }
89 |
90 | @Test
91 | fun testGetRectBrickPaddinh() {
92 | brickPaddingFactory.getRectBrickPadding(ONE_DP, TWO_DP, THREE_DP, FOUR_DP).apply {
93 | assertEquals(1, innerLeftPadding)
94 | assertEquals(2, innerTopPadding)
95 | assertEquals(3, innerRightPadding)
96 | assertEquals(4, innerBottomPadding)
97 | assertEquals(1, outerLeftPadding)
98 | assertEquals(2, outerTopPadding)
99 | assertEquals(3, outerRightPadding)
100 | assertEquals(4, outerBottomPadding)
101 | }
102 | }
103 |
104 | @Test
105 | fun testGetInnerOuterRectBrickPadding_defaults() {
106 | brickPaddingFactory.getInnerOuterRectBrickPadding().apply {
107 | assertEquals(0, innerLeftPadding)
108 | assertEquals(0, innerTopPadding)
109 | assertEquals(0, innerRightPadding)
110 | assertEquals(0, innerBottomPadding)
111 | assertEquals(0, outerLeftPadding)
112 | assertEquals(0, outerTopPadding)
113 | assertEquals(0, outerRightPadding)
114 | assertEquals(0, outerBottomPadding)
115 | }
116 | }
117 |
118 | @Test
119 | fun testGetInnerOuterRectBrickPadding() {
120 | brickPaddingFactory.getInnerOuterRectBrickPadding(
121 | ONE_DP,
122 | TWO_DP,
123 | THREE_DP,
124 | FOUR_DP,
125 | FIVE_DP,
126 | SIX_DP,
127 | SEVEN_DP,
128 | EIGHT_DP
129 | ).apply {
130 | assertEquals(1, innerLeftPadding)
131 | assertEquals(2, innerTopPadding)
132 | assertEquals(3, innerRightPadding)
133 | assertEquals(4, innerBottomPadding)
134 | assertEquals(5, outerLeftPadding)
135 | assertEquals(6, outerTopPadding)
136 | assertEquals(7, outerRightPadding)
137 | assertEquals(8, outerBottomPadding)
138 | }
139 | }
140 |
141 | @Test
142 | fun testViewInsetBrickPadding() {
143 | brickPaddingFactory.getViewInsetPadding(
144 | ONE_DP,
145 | TWO_DP,
146 | THREE_DP,
147 | FOUR_DP
148 | ).apply {
149 | assertEquals(1, innerLeftPadding)
150 | assertEquals(2, innerTopPadding)
151 | assertEquals(3, innerRightPadding)
152 | assertEquals(4, innerBottomPadding)
153 | assertEquals(12, outerLeftPadding)
154 | assertEquals(12, outerTopPadding)
155 | assertEquals(12, outerRightPadding)
156 | assertEquals(12, outerBottomPadding)
157 | }
158 | }
159 |
160 | @Test
161 | fun testViewInsetBrickPaddingDefault() {
162 | brickPaddingFactory.getViewInsetPadding().apply {
163 | assertEquals(11, innerLeftPadding)
164 | assertEquals(11, innerTopPadding)
165 | assertEquals(11, innerRightPadding)
166 | assertEquals(11, innerBottomPadding)
167 | assertEquals(12, outerLeftPadding)
168 | assertEquals(12, outerTopPadding)
169 | assertEquals(12, outerRightPadding)
170 | assertEquals(12, outerBottomPadding)
171 | }
172 | }
173 |
174 | companion object {
175 | private const val ONE_DP = 1111
176 | private const val TWO_DP = 2222
177 | private const val THREE_DP = 3333
178 | private const val FOUR_DP = 4444
179 | private const val FIVE_DP = 5555
180 | private const val SIX_DP = 6666
181 | private const val SEVEN_DP = 7777
182 | private const val EIGHT_DP = 8888
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/main/java/com/wayfair/brickkit/BrickRecyclerAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2017-2021 Wayfair. All rights reserved.
3 | */
4 | package com.wayfair.brickkit
5 |
6 | import android.annotation.SuppressLint
7 | import android.os.Handler
8 | import android.util.Log
9 | import android.view.ViewGroup
10 | import androidx.recyclerview.widget.RecyclerView
11 | import androidx.recyclerview.widget.StaggeredGridLayoutManager
12 | import com.wayfair.brickkit.brick.BaseBrick
13 | import com.wayfair.brickkit.viewholder.BrickViewHolder
14 | import com.wayfair.brickkit.viewholder.factory.BrickViewHolderFactory
15 |
16 | /**
17 | * Extension of [androidx.recyclerview.widget.RecyclerView.Adapter] which combines a given
18 | * [BrickDataManager] to a given [RecyclerView].
19 | */
20 | @OpenForTesting
21 | internal class BrickRecyclerAdapter(
22 | val brickDataManager: BrickDataManager,
23 | private val recyclerView: RecyclerView
24 | ) : RecyclerView.Adapter() {
25 |
26 | private val handler: Handler = Handler()
27 | private var onReachedItemAtPosition: OnReachedItemAtPosition? = null
28 |
29 | /**
30 | * Safe version of [RecyclerView.Adapter.notifyDataSetChanged].
31 | */
32 | @SuppressLint("NotifyDataSetChanged")
33 | fun safeNotifyDataSetChanged() {
34 | runWhenNotComputingLayout { notifyDataSetChanged() }
35 | }
36 |
37 | /**
38 | * Safe version of [RecyclerView.Adapter.notifyItemChanged].
39 | *
40 | * @param position Position of the item that has changed
41 | * @param payload Optional parameter, use null to identify a "full" update
42 | */
43 | fun safeNotifyItemChanged(position: Int, payload: Any?) {
44 | if (position >= 0) {
45 | runWhenNotComputingLayout { notifyItemChanged(position, payload) }
46 | } else {
47 | Log.w(TAG, "safeNotifyItemChanged: position is negative")
48 | }
49 | }
50 |
51 | /**
52 | * Safe version of [RecyclerView.Adapter.notifyItemChanged].
53 | *
54 | * @param position Position of the item that has changed
55 | */
56 | fun safeNotifyItemChanged(position: Int) {
57 | if (position >= 0) {
58 | runWhenNotComputingLayout { notifyItemChanged(position) }
59 | } else {
60 | Log.w(TAG, "safeNotifyItemChanged: position is negative")
61 | }
62 | }
63 |
64 | /**
65 | * Safe version of [RecyclerView.Adapter.notifyItemInserted].
66 | *
67 | * @param position Position of the newly inserted item in the data set
68 | */
69 | fun safeNotifyItemInserted(position: Int) {
70 | if (position >= 0) {
71 | runWhenNotComputingLayout { notifyItemInserted(position) }
72 | } else {
73 | Log.w(TAG, "safeNotifyItemInserted: position is negative")
74 | }
75 | }
76 |
77 | /**
78 | * Safe version of [RecyclerView.Adapter.notifyItemMoved].
79 | *
80 | * @param fromPosition Previous position of the item.
81 | * @param toPosition New position of the item.
82 | */
83 | fun safeNotifyItemMoved(fromPosition: Int, toPosition: Int) {
84 | if (fromPosition >= 0 && toPosition >= 0) {
85 | runWhenNotComputingLayout { notifyItemMoved(fromPosition, toPosition) }
86 | } else {
87 | Log.w(TAG, "safeNotifyItemRangeChanged: fromPosition / toPosition is/are negative")
88 | }
89 | }
90 |
91 | /**
92 | * Safe version of [RecyclerView.Adapter.notifyItemRangeChanged].
93 | *
94 | * @param positionStart Position of the first item that has changed
95 | * @param itemCount Number of items that have changed
96 | * @param payload Optional parameter, use null to identify a "full" update
97 | */
98 | fun safeNotifyItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
99 | if (positionStart >= 0 && itemCount >= 0) {
100 | runWhenNotComputingLayout { notifyItemRangeChanged(positionStart, itemCount, payload) }
101 | } else {
102 | Log.w(TAG, "safeNotifyItemRangeChanged: positionStart / itemStart is/are negative")
103 | }
104 | }
105 |
106 | /**
107 | * Safe version of [RecyclerView.Adapter.notifyItemRangeChanged].
108 | *
109 | * @param positionStart Position of the first item that has changed
110 | * @param itemCount Number of items that have changed
111 | */
112 | fun safeNotifyItemRangeChanged(positionStart: Int, itemCount: Int) {
113 | if (positionStart >= 0 && itemCount >= 0) {
114 | runWhenNotComputingLayout { notifyItemRangeChanged(positionStart, itemCount) }
115 | } else {
116 | Log.w(TAG, "safeNotifyItemRangeChanged: positionStart / itemStart is/are negative")
117 | }
118 | }
119 |
120 | /**
121 | * Safe version of [RecyclerView.Adapter.notifyItemRangeInserted].
122 | *
123 | * @param positionStart Position of the first item that was inserted
124 | * @param itemCount Number of items inserted
125 | */
126 | fun safeNotifyItemRangeInserted(positionStart: Int, itemCount: Int) {
127 | if (positionStart >= 0 && itemCount >= 0) {
128 | runWhenNotComputingLayout { notifyItemRangeInserted(positionStart, itemCount) }
129 | } else {
130 | Log.w(TAG, "safeNotifyItemRangeInserted: positionStart / itemStart is/are negative")
131 | }
132 | }
133 |
134 | /**
135 | * Safe version of [RecyclerView.Adapter.notifyItemRangeRemoved].
136 | *
137 | * @param positionStart Previous position of the first item that was removed
138 | * @param itemCount Number of items removed from the data set
139 | */
140 | fun safeNotifyItemRangeRemoved(positionStart: Int, itemCount: Int) {
141 | if (positionStart >= 0 && itemCount >= 0) {
142 | runWhenNotComputingLayout { notifyItemRangeRemoved(positionStart, itemCount) }
143 | } else {
144 | Log.w(TAG, "safeNotifyItemRangeRemoved: positionStart / itemStart is/are negative")
145 | }
146 | }
147 |
148 | /**
149 | * Safe version of [RecyclerView.Adapter.notifyItemRemoved].
150 | *
151 | * @param position Position of the item that has now been removed
152 | */
153 | fun safeNotifyItemRemoved(position: Int) {
154 | if (position >= 0) {
155 | runWhenNotComputingLayout { notifyItemRemoved(position) }
156 | } else {
157 | Log.w(TAG, "safeNotifyItemRemoved: positionStart / itemStart is/are negative")
158 | }
159 | }
160 |
161 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BrickViewHolder =
162 | BrickViewHolderFactory().createBrickViewHolder(parent, viewType, brickDataManager.recyclerViewItems)
163 |
164 | override fun onBindViewHolder(holder: BrickViewHolder, position: Int) {
165 | brickDataManager.recyclerViewItems.getOrNull(position)?.let { baseBrick ->
166 | (holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams)?.isFullSpan =
167 | baseBrick.spanSize.getSpans(holder.itemView.context) == BrickDataManager.SPAN_COUNT
168 |
169 | if (baseBrick.isDataReady) {
170 | baseBrick.onBindData(holder)
171 | }
172 | onReachedItemAtPosition?.bindingItemAtPosition(position)
173 | }
174 | }
175 |
176 | override fun onViewAttachedToWindow(holder: BrickViewHolder) = holder.onViewAttachedToWindow()
177 |
178 | override fun onViewDetachedFromWindow(holder: BrickViewHolder) = holder.releaseViewsOnDetach()
179 |
180 | override fun getItemCount(): Int = brickDataManager.recyclerViewItems.size
181 |
182 | override fun getItemViewType(position: Int): Int {
183 | val brick: BaseBrick? = brickDataManager.recyclerViewItems.getOrNull(position)
184 | return when {
185 | brick == null -> DEFAULT_LAYOUT_RES_ID
186 | brick.isDataReady -> brick.layout
187 | else -> brick.placeholderLayout
188 | }
189 | }
190 |
191 | /**
192 | * Set an [OnReachedItemAtPosition].
193 | *
194 | * @param onReachedItemAtPosition [OnReachedItemAtPosition] to set
195 | */
196 | fun setOnReachedItemAtPosition(onReachedItemAtPosition: OnReachedItemAtPosition?) {
197 | this.onReachedItemAtPosition = onReachedItemAtPosition
198 | }
199 |
200 | private fun runWhenNotComputingLayout(callToRun: RecyclerView.Adapter.() -> Unit) {
201 | if (recyclerView.isComputingLayout) {
202 | handler.post { callToRun.invoke(this) }
203 | } else {
204 | callToRun.invoke(this)
205 | }
206 | }
207 |
208 | companion object {
209 | private val TAG = BrickRecyclerAdapter::class.java.name
210 | const val DEFAULT_LAYOUT_RES_ID = 0
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/brick/ViewModelBrickTest.kt:
--------------------------------------------------------------------------------
1 | package com.wayfair.brickkit.brick
2 |
3 | import android.view.LayoutInflater
4 | import android.widget.LinearLayout
5 | import androidx.databinding.Bindable
6 | import androidx.test.core.app.ApplicationProvider
7 | import androidx.test.ext.junit.runners.AndroidJUnit4
8 | import org.mockito.kotlin.mock
9 | import org.mockito.kotlin.spy
10 | import org.mockito.kotlin.verify
11 | import com.wayfair.brickkit.OpenForTesting
12 | import com.wayfair.brickkit.padding.BrickPadding
13 | import com.wayfair.brickkit.size.BrickSize
14 | import com.wayfair.brickkit.test.BR
15 | import com.wayfair.brickkit.test.R
16 | import org.junit.Assert.assertEquals
17 | import org.junit.Assert.assertFalse
18 | import org.junit.Assert.assertNull
19 | import org.junit.Assert.assertTrue
20 | import org.junit.Test
21 | import org.junit.runner.RunWith
22 | import java.util.concurrent.CountDownLatch
23 |
24 | @RunWith(AndroidJUnit4::class)
25 | class ViewModelBrickTest {
26 |
27 | @Test
28 | fun testSingleViewModel_Test() {
29 | val textDataModel = TextDataModel(TEXT)
30 | val textViewModel = spy(TextViewModel(textDataModel))
31 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
32 | .addViewModel(BR.viewModel, textViewModel)
33 | .build()
34 |
35 | viewModelBrick.onBindData(
36 | viewModelBrick.createViewHolder(
37 | LayoutInflater.from(ApplicationProvider.getApplicationContext()).inflate(
38 | viewModelBrick.layout,
39 | LinearLayout(ApplicationProvider.getApplicationContext()),
40 | false
41 | )
42 | )
43 | )
44 |
45 | verify(viewModelBrick.getViewModel(BR.viewModel) as TextViewModel).text
46 | }
47 |
48 | @Test
49 | fun testEquals_dataModelsAreEqual() {
50 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
51 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel(TEXT)))
52 | .build()
53 |
54 | val viewModelBrick2 = ViewModelBrick.Builder(R.layout.text_brick_vm)
55 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel(TEXT)))
56 | .build()
57 |
58 | assertTrue(viewModelBrick == viewModelBrick2)
59 | }
60 |
61 | @Test
62 | fun testEquals_dataModelsAreNotEqual() {
63 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
64 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel(TEXT)))
65 | .build()
66 |
67 | val viewModelBrick2 = ViewModelBrick.Builder(R.layout.text_brick_vm)
68 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel(TEXT_2)))
69 | .build()
70 |
71 | assertFalse(viewModelBrick == viewModelBrick2)
72 | }
73 |
74 | @Test
75 | fun testEquals_dataModelsAreEqualDifferentOrder() {
76 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
77 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel(TEXT)))
78 | .addViewModel(BR.text, TextViewModel(TextDataModel(TEXT_2)))
79 | .build()
80 |
81 | val viewModelBrick2 = ViewModelBrick.Builder(R.layout.text_brick_vm)
82 | .addViewModel(BR.text, TextViewModel(TextDataModel(TEXT_2)))
83 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel(TEXT)))
84 | .build()
85 |
86 | assertTrue(viewModelBrick == viewModelBrick2)
87 | }
88 |
89 | @Test
90 | fun testEquals_viewModelsAreDifferentTypes() {
91 | assertFalse(ViewModelBrick.Builder(R.layout.text_brick_vm).build() == mock())
92 | }
93 |
94 | @Test
95 | fun testAddViewModel() {
96 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
97 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel(TEXT)))
98 | .build()
99 |
100 | viewModelBrick.addViewModel(BR.text, TextViewModel(TextDataModel(TEXT_2)))
101 |
102 | assertEquals(2, viewModelBrick.viewModels.size())
103 | }
104 |
105 | @Test
106 | fun testIsDataReady_emptyViewModels() {
107 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
108 | .build()
109 |
110 | assertFalse(viewModelBrick.isDataReady)
111 | }
112 |
113 | @Test
114 | fun testIsDataReady_firstNotReady() {
115 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
116 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel("")))
117 | .addViewModel(BR.text, TextViewModel(TextDataModel(TEXT)))
118 | .build()
119 |
120 | assertFalse(viewModelBrick.isDataReady)
121 | }
122 |
123 | @Test
124 | fun testIsDataReady_lastNotReady() {
125 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
126 | .addViewModel(BR.text, TextViewModel(TextDataModel(TEXT)))
127 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel("")))
128 | .build()
129 |
130 | assertFalse(viewModelBrick.isDataReady)
131 | }
132 |
133 | @Test
134 | fun testIsDataReady_ready() {
135 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
136 | .addViewModel(BR.text, TextViewModel(TextDataModel(TEXT)))
137 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel(TEXT)))
138 | .build()
139 |
140 | assertTrue(viewModelBrick.isDataReady)
141 | }
142 |
143 | @Test
144 | fun testBuilder_setPlaceholder() {
145 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
146 | .addViewModel(BR.viewModel, TextViewModel(TextDataModel("")))
147 | .setPlaceholder(R.layout.text_brick_vm_placeholder)
148 | .build()
149 |
150 | assertEquals(R.layout.text_brick_vm_placeholder, viewModelBrick.placeholderLayout)
151 | }
152 |
153 | @Test
154 | fun testBuilder_setPadding() {
155 | val padding = mock()
156 |
157 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
158 | .setPadding(padding)
159 | .build()
160 |
161 | assertEquals(padding, viewModelBrick.padding)
162 | }
163 |
164 | @Test
165 | fun testBuilder_setSpanSize() {
166 | val spanSize = mock()
167 |
168 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
169 | .setSpanSize(spanSize)
170 | .build()
171 |
172 | assertEquals(spanSize, viewModelBrick.spanSize)
173 | }
174 |
175 | @Test
176 | fun testBuilder_addViewModel() {
177 | val viewModel = mock>()
178 |
179 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
180 | .addViewModel(BR.viewModel, viewModel)
181 | .build()
182 |
183 | assertEquals(viewModel, viewModelBrick.viewModels[BR.viewModel])
184 | }
185 |
186 | @Test
187 | fun testBuilder_addNullViewModel() {
188 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
189 | .addViewModel(BR.viewModel, null)
190 | .build()
191 |
192 | assertNull(viewModelBrick.viewModels[BR.viewModel])
193 | }
194 |
195 | @Test
196 | fun testOnChange() {
197 | val dataModel = TextDataModel(TEXT)
198 | val viewModel = TextViewModel(dataModel)
199 |
200 | val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm)
201 | .addViewModel(BR.viewModel, viewModel)
202 | .setPlaceholder(R.layout.text_brick_vm_placeholder)
203 | .build()
204 |
205 | val countDownLatch = CountDownLatch(1)
206 | viewModel.addUpdateListener(
207 | object : ViewModel.ViewModelUpdateListener {
208 | override fun onChange() {
209 | countDownLatch.countDown()
210 | }
211 | }
212 | )
213 |
214 | assertFalse(viewModelBrick.isHidden)
215 |
216 | dataModel.text = ""
217 |
218 | countDownLatch.await()
219 |
220 | assertTrue(viewModelBrick.isHidden)
221 | }
222 |
223 | @OpenForTesting
224 | class TextViewModel(dataModel: TextDataModel) : ViewModel(dataModel) {
225 | @get:Bindable
226 | val text: String
227 | get() = dataModel.text
228 |
229 | override fun equals(other: Any?): Boolean = other is TextViewModel && dataModel.text == other.dataModel.text
230 |
231 | override fun hashCode(): Int = 1
232 | }
233 |
234 | class TextDataModel(initialText: String) : DataModel() {
235 |
236 | var text: String = initialText
237 | set(value) {
238 | field = value
239 | notifyChange()
240 | }
241 |
242 | override val isReady: Boolean
243 | get() = text.isNotEmpty()
244 | }
245 |
246 | companion object {
247 | private const val TEXT = "Test Text..."
248 | private const val TEXT_2 = "Not Test Text..."
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | **Merged pull requests:**
4 | - Code hardening with index out of bounds exceptions in BrickKitDataManager.
5 |
6 | ## [0.9.46](https://github.com/wayfair/brickkit-android/tree/0.9.46) (2020-01-21)
7 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.45...0.9.46)
8 |
9 | **Merged pull requests:**
10 | - Code hardening with index out of bounds exceptions in BrickKitDataManager.
11 |
12 | ## [0.9.45](https://github.com/wayfair/brickkit-android/tree/0.9.45) (2020-01-16)
13 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.44...0.9.45)
14 |
15 | **Merged pull requests:**
16 | ## [0.9.44](https://github.com/wayfair/brickkit-android/tree/0.9.44) (2020-01-02)
17 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.43...0.9.44)
18 |
19 | **Merged pull requests:**
20 |
21 | ## [0.9.12](https://github.com/wayfair/brickkit-android/tree/0.9.12) (2017-04-06)
22 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.11...0.9.12)
23 |
24 | **Merged pull requests:**
25 |
26 | - Fixing crash when staggeredgrid used with header [\#50](https://github.com/wayfair/brickkit-android/pull/50) ([patbeagan1](https://github.com/patbeagan1))
27 | - Release 0.9.11 [\#49](https://github.com/wayfair/brickkit-android/pull/49) ([thusson13](https://github.com/thusson13))
28 |
29 | ## [0.9.11](https://github.com/wayfair/brickkit-android/tree/0.9.11) (2017-04-05)
30 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.10...0.9.11)
31 |
32 | **Closed issues:**
33 |
34 | - GirdLayoutManager.SpanSizeLookup\#getSpanSize\(\) support lib bug [\#45](https://github.com/wayfair/brickkit-android/issues/45)
35 |
36 | **Merged pull requests:**
37 |
38 | - Release 0.9.10 [\#48](https://github.com/wayfair/brickkit-android/pull/48) ([thusson13](https://github.com/thusson13))
39 | - Add data binding support [\#47](https://github.com/wayfair/brickkit-android/pull/47) ([kojadin](https://github.com/kojadin))
40 |
41 | ## [0.9.10](https://github.com/wayfair/brickkit-android/tree/0.9.10) (2017-03-30)
42 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.9...0.9.10)
43 |
44 | **Merged pull requests:**
45 |
46 | - Fix for getSpanSize\(\) support lib bug [\#46](https://github.com/wayfair/brickkit-android/pull/46) ([craigtack](https://github.com/craigtack))
47 | - Release 0.9.9 [\#44](https://github.com/wayfair/brickkit-android/pull/44) ([thusson13](https://github.com/thusson13))
48 |
49 | ## [0.9.9](https://github.com/wayfair/brickkit-android/tree/0.9.9) (2017-03-23)
50 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.8...0.9.9)
51 |
52 | **Merged pull requests:**
53 |
54 | - Adding ability to switch between StaggeredGridLayout and GridLayout [\#43](https://github.com/wayfair/brickkit-android/pull/43) ([patbeagan1](https://github.com/patbeagan1))
55 | - Release 0.9.8 [\#41](https://github.com/wayfair/brickkit-android/pull/41) ([thusson13](https://github.com/thusson13))
56 |
57 | ## [0.9.8](https://github.com/wayfair/brickkit-android/tree/0.9.8) (2017-03-09)
58 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.7...0.9.8)
59 |
60 | **Merged pull requests:**
61 |
62 | - Horizontal scroll issue [\#39](https://github.com/wayfair/brickkit-android/pull/39) ([jhuwayfair](https://github.com/jhuwayfair))
63 | - Update README.md to point at new bintray location [\#38](https://github.com/wayfair/brickkit-android/pull/38) ([thusson13](https://github.com/thusson13))
64 | - Release 0.9.7 [\#37](https://github.com/wayfair/brickkit-android/pull/37) ([thusson13](https://github.com/thusson13))
65 |
66 | ## [0.9.7](https://github.com/wayfair/brickkit-android/tree/0.9.7) (2017-03-06)
67 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.6...0.9.7)
68 |
69 | **Closed issues:**
70 |
71 | - No callback for when view attached to window [\#32](https://github.com/wayfair/brickkit-android/issues/32)
72 |
73 | **Merged pull requests:**
74 |
75 | - Fixed bug causing footer to be displayed twice. [\#36](https://github.com/wayfair/brickkit-android/pull/36) ([ahsrav](https://github.com/ahsrav))
76 | - 0.9.6 release [\#34](https://github.com/wayfair/brickkit-android/pull/34) ([lizottenj](https://github.com/lizottenj))
77 |
78 | ## [0.9.6](https://github.com/wayfair/brickkit-android/tree/0.9.6) (2017-03-01)
79 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.5...0.9.6)
80 |
81 | **Merged pull requests:**
82 |
83 | - Add callback for when view attached to window [\#33](https://github.com/wayfair/brickkit-android/pull/33) ([craigtack](https://github.com/craigtack))
84 | - Release 0.9.5 [\#31](https://github.com/wayfair/brickkit-android/pull/31) ([thusson13](https://github.com/thusson13))
85 |
86 | ## [0.9.5](https://github.com/wayfair/brickkit-android/tree/0.9.5) (2017-02-27)
87 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.4...0.9.5)
88 |
89 | **Merged pull requests:**
90 |
91 | - Add itemExist function and test [\#30](https://github.com/wayfair/brickkit-android/pull/30) ([jhuwayfair](https://github.com/jhuwayfair))
92 | - Release 0.9.4 [\#29](https://github.com/wayfair/brickkit-android/pull/29) ([thusson13](https://github.com/thusson13))
93 |
94 | ## [0.9.4](https://github.com/wayfair/brickkit-android/tree/0.9.4) (2017-02-23)
95 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.3...0.9.4)
96 |
97 | **Merged pull requests:**
98 |
99 | - Replacing a brick with any hidden bricks before it was broken [\#28](https://github.com/wayfair/brickkit-android/pull/28) ([rnorvielwf](https://github.com/rnorvielwf))
100 | - Release 0.9.3 [\#27](https://github.com/wayfair/brickkit-android/pull/27) ([thusson13](https://github.com/thusson13))
101 |
102 | ## [0.9.3](https://github.com/wayfair/brickkit-android/tree/0.9.3) (2017-02-10)
103 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.2...0.9.3)
104 |
105 | **Merged pull requests:**
106 |
107 | - Add smoothScrollToBrick method to BrickDataManager [\#26](https://github.com/wayfair/brickkit-android/pull/26) ([thusson13](https://github.com/thusson13))
108 | - Release 0.9.2 [\#25](https://github.com/wayfair/brickkit-android/pull/25) ([thusson13](https://github.com/thusson13))
109 |
110 | ## [0.9.2](https://github.com/wayfair/brickkit-android/tree/0.9.2) (2017-02-09)
111 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.1...0.9.2)
112 |
113 | **Merged pull requests:**
114 |
115 | - Created BrickDialogFragment [\#24](https://github.com/wayfair/brickkit-android/pull/24) ([ahsrav](https://github.com/ahsrav))
116 | - Update CONTRIBUTING.md [\#23](https://github.com/wayfair/brickkit-android/pull/23) ([thusson13](https://github.com/thusson13))
117 | - Update clone instructions in README.md [\#21](https://github.com/wayfair/brickkit-android/pull/21) ([thusson13](https://github.com/thusson13))
118 | - Update installation instructions in README.md [\#20](https://github.com/wayfair/brickkit-android/pull/20) ([thusson13](https://github.com/thusson13))
119 | - Release brickkit-android-0.9.1 [\#19](https://github.com/wayfair/brickkit-android/pull/19) ([thusson13](https://github.com/thusson13))
120 |
121 | ## [0.9.1](https://github.com/wayfair/brickkit-android/tree/0.9.1) (2017-02-02)
122 | [Full Changelog](https://github.com/wayfair/brickkit-android/compare/0.9.0...0.9.1)
123 |
124 | **Merged pull requests:**
125 |
126 | - Don’t use View from RecyclerView as sticky header [\#22](https://github.com/wayfair/brickkit-android/pull/22) ([lizottenj](https://github.com/lizottenj))
127 | - Update push locations for bintray & upload pom file [\#18](https://github.com/wayfair/brickkit-android/pull/18) ([thusson13](https://github.com/thusson13))
128 | - Fix issue with uploadPattern [\#17](https://github.com/wayfair/brickkit-android/pull/17) ([thusson13](https://github.com/thusson13))
129 | - Add upload location to bintray.descriptor [\#16](https://github.com/wayfair/brickkit-android/pull/16) ([thusson13](https://github.com/thusson13))
130 | - Retry key encrypt [\#15](https://github.com/wayfair/brickkit-android/pull/15) ([thusson13](https://github.com/thusson13))
131 | - Publish to bintray [\#13](https://github.com/wayfair/brickkit-android/pull/13) ([thusson13](https://github.com/thusson13))
132 | - Remove `createBricks`, add new way to add many items [\#12](https://github.com/wayfair/brickkit-android/pull/12) ([mdemaso](https://github.com/mdemaso))
133 | - Publish to bintray [\#11](https://github.com/wayfair/brickkit-android/pull/11) ([thusson13](https://github.com/thusson13))
134 | - Publish to bintray [\#10](https://github.com/wayfair/brickkit-android/pull/10) ([thusson13](https://github.com/thusson13))
135 | - Reset working directory after build step [\#9](https://github.com/wayfair/brickkit-android/pull/9) ([thusson13](https://github.com/thusson13))
136 | - Fix bintray deploy section of .travis.yml [\#7](https://github.com/wayfair/brickkit-android/pull/7) ([thusson13](https://github.com/thusson13))
137 | - Publish releases to bintray [\#4](https://github.com/wayfair/brickkit-android/pull/4) ([thusson13](https://github.com/thusson13))
138 |
139 | ## [0.9.0](https://github.com/wayfair/brickkit-android/tree/0.9.0) (2017-02-01)
140 | **Merged pull requests:**
141 |
142 | - Add coverage / build icons to README [\#3](https://github.com/wayfair/brickkit-android/pull/3) ([thusson13](https://github.com/thusson13))
143 | - Add codecov.io support [\#2](https://github.com/wayfair/brickkit-android/pull/2) ([thusson13](https://github.com/thusson13))
144 | - Adding support for Travis CI [\#1](https://github.com/wayfair/brickkit-android/pull/1) ([thusson13](https://github.com/thusson13))
145 |
146 |
147 |
148 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
149 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2016 Wayfair
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------