├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── drawable │ │ │ ├── eraser.png │ │ │ ├── poster.png │ │ │ ├── palette.png │ │ │ ├── stream_logo.png │ │ │ ├── sketchbook_bg.png │ │ │ ├── drawing_content.png │ │ │ ├── ic_line_weight.xml │ │ │ ├── ic_chat.xml │ │ │ ├── ic_bubble.xml │ │ │ ├── ic_clear.xml │ │ │ ├── ic_gallery.xml │ │ │ ├── ic_line_style.xml │ │ │ ├── ic_brush.xml │ │ │ ├── ic_colorized.xml │ │ │ └── ic_launcher_background.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── themes.xml │ │ │ └── strings.xml │ │ └── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ ├── kotlin │ │ └── io │ │ │ └── getstream │ │ │ └── streamdraw │ │ │ ├── states │ │ │ └── AnimationState.kt │ │ │ ├── StreamDrawApp.kt │ │ │ ├── utils │ │ │ ├── Constants.kt │ │ │ └── Commons.kt │ │ │ ├── network │ │ │ ├── RandomWordsFetcher.kt │ │ │ └── RandomWordsApi.kt │ │ │ ├── data │ │ │ ├── GameMessage.kt │ │ │ └── GameConnectionState.kt │ │ │ ├── ui │ │ │ ├── theme │ │ │ │ ├── Shape.kt │ │ │ │ ├── Color.kt │ │ │ │ ├── Type.kt │ │ │ │ └── Theme.kt │ │ │ ├── screens │ │ │ │ ├── game │ │ │ │ │ ├── GameStatus.kt │ │ │ │ │ ├── GameScreen.kt │ │ │ │ │ ├── GameRestartButton.kt │ │ │ │ │ ├── PlayerScreen.kt │ │ │ │ │ ├── GameExitDialog.kt │ │ │ │ │ ├── GameDeletedScreen.kt │ │ │ │ │ ├── NewGameMessage.kt │ │ │ │ │ ├── DrawingScreen.kt │ │ │ │ │ ├── WordSelectionDialog.kt │ │ │ │ │ ├── GameActivity.kt │ │ │ │ │ ├── GameChatDialog.kt │ │ │ │ │ ├── GameWinnerScreen.kt │ │ │ │ │ └── GameDrawing.kt │ │ │ │ ├── main │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ ├── MainNavigation.kt │ │ │ │ │ ├── MainScreen.kt │ │ │ │ │ └── MainViewModel.kt │ │ │ │ ├── group │ │ │ │ │ ├── GroupBottomSheet.kt │ │ │ │ │ ├── CreateGroup.kt │ │ │ │ │ ├── JoinGroup.kt │ │ │ │ │ ├── CreateGroupForm.kt │ │ │ │ │ ├── JoinGroupForm.kt │ │ │ │ │ └── GroupEntranceSheetContent.kt │ │ │ │ ├── home │ │ │ │ │ └── HomeScreen.kt │ │ │ │ └── chat │ │ │ │ │ └── ChatScreen.kt │ │ │ └── components │ │ │ │ ├── LoadingIndicator.kt │ │ │ │ ├── TextFields.kt │ │ │ │ ├── SketchbookScreen.kt │ │ │ │ ├── Texts.kt │ │ │ │ ├── Buttons.kt │ │ │ │ └── SketchbookControlMenu.kt │ │ │ ├── extensions │ │ │ ├── StringExtensions.kt │ │ │ ├── ContextExtensions.kt │ │ │ ├── BitmapExtensions.kt │ │ │ └── ChannelExtensions.kt │ │ │ ├── di │ │ │ ├── StreamModule.kt │ │ │ ├── PersistenceModule.kt │ │ │ └── NetworkModule.kt │ │ │ ├── initializer │ │ │ ├── TimberInitializer.kt │ │ │ └── StreamChatInitializer.kt │ │ │ └── persistence │ │ │ └── AppPreferences.kt │ │ └── AndroidManifest.xml └── build.gradle ├── previews ├── cover.gif ├── preview0.gif ├── preview1.png ├── preview2.png └── preview3.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── spotless.gradle ├── settings.gradle ├── spotless.license.kt ├── CONTRIBUTING.md ├── .github ├── CODEOWNERS ├── pull_request_template.md └── workflows │ └── android.yml ├── .gitignore ├── gradle.properties ├── dependencies.gradle ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── README.md ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /previews/cover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/previews/cover.gif -------------------------------------------------------------------------------- /previews/preview0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/previews/preview0.gif -------------------------------------------------------------------------------- /previews/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/previews/preview1.png -------------------------------------------------------------------------------- /previews/preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/previews/preview2.png -------------------------------------------------------------------------------- /previews/preview3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/previews/preview3.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/drawable/eraser.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/drawable/poster.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/drawable/palette.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/stream_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/drawable/stream_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/sketchbook_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/drawable/sketchbook_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/drawing_content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/drawable/drawing_content.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-draw-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /spotless.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.diffplug.spotless" 2 | apply from: "$rootDir/dependencies.gradle" 3 | spotless { 4 | kotlin { 5 | target "**/*.kt" 6 | ktlint("$versions.ktlint").userData(['indent_size': '4', 'continuation_indent_size': '4']) 7 | licenseHeaderFile "$rootDir/spotless.license.kt" 8 | trimTrailingWhitespace() 9 | endWithNewline() 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_line_weight.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bubble.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clear.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #0055FF 4 | #FFBB86FC 5 | #FF6200EE 6 | #FF3700B3 7 | #FF03DAC5 8 | #FF018786 9 | #FF000000 10 | #FFFFFFFF 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gallery.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } 14 | maven { url "https://jitpack.io" } 15 | } 16 | } 17 | rootProject.name = "Stream-Draw" 18 | include ':app' 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_line_style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_brush.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_colorized.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /spotless.license.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. 3 | 4 | ## Preparing a pull request for review 5 | Ensure your change is properly formatted by running: 6 | 7 | ```gradle 8 | ./gradlew spotlessApply 9 | ``` 10 | 11 | Please correct any failures before requesting a review. 12 | 13 | ## Code reviews 14 | All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) for more information on using pull requests. 15 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # More details are here: https://help.github.com/articles/about-codeowners/ 5 | 6 | # The '*' pattern is global owners. 7 | # Not adding in this PR, but I'd like to try adding a global owner set with the entire team. 8 | # One interpretation of their docs is that global owners are added only if not removed 9 | # by a more local rule. 10 | 11 | # Order is important. The last matching pattern has the most precedence. 12 | # The folders are ordered as follows: 13 | 14 | # In each subsection folders are ordered first by depth, then alphabetically. 15 | # This should make it easy to add new rules without breaking existing ones. 16 | * @skydoves -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Guidelines 2 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 3 | 4 | ### Types of changes 5 | What types of changes does your code introduce? 6 | 7 | - [ ] Bugfix (non-breaking change which fixes an issue) 8 | - [ ] New feature (non-breaking change which adds functionality) 9 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 10 | 11 | ### Preparing a pull request for review 12 | Ensure your change is properly formatted by running: 13 | 14 | ```gradle 15 | $ ./gradlew spotlessApply 16 | ``` 17 | 18 | Please correct any failures before requesting a review. -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/states/AnimationState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.states 18 | 19 | enum class AnimationState { 20 | NONE, 21 | START, 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: set up JDK 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 11 19 | 20 | - name: Cache Gradle and wrapper 21 | uses: actions/cache@v2 22 | with: 23 | path: | 24 | ~/.gradle/caches 25 | ~/.gradle/wrapper 26 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 27 | restore-keys: | 28 | ${{ runner.os }}-gradle- 29 | - name: Make Gradle executable 30 | run: chmod +x ./gradlew 31 | 32 | - name: Build with Gradle 33 | run: ./gradlew build -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/StreamDrawApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw 18 | 19 | import android.app.Application 20 | import dagger.hilt.android.HiltAndroidApp 21 | 22 | @HiltAndroidApp 23 | class StreamDrawApp : Application() 24 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.utils 18 | 19 | const val CHANNEL_MESSAGING = "messaging" 20 | 21 | const val KEY_NAME = "name" 22 | const val KEY_HOST_NAME = "host_name" 23 | const val KEY_SELECTED_WORD = "selected_word" 24 | const val KEY_GAME_STATUS = "game_status" 25 | const val KEY_WINNER = "game_winner" 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | /.idea 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Intellij 37 | *.iml 38 | .idea/workspace.xml 39 | .idea/tasks.xml 40 | .idea/gradle.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | app/.idea/ 44 | 45 | # Mac 46 | *.DS_Store 47 | 48 | # Keystore files 49 | *.jks 50 | 51 | # External native build folder generated in Android Studio 2.2 and later 52 | .externalNativeBuild 53 | 54 | # Google Services (e.g. APIs or Firebase) 55 | google-services.json 56 | 57 | # Freeline 58 | freeline.py 59 | freeline/ 60 | freeline_project_description.json 61 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/network/RandomWordsFetcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.network 18 | 19 | import javax.inject.Inject 20 | 21 | class RandomWordsFetcher @Inject constructor( 22 | private val randomWordsApi: RandomWordsApi 23 | ) { 24 | suspend fun getRandomWords(): List { 25 | return randomWordsApi.getWordList().shuffled().take(3) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/data/GameMessage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.data 18 | 19 | import io.getstream.chat.android.client.models.Message 20 | 21 | data class GameMessage( 22 | val name: String, 23 | val message: String 24 | ) 25 | 26 | fun Message.toGameMessage(): GameMessage { 27 | return GameMessage( 28 | name = user.name, 29 | message = text, 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.theme 18 | 19 | import androidx.compose.foundation.shape.RoundedCornerShape 20 | import androidx.compose.material.Shapes 21 | import androidx.compose.ui.unit.dp 22 | 23 | val Shapes = Shapes( 24 | small = RoundedCornerShape(4.dp), 25 | medium = RoundedCornerShape(4.dp), 26 | large = RoundedCornerShape(0.dp) 27 | ) 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/extensions/StringExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.extensions 18 | 19 | import io.getstream.streamdraw.utils.CHANNEL_MESSAGING 20 | 21 | fun String.toChannelId() = "$CHANNEL_MESSAGING:$this" 22 | 23 | val String.groupName: String 24 | get() = "$this's Group" 25 | 26 | val String.image: String 27 | get() = "https://getstream.imgix.net/images/random_svg/${first().uppercase()}.png" 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/utils/Commons.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.utils 18 | 19 | fun generateGroupId(): String { 20 | return generateUniqueId(6, ('A'..'Z') + ('0'..'9')) 21 | } 22 | 23 | fun generateUserId(): String { 24 | return generateUniqueId(8, ('A'..'Z') + ('a'..'z') + ('0'..'9')) 25 | } 26 | 27 | private fun generateUniqueId(length: Int, allowedChars: List): String { 28 | return (1..length) 29 | .map { allowedChars.random() } 30 | .joinToString("") 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/di/StreamModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.di 18 | 19 | import dagger.Module 20 | import dagger.Provides 21 | import dagger.hilt.InstallIn 22 | import dagger.hilt.components.SingletonComponent 23 | import io.getstream.chat.android.client.ChatClient 24 | import javax.inject.Singleton 25 | 26 | @Module 27 | @InstallIn(SingletonComponent::class) 28 | object StreamModule { 29 | 30 | @Provides 31 | @Singleton 32 | fun provideStreamChatClient() = ChatClient.instance() 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/GameStatus.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | enum class GameStatus(val status: String) { 20 | START("START"), 21 | FINISH("FINISH"), 22 | DELETED("DELETED"), 23 | UNKNOWN("UNKNOWN"); 24 | 25 | companion object { 26 | fun getGameStatusByName(status: String?): GameStatus { 27 | return when (status) { 28 | "START" -> START 29 | "FINISH" -> FINISH 30 | "DELETED" -> DELETED 31 | else -> UNKNOWN 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/data/GameConnectionState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.data 18 | 19 | import io.getstream.chat.android.client.errors.ChatError 20 | import io.getstream.chat.android.client.models.Channel 21 | 22 | sealed class GameConnectionState { 23 | object None : GameConnectionState() 24 | object Loading : GameConnectionState() 25 | data class Success(val channel: Channel) : GameConnectionState() 26 | data class Failure(val error: ChatError) : GameConnectionState() 27 | } 28 | 29 | val GameConnectionState.isLoading: Boolean 30 | inline get() = this == GameConnectionState.Loading 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/initializer/TimberInitializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.initializer 18 | 19 | import android.content.Context 20 | import androidx.startup.Initializer 21 | import io.getstream.streamdraw.BuildConfig 22 | import timber.log.Timber 23 | 24 | class TimberInitializer : Initializer { 25 | 26 | override fun create(context: Context) { 27 | if (BuildConfig.DEBUG) { 28 | Timber.plant(Timber.DebugTree()) 29 | Timber.d("TimberInitializer is initialized") 30 | } 31 | } 32 | 33 | override fun dependencies(): List>> = emptyList() 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/di/PersistenceModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.di 18 | 19 | import android.content.Context 20 | import dagger.Module 21 | import dagger.Provides 22 | import dagger.hilt.InstallIn 23 | import dagger.hilt.android.qualifiers.ApplicationContext 24 | import dagger.hilt.components.SingletonComponent 25 | import io.getstream.streamdraw.persistence.AppPreferences 26 | import javax.inject.Singleton 27 | 28 | @Module 29 | @InstallIn(SingletonComponent::class) 30 | object PersistenceModule { 31 | 32 | @Provides 33 | @Singleton 34 | fun provideAppPreferences(@ApplicationContext context: Context) = AppPreferences(context) 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.main 18 | 19 | import android.os.Bundle 20 | import androidx.activity.ComponentActivity 21 | import androidx.activity.compose.setContent 22 | import dagger.hilt.android.AndroidEntryPoint 23 | import io.getstream.streamdraw.ui.theme.StreamDrawTheme 24 | 25 | @AndroidEntryPoint 26 | class MainActivity : ComponentActivity() { 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | 31 | setContent { 32 | StreamDrawTheme { 33 | MainNavigation() 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.theme 18 | 19 | import androidx.compose.ui.graphics.Color 20 | 21 | val Purple200 = Color(0xFFBB86FC) 22 | val Purple500 = Color(0xFF6200EE) 23 | val Purple700 = Color(0xFF3700B3) 24 | val Teal200 = Color(0xFF03DAC5) 25 | 26 | val streamAccent = Color(0xFF005FFF) 27 | 28 | val hostAccent = Color(0xFFBB86FC) 29 | 30 | val PrimaryColor = streamAccent 31 | 32 | val LightColor = Color(0xFFEBEBEB) 33 | 34 | val DefaultButtonColor = Color(0xFF31AEE7) 35 | 36 | val DefaultTextColor = Color(0xFF313131) 37 | 38 | val PrimaryButtonColor = Color(0xFF086E7D) 39 | val LightTextColor = Color(0xFFF0F0F0) 40 | 41 | val SecondaryButtonColor = Color(0xFFFFC900) 42 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/persistence/AppPreferences.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.persistence 18 | 19 | import android.content.Context 20 | import android.content.SharedPreferences 21 | import javax.inject.Inject 22 | 23 | class AppPreferences @Inject constructor( 24 | context: Context 25 | ) { 26 | private var prefs: SharedPreferences = 27 | context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) 28 | 29 | var userId: String? 30 | get() = prefs.getString(KEY_USER_ID, null) 31 | set(value) = prefs.edit().putString(KEY_USER_ID, value).apply() 32 | 33 | companion object { 34 | private const val PREFS_NAME = "stream_draw" 35 | private const val KEY_USER_ID = "user_id" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/extensions/ContextExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.extensions 18 | 19 | import android.content.ClipData 20 | import android.content.Context 21 | import android.widget.Toast 22 | 23 | fun Context.toast(message: String?) { 24 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 25 | } 26 | 27 | fun Context.toast(resource: Int) { 28 | Toast.makeText(this, getString(resource), Toast.LENGTH_SHORT).show() 29 | } 30 | 31 | fun Context.setClipboard(text: String?) { 32 | val clipboard = 33 | getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager 34 | val clip = ClipData.newPlainText("Copied Text", text) 35 | clipboard.setPrimaryClip(clip) 36 | toast("Copied: $text") 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/extensions/BitmapExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.extensions 18 | 19 | import android.graphics.Bitmap 20 | import android.graphics.BitmapFactory 21 | import android.util.Base64 22 | import java.io.ByteArrayOutputStream 23 | 24 | fun Bitmap.toBase64String(): String? { 25 | val outputStream = ByteArrayOutputStream() 26 | this.compress(Bitmap.CompressFormat.PNG, 85, outputStream) 27 | return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT) 28 | } 29 | 30 | fun String.toBitmap(): Bitmap? { 31 | val decodedBytes: ByteArray = Base64.decode( 32 | this.substring(this.indexOf(",") + 1), 33 | Base64.DEFAULT 34 | ) 35 | return BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size) 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.di 18 | 19 | import dagger.Module 20 | import dagger.Provides 21 | import dagger.hilt.InstallIn 22 | import dagger.hilt.components.SingletonComponent 23 | import io.getstream.streamdraw.network.RandomWordsApi 24 | import io.getstream.streamdraw.network.RandomWordsFetcher 25 | import javax.inject.Singleton 26 | 27 | @Module 28 | @InstallIn(SingletonComponent::class) 29 | object NetworkModule { 30 | 31 | @Provides 32 | @Singleton 33 | fun provideRandomWordsApi(): RandomWordsApi = RandomWordsApi.invoke() 34 | 35 | @Provides 36 | @Singleton 37 | fun provideRandomWordsFetcher( 38 | randomWordsApi: RandomWordsApi 39 | ): RandomWordsFetcher = RandomWordsFetcher(randomWordsApi) 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/components/LoadingIndicator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.components 18 | 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.material.CircularProgressIndicator 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.Alignment 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.unit.dp 26 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 27 | 28 | @Composable 29 | fun LoadingIndicator(modifier: Modifier = Modifier) { 30 | Box( 31 | modifier.background(ChatTheme.colors.appBackground), 32 | contentAlignment = Alignment.Center, 33 | ) { 34 | CircularProgressIndicator(strokeWidth = 2.dp, color = ChatTheme.colors.primaryAccent) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | # Enables namespacing of each library's R class so that its R class includes only the 23 | # resources declared in the library itself and none from the library's dependencies, 24 | # thereby reducing the size of the R class for that library 25 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/network/RandomWordsApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.network 18 | 19 | import retrofit2.Retrofit 20 | import retrofit2.converter.moshi.MoshiConverterFactory 21 | import retrofit2.http.GET 22 | 23 | interface RandomWordsApi { 24 | 25 | @GET("skribble_words.json") 26 | suspend fun getWordList(): List 27 | 28 | companion object { 29 | private const val DATA_BASE_URL = 30 | "https://gist.githubusercontent.com/skydoves/b7a045f42e66a7a61fd850e566993c9d/raw/c671a08e5bad0296e30c182ace5113bf4f18bc71/" 31 | 32 | operator fun invoke(): RandomWordsApi { 33 | return Retrofit.Builder() 34 | .addConverterFactory(MoshiConverterFactory.create()) 35 | .baseUrl(DATA_BASE_URL) 36 | .build() 37 | .create(RandomWordsApi::class.java) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext.versions = [ 2 | minSdk : 21, 3 | compileSdk : 33, 4 | versionCode : 1, 5 | versionName : '1.0.0', 6 | 7 | // gradle plugins 8 | spotlessGradle : '6.3.0', 9 | googleService : '4.3.10', 10 | ktlint : '0.40.0', 11 | 12 | // kotlin 13 | kotlin : '1.7.0', 14 | 15 | // androidx 16 | material : '1.5.0', 17 | activity : '1.4.0', 18 | lifecycle : '2.4.1', 19 | 20 | // compose 21 | compose : '1.2.0-rc03', 22 | composeCompiler : '1.2.0', 23 | composeNavigation : '2.4.1', 24 | constraintLayout : '1.0.0', 25 | hiltComposeNavigation: '1.0.0', 26 | 27 | // stream chat SDK 28 | streamChatSDK : '5.17.0', 29 | 30 | // sketchbook 31 | sketchbook : '1.0.4', 32 | 33 | // konfetti 34 | konfetti : '2.0.2', 35 | 36 | // startup 37 | startupVersion : '1.1.1', 38 | 39 | // di 40 | hilt : '2.42', 41 | 42 | // network 43 | retrofitVersion : '2.9.0', 44 | okhttpVersion : '4.9.1', 45 | 46 | // moshi 47 | moshiVersion : '1.13.0', 48 | 49 | // image loading 50 | landscapist : '1.5.2', 51 | 52 | // firebase 53 | firebase : '19.2.1', 54 | 55 | // debugging 56 | timberVersion : '5.0.0', 57 | ] 58 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.theme 18 | 19 | import androidx.compose.material.Typography 20 | import androidx.compose.ui.text.TextStyle 21 | import androidx.compose.ui.text.font.FontFamily 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.unit.sp 24 | 25 | // Set of Material typography styles to start with 26 | val Typography = Typography( 27 | body1 = TextStyle( 28 | fontFamily = FontFamily.Default, 29 | fontWeight = FontWeight.Normal, 30 | fontSize = 16.sp 31 | ) 32 | /* Other default text styles to override 33 | button = TextStyle( 34 | fontFamily = FontFamily.Default, 35 | fontWeight = FontWeight.W500, 36 | fontSize = 14.sp 37 | ), 38 | caption = TextStyle( 39 | fontFamily = FontFamily.Default, 40 | fontWeight = FontWeight.Normal, 41 | fontSize = 12.sp 42 | ) 43 | */ 44 | ) 45 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Stream Draw 3 | 4 | Create Group 5 | Join Group 6 | 7 | Display Name 8 | Limit User 9 | Limit Time 10 | No of Rounds 11 | Group Code 12 | Continue to Game 13 | Check out the ticket! 14 | Your friends can join using this code 15 | Something went wrong. 😢 16 | Select a word 17 | Click here to share it with your friends 18 | Submit 19 | Do you want to exit the game? 20 | Exit 21 | please fill all required fields 22 | Group has been deleted 😢 23 | Exit Game 24 | Group Chat 25 | Congratulations, \nYou win! 🎉🎉 26 | Restart 27 | 28 | 7uyq8e43evdp 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/GameScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.runtime.LaunchedEffect 21 | import androidx.compose.ui.graphics.Color 22 | import io.getstream.chat.android.compose.viewmodel.messages.MessageListViewModel 23 | import io.getstream.sketchbook.SketchbookController 24 | 25 | @Composable 26 | fun GameScreen( 27 | gameViewModel: GameViewModel, 28 | listViewModel: MessageListViewModel, 29 | sketchbookController: SketchbookController, 30 | exitGame: () -> Unit 31 | ) { 32 | LaunchedEffect(Unit) { 33 | sketchbookController.setPaintStrokeWidth(23f) 34 | sketchbookController.setPaintColor(Color.Black) 35 | } 36 | 37 | GameDrawing( 38 | isHost = gameViewModel.isHost.value, 39 | gameViewModel = gameViewModel, 40 | listViewModel = listViewModel, 41 | sketchbookController = sketchbookController, 42 | exitGame = exitGame, 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/group/GroupBottomSheet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.group 18 | 19 | import androidx.compose.material.BottomSheetScaffold 20 | import androidx.compose.material.BottomSheetScaffoldState 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.runtime.LaunchedEffect 23 | import androidx.compose.ui.unit.dp 24 | 25 | @Composable 26 | fun GroupBottomSheet( 27 | bottomSheetScaffoldState: BottomSheetScaffoldState, 28 | expand: Boolean, 29 | sheetContent: @Composable () -> Unit, 30 | mainContent: @Composable () -> Unit 31 | ) { 32 | BottomSheetScaffold( 33 | scaffoldState = bottomSheetScaffoldState, 34 | sheetContent = { sheetContent() }, 35 | sheetPeekHeight = 0.dp 36 | ) { 37 | mainContent() 38 | } 39 | if (expand && bottomSheetScaffoldState.bottomSheetState.isCollapsed) { 40 | LaunchedEffect(bottomSheetScaffoldState.bottomSheetState) { 41 | bottomSheetScaffoldState.bottomSheetState.expand() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 36 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/group/CreateGroup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.group 18 | 19 | import androidx.compose.material.rememberBottomSheetScaffoldState 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.collectAsState 22 | import androidx.compose.runtime.getValue 23 | import io.getstream.streamdraw.data.GameConnectionState 24 | import io.getstream.streamdraw.ui.screens.main.MainViewModel 25 | 26 | @Composable 27 | fun CreateGroup(viewModel: MainViewModel) { 28 | val connectionState by viewModel.gameConnectionState.collectAsState() 29 | val channel by viewModel.connectedChannel.collectAsState(initial = null) 30 | val bottomSheetScaffoldState = rememberBottomSheetScaffoldState() 31 | 32 | GroupBottomSheet( 33 | bottomSheetScaffoldState = bottomSheetScaffoldState, 34 | expand = connectionState is GameConnectionState.Success, 35 | mainContent = { CreateGroupForm(viewModel = viewModel, connectionState = connectionState) }, 36 | sheetContent = { GroupEntranceSheetContent(channel) }, 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/main/MainNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.main 18 | 19 | import androidx.compose.runtime.Composable 20 | import androidx.hilt.navigation.compose.hiltViewModel 21 | import androidx.navigation.compose.NavHost 22 | import androidx.navigation.compose.composable 23 | import androidx.navigation.compose.rememberNavController 24 | import io.getstream.streamdraw.ui.screens.group.CreateGroup 25 | import io.getstream.streamdraw.ui.screens.group.JoinGroup 26 | import io.getstream.streamdraw.ui.screens.home.HomeScreen 27 | 28 | @Composable 29 | fun MainNavigation() { 30 | val navController = rememberNavController() 31 | NavHost(navController = navController, startDestination = NAV_SPLASH) { 32 | composable(NAV_SPLASH) { HomeScreen(navController) } 33 | composable(NAV_CREATE_GROUP) { CreateGroup(hiltViewModel()) } 34 | composable(NAV_JOIN_GROUP) { JoinGroup(hiltViewModel()) } 35 | } 36 | } 37 | 38 | const val NAV_SPLASH = "splash" 39 | const val NAV_CREATE_GROUP = "create_group" 40 | const val NAV_JOIN_GROUP = "join_group" 41 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/extensions/ChannelExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.extensions 18 | 19 | import io.getstream.chat.android.client.channel.ChannelClient 20 | import io.getstream.chat.android.client.models.Channel 21 | import io.getstream.streamdraw.ui.screens.game.GameStatus 22 | import io.getstream.streamdraw.utils.KEY_GAME_STATUS 23 | import io.getstream.streamdraw.utils.KEY_HOST_NAME 24 | import io.getstream.streamdraw.utils.KEY_SELECTED_WORD 25 | import io.getstream.streamdraw.utils.KEY_WINNER 26 | 27 | inline val Channel.groupId: String 28 | get() = cid.split(":")[1] 29 | 30 | inline val ChannelClient.groupId: String 31 | get() = cid.split(":")[1] 32 | 33 | inline val Channel.selectedWord: String? 34 | get() = extraData[KEY_SELECTED_WORD]?.toString() 35 | 36 | inline val Channel.hostName: String? 37 | get() = extraData[KEY_HOST_NAME]?.toString() 38 | 39 | inline val Channel.gameStatus: GameStatus 40 | get() { 41 | val statusName = extraData[KEY_GAME_STATUS]?.toString() ?: GameStatus.START.name 42 | return GameStatus.getGameStatusByName(statusName) 43 | } 44 | 45 | inline val Channel.winner: String? 46 | get() = extraData[KEY_WINNER]?.toString() 47 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/GameRestartButton.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import androidx.compose.foundation.layout.ColumnScope 20 | import androidx.compose.foundation.layout.padding 21 | import androidx.compose.material.Button 22 | import androidx.compose.material.ButtonDefaults 23 | import androidx.compose.material.Text 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.ui.Alignment 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.graphics.Color 28 | import androidx.compose.ui.res.stringResource 29 | import androidx.compose.ui.unit.dp 30 | import androidx.compose.ui.unit.sp 31 | import io.getstream.streamdraw.R 32 | import io.getstream.streamdraw.ui.theme.streamAccent 33 | 34 | @Composable 35 | fun ColumnScope.GameRestartButton( 36 | gameViewModel: GameViewModel 37 | ) { 38 | Button( 39 | modifier = Modifier 40 | .padding(12.dp) 41 | .align(Alignment.CenterHorizontally), 42 | colors = ButtonDefaults.buttonColors( 43 | backgroundColor = streamAccent, 44 | contentColor = Color.White 45 | ), 46 | onClick = { gameViewModel.restartGame() } 47 | ) { 48 | Text( 49 | text = stringResource(R.string.game_restart), 50 | fontSize = 21.sp 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/home/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.home 18 | 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.ui.res.stringResource 21 | import androidx.compose.ui.unit.dp 22 | import androidx.navigation.NavController 23 | import io.getstream.streamdraw.R 24 | import io.getstream.streamdraw.ui.components.PrimaryButton 25 | import io.getstream.streamdraw.ui.components.SecondaryButton 26 | import io.getstream.streamdraw.ui.screens.main.MainScreen 27 | import io.getstream.streamdraw.ui.screens.main.NAV_CREATE_GROUP 28 | import io.getstream.streamdraw.ui.screens.main.NAV_JOIN_GROUP 29 | 30 | @Composable 31 | fun HomeScreen(navController: NavController) { 32 | MainScreen { 33 | PrimaryButton( 34 | onClick = { navController.navigate(NAV_CREATE_GROUP) }, 35 | text = stringResource(id = R.string.create_group), 36 | marginTop = 12.dp, 37 | marginBottom = 12.dp, 38 | marginLeft = 24.dp, 39 | marginRight = 24.dp 40 | ) 41 | SecondaryButton( 42 | onClick = { navController.navigate(NAV_JOIN_GROUP) }, 43 | text = stringResource(id = R.string.join_group), 44 | marginTop = 12.dp, 45 | marginBottom = 12.dp, 46 | marginLeft = 24.dp, 47 | marginRight = 24.dp 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.theme 18 | 19 | import androidx.compose.foundation.isSystemInDarkTheme 20 | import androidx.compose.material.MaterialTheme 21 | import androidx.compose.material.darkColors 22 | import androidx.compose.material.lightColors 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.ui.graphics.Color 25 | 26 | private val DarkColorPalette = darkColors( 27 | background = Color.Black, 28 | surface = Color.Black, 29 | primary = streamAccent, 30 | primaryVariant = streamAccent, 31 | secondary = streamAccent, 32 | onPrimary = Color.White, 33 | onSecondary = Color.White 34 | ) 35 | 36 | private val LightColorPalette = lightColors( 37 | background = Color.White, 38 | surface = Color.White, 39 | primary = streamAccent, 40 | primaryVariant = streamAccent, 41 | secondary = streamAccent, 42 | onPrimary = Color.Black, 43 | onSecondary = Color.Black 44 | ) 45 | 46 | @Composable 47 | fun StreamDrawTheme( 48 | darkTheme: Boolean = isSystemInDarkTheme(), 49 | content: @Composable () -> Unit 50 | ) { 51 | val colors = if (darkTheme) { 52 | DarkColorPalette 53 | } else { 54 | LightColorPalette 55 | } 56 | 57 | MaterialTheme( 58 | colors = colors, 59 | typography = Typography, 60 | shapes = Shapes, 61 | content = content 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/PlayerScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import androidx.compose.foundation.layout.fillMaxWidth 20 | import androidx.compose.foundation.layout.padding 21 | import androidx.compose.foundation.layout.size 22 | import androidx.compose.foundation.lazy.LazyListState 23 | import androidx.compose.foundation.lazy.LazyRow 24 | import androidx.compose.foundation.lazy.items 25 | import androidx.compose.foundation.lazy.rememberLazyListState 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.unit.dp 29 | import io.getstream.chat.android.client.models.Member 30 | import io.getstream.chat.android.compose.ui.components.avatar.UserAvatar 31 | 32 | @Composable 33 | fun PlayerScreen( 34 | players: List, 35 | lazyListState: LazyListState = rememberLazyListState() 36 | ) { 37 | if (players.isNotEmpty()) { 38 | LazyRow( 39 | modifier = Modifier 40 | .fillMaxWidth() 41 | .padding(start = 12.dp, end = 12.dp, top = 12.dp), 42 | state = lazyListState, 43 | ) { 44 | items(players) { player -> 45 | UserAvatar( 46 | user = player.user.copy(online = true), 47 | modifier = Modifier 48 | .padding(6.dp) 49 | .size(45.dp) 50 | ) 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/GameExitDialog.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import android.app.Activity 20 | import androidx.activity.compose.BackHandler 21 | import androidx.compose.material.AlertDialog 22 | import androidx.compose.material.Button 23 | import androidx.compose.material.Text 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.getValue 26 | import androidx.compose.runtime.mutableStateOf 27 | import androidx.compose.runtime.remember 28 | import androidx.compose.runtime.setValue 29 | import androidx.compose.ui.platform.LocalContext 30 | import androidx.compose.ui.res.stringResource 31 | import io.getstream.streamdraw.R 32 | 33 | @Composable 34 | fun GameExitDialog( 35 | gameViewModel: GameViewModel, 36 | ) { 37 | var expandDialog by remember { mutableStateOf(false) } 38 | val activity = LocalContext.current as Activity 39 | BackHandler(enabled = true) { expandDialog = !expandDialog } 40 | if (expandDialog) { 41 | AlertDialog( 42 | onDismissRequest = { expandDialog = false }, 43 | title = { Text(text = stringResource(R.string.exit_game_title)) }, 44 | confirmButton = { 45 | Button( 46 | onClick = { 47 | gameViewModel.exitChannel() 48 | activity.finish() 49 | } 50 | ) { 51 | Text(text = stringResource(R.string.exit_game_confirm)) 52 | } 53 | } 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/group/JoinGroup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.group 18 | 19 | import androidx.compose.material.rememberBottomSheetScaffoldState 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.LaunchedEffect 22 | import androidx.compose.runtime.collectAsState 23 | import androidx.compose.runtime.getValue 24 | import androidx.compose.runtime.rememberCoroutineScope 25 | import androidx.compose.ui.platform.LocalContext 26 | import io.getstream.streamdraw.data.GameConnectionState 27 | import io.getstream.streamdraw.extensions.toast 28 | import io.getstream.streamdraw.ui.screens.main.MainViewModel 29 | import kotlinx.coroutines.flow.filterIsInstance 30 | import kotlinx.coroutines.launch 31 | 32 | @Composable 33 | fun JoinGroup(viewModel: MainViewModel) { 34 | val connectionState by viewModel.gameConnectionState.collectAsState() 35 | val channel by viewModel.connectedChannel.collectAsState(initial = null) 36 | val bottomSheetScaffoldState = rememberBottomSheetScaffoldState() 37 | 38 | GroupBottomSheet( 39 | bottomSheetScaffoldState = bottomSheetScaffoldState, 40 | expand = connectionState is GameConnectionState.Success, 41 | mainContent = { JoinGroupForm(viewModel = viewModel, connectionState = connectionState) }, 42 | sheetContent = { GroupEntranceSheetContent(channel) }, 43 | ) 44 | 45 | val context = LocalContext.current 46 | val coroutineScope = rememberCoroutineScope() 47 | LaunchedEffect(key1 = viewModel) { 48 | coroutineScope.launch { 49 | viewModel.gameConnectionState.filterIsInstance().collect { 50 | context.toast("Check out the Group Code: ${it.error.message}") 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/components/TextFields.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.components 18 | 19 | import androidx.compose.foundation.layout.fillMaxWidth 20 | import androidx.compose.foundation.layout.padding 21 | import androidx.compose.foundation.layout.wrapContentHeight 22 | import androidx.compose.foundation.text.KeyboardOptions 23 | import androidx.compose.material.MaterialTheme 24 | import androidx.compose.material.Text 25 | import androidx.compose.material.TextField 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.text.TextStyle 29 | import androidx.compose.ui.text.input.KeyboardType 30 | import androidx.compose.ui.tooling.preview.Preview 31 | import androidx.compose.ui.unit.Dp 32 | import androidx.compose.ui.unit.dp 33 | 34 | @Composable 35 | fun AppTextField( 36 | onValueChange: ((input: String) -> Unit)? = null, 37 | value: String = "", 38 | label: String, 39 | enabled: Boolean = true, 40 | keyboardType: KeyboardType = KeyboardType.Text, 41 | marginTop: Dp = 0.dp, 42 | marginBottom: Dp = 0.dp, 43 | marginLeft: Dp = 0.dp, 44 | marginRight: Dp = 0.dp 45 | ) { 46 | TextField( 47 | modifier = Modifier 48 | .fillMaxWidth() 49 | .wrapContentHeight() 50 | .padding(top = marginTop, bottom = marginBottom, start = marginLeft, end = marginRight), 51 | textStyle = TextStyle(color = MaterialTheme.colors.onPrimary), 52 | value = value, 53 | enabled = enabled, 54 | onValueChange = { onValueChange?.invoke(it) }, 55 | label = { Text(label) }, 56 | keyboardOptions = KeyboardOptions(keyboardType = keyboardType) 57 | ) 58 | } 59 | 60 | @Preview 61 | @Composable 62 | fun TextFieldPreview() { 63 | AppTextField( 64 | label = "Sample Text Field" 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/components/SketchbookScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.components 18 | 19 | import android.graphics.Bitmap 20 | import androidx.compose.foundation.Image 21 | import androidx.compose.foundation.layout.Box 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.graphics.Color 26 | import androidx.compose.ui.graphics.ImageBitmap 27 | import androidx.compose.ui.graphics.asAndroidBitmap 28 | import androidx.compose.ui.res.imageResource 29 | import androidx.compose.ui.unit.dp 30 | import io.getstream.sketchbook.Sketchbook 31 | import io.getstream.sketchbook.SketchbookController 32 | import io.getstream.streamdraw.R 33 | 34 | @Composable 35 | fun SketchbookScreen( 36 | modifier: Modifier, 37 | controller: SketchbookController, 38 | onEventListener: (bitmap: Bitmap) -> Unit 39 | ) { 40 | Box(modifier = modifier) { 41 | Image( 42 | modifier = Modifier 43 | .matchParentSize() 44 | .padding(horizontal = 20.dp), 45 | bitmap = ImageBitmap.imageResource(R.drawable.sketchbook_bg), 46 | contentDescription = null 47 | ) 48 | 49 | Sketchbook( 50 | modifier = Modifier 51 | .matchParentSize() 52 | .padding( 53 | start = 30.dp, 54 | top = 40.dp, 55 | end = 30.dp, 56 | bottom = 20.dp 57 | ), 58 | controller = controller, 59 | backgroundColor = Color.White, 60 | onPathListener = { 61 | val bitmap = controller.getSketchbookBitmap() 62 | onEventListener.invoke(bitmap.asAndroidBitmap()) 63 | } 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/GameDeletedScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import androidx.compose.foundation.layout.Box 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.foundation.layout.fillMaxSize 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.foundation.layout.wrapContentSize 24 | import androidx.compose.material.Button 25 | import androidx.compose.material.Text 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.Alignment 28 | import androidx.compose.ui.Modifier 29 | import androidx.compose.ui.res.stringResource 30 | import androidx.compose.ui.text.font.FontWeight 31 | import androidx.compose.ui.unit.dp 32 | import androidx.compose.ui.unit.sp 33 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 34 | import io.getstream.streamdraw.R 35 | 36 | @Composable 37 | fun GameDeletedScreen( 38 | exitGame: () -> Unit 39 | ) { 40 | Box( 41 | modifier = Modifier.fillMaxSize() 42 | ) { 43 | 44 | Column( 45 | modifier = Modifier 46 | .wrapContentSize() 47 | .padding(horizontal = 20.dp) 48 | .align(Alignment.Center) 49 | ) { 50 | Text( 51 | modifier = Modifier.align(Alignment.CenterHorizontally), 52 | text = stringResource(id = R.string.game_deleted), 53 | color = ChatTheme.colors.textHighEmphasis, 54 | fontSize = 24.sp, 55 | fontWeight = FontWeight.Bold 56 | ) 57 | 58 | Button( 59 | modifier = Modifier.align(Alignment.CenterHorizontally), 60 | onClick = { exitGame() } 61 | ) { 62 | Text( 63 | text = stringResource(id = R.string.exit_game), 64 | color = ChatTheme.colors.textHighEmphasis, 65 | fontSize = 23.sp 66 | ) 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/initializer/StreamChatInitializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.initializer 18 | 19 | import android.content.Context 20 | import androidx.startup.Initializer 21 | import io.getstream.chat.android.client.ChatClient 22 | import io.getstream.chat.android.client.logger.ChatLogLevel 23 | import io.getstream.chat.android.offline.model.message.attachments.UploadAttachmentsNetworkType 24 | import io.getstream.chat.android.offline.plugin.configuration.Config 25 | import io.getstream.chat.android.offline.plugin.factory.StreamOfflinePluginFactory 26 | import io.getstream.streamdraw.BuildConfig 27 | import io.getstream.streamdraw.R 28 | import timber.log.Timber 29 | 30 | /** 31 | * StreamChatInitializer initializes all Stream Client components. 32 | */ 33 | class StreamChatInitializer : Initializer { 34 | 35 | override fun create(context: Context) { 36 | Timber.d("StreamChatInitializer is initialized") 37 | 38 | /** 39 | * initialize a global instance of the [ChatClient]. 40 | * The ChatClient is the main entry point for all low-level operations on chat. 41 | * e.g, connect/disconnect user to the server, send/update/pin message, etc. 42 | */ 43 | val logLevel = if (BuildConfig.DEBUG) ChatLogLevel.ALL else ChatLogLevel.NOTHING 44 | val offlinePluginFactory = StreamOfflinePluginFactory( 45 | config = Config( 46 | backgroundSyncEnabled = true, 47 | userPresence = true, 48 | persistenceEnabled = true, 49 | uploadAttachmentsNetworkType = UploadAttachmentsNetworkType.NOT_ROAMING, 50 | ), 51 | appContext = context, 52 | ) 53 | ChatClient.Builder(context.getString(R.string.stream_api_key), context) 54 | .withPlugin(offlinePluginFactory) 55 | .logLevel(logLevel) 56 | .build() 57 | } 58 | 59 | override fun dependencies(): List>> = 60 | listOf(TimberInitializer::class.java) 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/NewGameMessage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import androidx.compose.animation.core.Spring 20 | import androidx.compose.animation.core.animateFloatAsState 21 | import androidx.compose.animation.core.spring 22 | import androidx.compose.foundation.layout.height 23 | import androidx.compose.foundation.layout.offset 24 | import androidx.compose.foundation.layout.padding 25 | import androidx.compose.material.Text 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.runtime.LaunchedEffect 28 | import androidx.compose.runtime.getValue 29 | import androidx.compose.runtime.mutableStateOf 30 | import androidx.compose.runtime.remember 31 | import androidx.compose.runtime.setValue 32 | import androidx.compose.ui.Modifier 33 | import androidx.compose.ui.text.style.TextAlign 34 | import androidx.compose.ui.unit.dp 35 | import androidx.compose.ui.unit.sp 36 | import io.getstream.streamdraw.states.AnimationState 37 | import io.getstream.streamdraw.ui.theme.hostAccent 38 | 39 | @Composable 40 | fun NewGameMessage( 41 | viewModel: GameViewModel 42 | ) { 43 | val newMessage = viewModel.newSingleMessage.value 44 | if (newMessage != null) { 45 | var animationState by remember { mutableStateOf(AnimationState.NONE) } 46 | val springValue: Float by animateFloatAsState( 47 | if (animationState == AnimationState.NONE) 0f else 1f, 48 | spring(dampingRatio = 0.3f, stiffness = Spring.StiffnessMediumLow) 49 | ) 50 | LaunchedEffect(newMessage) { 51 | animationState = AnimationState.START 52 | } 53 | Text( 54 | modifier = Modifier 55 | .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) 56 | .height(40.dp) 57 | .offset(x = (-200).dp + (200 * springValue).dp), 58 | text = "${newMessage.name}: ${newMessage.message}", 59 | textAlign = TextAlign.Center, 60 | fontSize = 16.sp, 61 | color = hostAccent, 62 | maxLines = 2, 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/DrawingScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import android.app.Activity 20 | import android.graphics.Bitmap 21 | import android.view.WindowManager 22 | import androidx.compose.foundation.Image 23 | import androidx.compose.foundation.background 24 | import androidx.compose.foundation.layout.Box 25 | import androidx.compose.foundation.layout.fillMaxSize 26 | import androidx.compose.foundation.layout.padding 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.runtime.LaunchedEffect 29 | import androidx.compose.ui.Modifier 30 | import androidx.compose.ui.graphics.ImageBitmap 31 | import androidx.compose.ui.graphics.asImageBitmap 32 | import androidx.compose.ui.platform.LocalContext 33 | import androidx.compose.ui.res.imageResource 34 | import androidx.compose.ui.unit.dp 35 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 36 | import io.getstream.streamdraw.R 37 | 38 | @Composable 39 | fun DrawingScreen( 40 | modifier: Modifier, 41 | bitmap: Bitmap? 42 | ) { 43 | Box(modifier = modifier) { 44 | Image( 45 | modifier = Modifier 46 | .fillMaxSize() 47 | .background(ChatTheme.colors.appBackground), 48 | bitmap = ImageBitmap.imageResource(R.drawable.sketchbook_bg), 49 | contentDescription = null 50 | ) 51 | 52 | if (bitmap != null) { 53 | val activity = LocalContext.current as Activity 54 | LaunchedEffect(Unit) { 55 | activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) 56 | } 57 | 58 | Image( 59 | modifier = modifier 60 | .fillMaxSize() 61 | .padding( 62 | start = 30.dp, 63 | top = 40.dp, 64 | end = 30.dp, 65 | bottom = 20.dp 66 | ), 67 | bitmap = bitmap.asImageBitmap(), 68 | contentDescription = null 69 | ) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/WordSelectionDialog.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import androidx.compose.foundation.layout.Column 20 | import androidx.compose.foundation.layout.fillMaxWidth 21 | import androidx.compose.foundation.layout.padding 22 | import androidx.compose.foundation.layout.wrapContentHeight 23 | import androidx.compose.material.Card 24 | import androidx.compose.material.Divider 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.res.stringResource 28 | import androidx.compose.ui.tooling.preview.Preview 29 | import androidx.compose.ui.unit.dp 30 | import androidx.compose.ui.window.Dialog 31 | import io.getstream.streamdraw.R 32 | import io.getstream.streamdraw.ui.components.SubtitleText 33 | import io.getstream.streamdraw.ui.components.TitleText 34 | import io.getstream.streamdraw.ui.theme.PrimaryColor 35 | import timber.log.Timber 36 | 37 | @Composable 38 | fun WordSelectionDialog( 39 | wordSelected: (selection: String) -> Unit, 40 | words: List 41 | ) { 42 | Dialog(onDismissRequest = { }) { 43 | WordSelectionDialogView(words = words, wordSelected) 44 | } 45 | } 46 | 47 | @Composable 48 | private fun WordSelectionDialogView( 49 | words: List, 50 | wordSelected: (selection: String) -> Unit 51 | ) { 52 | Card( 53 | modifier = Modifier 54 | .fillMaxWidth() 55 | .wrapContentHeight() 56 | ) { 57 | Column( 58 | modifier = Modifier 59 | .padding(24.dp) 60 | .fillMaxWidth() 61 | .wrapContentHeight() 62 | ) { 63 | TitleText(text = stringResource(id = R.string.select_a_word)) 64 | Column(modifier = Modifier.padding(top = 18.dp)) { 65 | words.forEach { 66 | SubtitleText( 67 | modifier = Modifier 68 | .fillMaxWidth() 69 | .padding(vertical = 8.dp), 70 | text = it, 71 | onClick = { wordSelected.invoke(it) } 72 | ) 73 | Divider(color = PrimaryColor, thickness = 1.dp) 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | @Preview 81 | @Composable 82 | private fun DefaultPreview() { 83 | WordSelectionDialogView(listOf("Pigeon", "Hammer", "Landslide")) { selection -> 84 | Timber.d(selection) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/GameActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import android.content.Context 20 | import android.content.Intent 21 | import android.os.Bundle 22 | import androidx.activity.ComponentActivity 23 | import androidx.activity.compose.setContent 24 | import androidx.activity.viewModels 25 | import androidx.compose.ui.platform.LocalContext 26 | import androidx.lifecycle.viewmodel.compose.viewModel 27 | import dagger.hilt.android.AndroidEntryPoint 28 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 29 | import io.getstream.chat.android.compose.viewmodel.messages.MessageListViewModel 30 | import io.getstream.chat.android.compose.viewmodel.messages.MessagesViewModelFactory 31 | import io.getstream.sketchbook.rememberSketchbookController 32 | import javax.inject.Inject 33 | 34 | @AndroidEntryPoint 35 | class GameActivity : ComponentActivity() { 36 | 37 | @set:Inject 38 | internal lateinit var gameViewModelFactory: GameViewModel.GameAssistedFactory 39 | private val cid by lazy { intent.getStringExtra(EXTRA_CID)!! } 40 | private val gameViewModel: GameViewModel by viewModels { 41 | GameViewModel.provideFactory(gameViewModelFactory, cid) 42 | } 43 | 44 | override fun onCreate(savedInstanceState: Bundle?) { 45 | super.onCreate(savedInstanceState) 46 | 47 | setContent { 48 | ChatTheme { 49 | // create a messages view model. 50 | val listViewModel = MessagesViewModelFactory( 51 | context = LocalContext.current, 52 | channelId = cid, 53 | enforceUniqueReactions = false 54 | ).let { 55 | viewModel(MessageListViewModel::class.java, factory = it) 56 | } 57 | 58 | GameScreen( 59 | gameViewModel = gameViewModel, 60 | listViewModel = listViewModel, 61 | exitGame = { finish() }, 62 | sketchbookController = rememberSketchbookController() 63 | ) 64 | } 65 | } 66 | } 67 | 68 | companion object { 69 | private const val EXTRA_CID: String = "EXTRA_CID" 70 | 71 | fun getIntent(context: Context, cid: String): Intent { 72 | return Intent(context, GameActivity::class.java).apply { 73 | putExtra(EXTRA_CID, cid) 74 | flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/group/CreateGroupForm.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.group 18 | 19 | import android.content.Context 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.fillMaxSize 23 | import androidx.compose.foundation.layout.padding 24 | import androidx.compose.material.CircularProgressIndicator 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.runtime.getValue 27 | import androidx.compose.runtime.mutableStateOf 28 | import androidx.compose.runtime.remember 29 | import androidx.compose.runtime.setValue 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.platform.LocalContext 33 | import androidx.compose.ui.res.stringResource 34 | import androidx.compose.ui.unit.dp 35 | import io.getstream.streamdraw.R 36 | import io.getstream.streamdraw.data.GameConnectionState 37 | import io.getstream.streamdraw.data.isLoading 38 | import io.getstream.streamdraw.extensions.toast 39 | import io.getstream.streamdraw.ui.components.AppTextField 40 | import io.getstream.streamdraw.ui.components.PrimaryButton 41 | import io.getstream.streamdraw.ui.screens.main.MainScreen 42 | import io.getstream.streamdraw.ui.screens.main.MainViewModel 43 | 44 | @Composable 45 | fun CreateGroupForm( 46 | viewModel: MainViewModel, 47 | connectionState: GameConnectionState 48 | ) { 49 | var displayName by remember { mutableStateOf("") } 50 | val context: Context = LocalContext.current 51 | 52 | Box( 53 | modifier = Modifier.fillMaxSize() 54 | ) { 55 | if (connectionState.isLoading) { 56 | CircularProgressIndicator( 57 | modifier = Modifier.align(Alignment.Center) 58 | ) 59 | } else { 60 | MainScreen { 61 | Column( 62 | modifier = Modifier 63 | .padding(start = 20.dp, end = 20.dp, bottom = 20.dp) 64 | ) { 65 | AppTextField( 66 | label = stringResource(id = R.string.display_name), 67 | marginTop = 16.dp, 68 | onValueChange = { displayName = it }, 69 | value = displayName 70 | ) 71 | 72 | PrimaryButton( 73 | onClick = { 74 | if (displayName.isNotEmpty()) { 75 | viewModel.createGameGroup(displayName = displayName) 76 | } else { 77 | context.toast(R.string.fill_all_blanks) 78 | } 79 | }, 80 | text = stringResource(id = R.string.create_group), 81 | marginTop = 24.dp 82 | ) 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/GameChatDialog.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.clickable 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.fillMaxWidth 23 | import androidx.compose.foundation.layout.height 24 | import androidx.compose.foundation.layout.padding 25 | import androidx.compose.foundation.layout.wrapContentSize 26 | import androidx.compose.foundation.lazy.LazyListState 27 | import androidx.compose.foundation.shape.RoundedCornerShape 28 | import androidx.compose.material.Text 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.runtime.MutableState 31 | import androidx.compose.ui.Alignment 32 | import androidx.compose.ui.Modifier 33 | import androidx.compose.ui.draw.clip 34 | import androidx.compose.ui.res.stringResource 35 | import androidx.compose.ui.text.font.FontWeight 36 | import androidx.compose.ui.unit.dp 37 | import androidx.compose.ui.unit.sp 38 | import androidx.compose.ui.window.Dialog 39 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 40 | import io.getstream.chat.android.compose.ui.util.rememberMessageListState 41 | import io.getstream.chat.android.compose.viewmodel.messages.MessageListViewModel 42 | import io.getstream.streamdraw.R 43 | import io.getstream.streamdraw.ui.screens.chat.ChatScreen 44 | 45 | @Composable 46 | fun GameChatDialog( 47 | expanded: MutableState, 48 | listViewModel: MessageListViewModel 49 | ) { 50 | if (expanded.value) { 51 | Dialog(onDismissRequest = { expanded.value = false }) { 52 | GameChatDialogContent( 53 | expanded, 54 | listViewModel 55 | ) 56 | } 57 | } 58 | } 59 | 60 | @Composable 61 | private fun GameChatDialogContent( 62 | expanded: MutableState, 63 | listViewModel: MessageListViewModel, 64 | lazyListState: LazyListState = rememberMessageListState(parentMessageId = listViewModel.currentMessagesState.parentMessageId) 65 | ) { 66 | Column( 67 | modifier = Modifier 68 | .wrapContentSize() 69 | .clip(RoundedCornerShape(8.dp)) 70 | .background(ChatTheme.colors.appBackground) 71 | ) { 72 | 73 | Text( 74 | modifier = Modifier 75 | .align(Alignment.CenterHorizontally) 76 | .padding(8.dp), 77 | text = stringResource(id = R.string.group_chat), 78 | color = ChatTheme.colors.textHighEmphasis, 79 | fontWeight = FontWeight.Bold, 80 | fontSize = 26.sp 81 | ) 82 | 83 | ChatScreen( 84 | modifier = Modifier 85 | .fillMaxWidth() 86 | .height(260.dp) 87 | .clickable { expanded.value = false }, 88 | lazyListState = lazyListState, 89 | currentState = listViewModel.currentMessagesState, 90 | onScrollToBottom = { listViewModel.clearNewMessageState() } 91 | ) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/components/Texts.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.components 18 | 19 | import androidx.compose.foundation.clickable 20 | import androidx.compose.foundation.layout.padding 21 | import androidx.compose.foundation.layout.wrapContentHeight 22 | import androidx.compose.foundation.layout.wrapContentWidth 23 | import androidx.compose.material.Text 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.graphics.Color 27 | import androidx.compose.ui.text.font.FontWeight 28 | import androidx.compose.ui.text.style.TextAlign 29 | import androidx.compose.ui.unit.Dp 30 | import androidx.compose.ui.unit.TextUnit 31 | import androidx.compose.ui.unit.dp 32 | import androidx.compose.ui.unit.sp 33 | import io.getstream.streamdraw.ui.theme.PrimaryColor 34 | 35 | @Composable 36 | fun TitleText( 37 | modifier: Modifier = Modifier, 38 | onClick: (() -> Unit)? = null, 39 | text: String, 40 | marginTop: Dp = 0.dp, 41 | marginBottom: Dp = 0.dp, 42 | marginLeft: Dp = 0.dp, 43 | marginRight: Dp = 0.dp 44 | ) { 45 | Text( 46 | text = text, 47 | modifier = modifier 48 | .clickable { onClick?.invoke() } 49 | .padding(top = marginTop, bottom = marginBottom, start = marginLeft, end = marginRight), 50 | fontSize = 32.sp, 51 | color = PrimaryColor, 52 | fontWeight = FontWeight.Bold, 53 | textAlign = TextAlign.Center 54 | ) 55 | } 56 | 57 | @Composable 58 | fun SubtitleText( 59 | modifier: Modifier = Modifier, 60 | onClick: (() -> Unit)? = null, 61 | text: String, 62 | marginTop: Dp = 0.dp, 63 | marginBottom: Dp = 0.dp, 64 | marginLeft: Dp = 0.dp, 65 | marginRight: Dp = 0.dp 66 | ) { 67 | Text( 68 | text = text, 69 | modifier = modifier 70 | .clickable { onClick?.invoke() } 71 | .padding(top = marginTop, bottom = marginBottom, start = marginLeft, end = marginRight), 72 | fontSize = 18.sp, 73 | color = PrimaryColor, 74 | fontWeight = FontWeight.Normal, 75 | textAlign = TextAlign.Center 76 | ) 77 | } 78 | 79 | @Composable 80 | fun NormalText( 81 | modifier: Modifier = Modifier, 82 | onClick: (() -> Unit)? = null, 83 | text: String, 84 | size: TextUnit, 85 | align: TextAlign = TextAlign.Left, 86 | color: Color = PrimaryColor, 87 | weight: FontWeight = FontWeight.Normal, 88 | marginTop: Dp = 0.dp, 89 | marginBottom: Dp = 0.dp, 90 | marginLeft: Dp = 0.dp, 91 | marginRight: Dp = 0.dp 92 | ) { 93 | Text( 94 | text = text, 95 | modifier = modifier 96 | .wrapContentWidth() 97 | .wrapContentHeight() 98 | .clickable { onClick?.invoke() } 99 | .padding(top = marginTop, bottom = marginBottom, start = marginLeft, end = marginRight), 100 | fontSize = size, 101 | color = color, 102 | fontWeight = weight, 103 | textAlign = align 104 | ) 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/group/JoinGroupForm.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.group 18 | 19 | import androidx.compose.foundation.layout.Box 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.foundation.layout.fillMaxSize 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.material.CircularProgressIndicator 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.getValue 26 | import androidx.compose.runtime.mutableStateOf 27 | import androidx.compose.runtime.remember 28 | import androidx.compose.runtime.setValue 29 | import androidx.compose.ui.Alignment 30 | import androidx.compose.ui.Modifier 31 | import androidx.compose.ui.platform.LocalContext 32 | import androidx.compose.ui.res.stringResource 33 | import androidx.compose.ui.unit.dp 34 | import io.getstream.streamdraw.R 35 | import io.getstream.streamdraw.data.GameConnectionState 36 | import io.getstream.streamdraw.data.isLoading 37 | import io.getstream.streamdraw.extensions.toast 38 | import io.getstream.streamdraw.ui.components.AppTextField 39 | import io.getstream.streamdraw.ui.components.SecondaryButton 40 | import io.getstream.streamdraw.ui.screens.main.MainScreen 41 | import io.getstream.streamdraw.ui.screens.main.MainViewModel 42 | 43 | @Composable 44 | fun JoinGroupForm( 45 | viewModel: MainViewModel, 46 | connectionState: GameConnectionState 47 | ) { 48 | var displayName by remember { mutableStateOf("") } 49 | var groupCode by remember { mutableStateOf("") } 50 | val context = LocalContext.current 51 | 52 | Box( 53 | modifier = Modifier.fillMaxSize() 54 | ) { 55 | if (connectionState.isLoading) { 56 | CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) 57 | } else { 58 | MainScreen { 59 | Column( 60 | modifier = Modifier 61 | .padding(start = 20.dp, end = 20.dp, bottom = 20.dp) 62 | ) { 63 | AppTextField( 64 | label = stringResource(id = R.string.display_name), 65 | onValueChange = { displayName = it }, 66 | value = displayName, 67 | marginTop = 16.dp 68 | ) 69 | AppTextField( 70 | label = stringResource(id = R.string.group_code), 71 | onValueChange = { groupCode = it }, 72 | value = groupCode, 73 | marginTop = 16.dp 74 | ) 75 | SecondaryButton( 76 | text = stringResource(id = R.string.join_group), 77 | onClick = { 78 | if (displayName.isNotEmpty() && groupCode.isNotEmpty()) { 79 | viewModel.joinGameGroup(displayName, groupCode) 80 | } else { 81 | context.toast(R.string.fill_all_blanks) 82 | } 83 | }, 84 | marginTop = 24.dp 85 | ) 86 | } 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'dagger.hilt.android.plugin' 6 | } 7 | 8 | apply from: "$rootDir/dependencies.gradle" 9 | 10 | android { 11 | compileSdk versions.compileSdk 12 | defaultConfig { 13 | applicationId "io.getstream.streamdraw" 14 | minSdk versions.minSdk 15 | targetSdk versions.compileSdk 16 | versionCode versions.versionCode 17 | versionName versions.versionName 18 | vectorDrawables.useSupportLibrary = true 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | kotlinOptions { 25 | jvmTarget = '1.8' 26 | } 27 | buildFeatures { 28 | compose true 29 | } 30 | composeOptions { 31 | kotlinCompilerExtensionVersion versions.composeCompiler 32 | } 33 | packagingOptions { 34 | resources { 35 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 36 | } 37 | } 38 | lintOptions { 39 | abortOnError false 40 | } 41 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 42 | kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"] 43 | kotlinOptions.freeCompilerArgs += ["-Xopt-in=androidx.compose.ui.ExperimentalComposeUiApi"] 44 | kotlinOptions.freeCompilerArgs += ["-Xopt-in=androidx.compose.material.ExperimentalMaterialApi"] 45 | } 46 | } 47 | 48 | dependencies { 49 | // androidx 50 | implementation "androidx.activity:activity-ktx:$versions.activity" 51 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$versions.lifecycle" 52 | 53 | // jetpack compose 54 | implementation "androidx.compose.ui:ui:$versions.compose" 55 | implementation "androidx.compose.material:material:$versions.compose" 56 | implementation "androidx.compose.ui:ui-tooling-preview:$versions.compose" 57 | implementation "androidx.activity:activity-compose:$versions.activity" 58 | implementation "androidx.navigation:navigation-compose:$versions.composeNavigation" 59 | implementation "androidx.constraintlayout:constraintlayout-compose:$versions.constraintLayout" 60 | debugImplementation "androidx.compose.ui:ui-tooling:$versions.compose" 61 | 62 | // sketchbook 63 | implementation "io.getstream:sketchbook:$versions.sketchbook" 64 | 65 | // Stream Chat Compose SDK 66 | implementation "io.getstream:stream-chat-android-compose:$versions.streamChatSDK" 67 | 68 | // network 69 | implementation "com.squareup.retrofit2:retrofit:$versions.retrofitVersion" 70 | implementation "com.squareup.retrofit2:converter-moshi:$versions.retrofitVersion" 71 | 72 | // image loading 73 | implementation "com.github.skydoves:landscapist-glide:$versions.landscapist" 74 | 75 | // moshi 76 | implementation "com.squareup.moshi:moshi-kotlin:$versions.moshiVersion" 77 | kapt "com.squareup.moshi:moshi-kotlin-codegen:$versions.moshiVersion" 78 | 79 | // hilt 80 | implementation "com.google.dagger:hilt-android:$versions.hilt" 81 | implementation "androidx.hilt:hilt-navigation-compose:$versions.hiltComposeNavigation" 82 | kapt "com.google.dagger:hilt-compiler:$versions.hilt" 83 | 84 | // startup 85 | implementation "androidx.startup:startup-runtime:$versions.startupVersion" 86 | 87 | // konfetti 88 | implementation "nl.dionsegijn:konfetti-compose:$versions.konfetti" 89 | 90 | // logging 91 | implementation "com.jakewharton.timber:timber:$versions.timberVersion" 92 | 93 | // firebase 94 | implementation "com.google.firebase:firebase-database:$versions.firebase" 95 | } 96 | 97 | apply from: "$rootDir/spotless.gradle" 98 | 99 | if (file("google-services.json").exists()) { 100 | apply plugin: "com.google.gms.google-services" 101 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/components/Buttons.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.components 18 | 19 | import androidx.compose.foundation.layout.PaddingValues 20 | import androidx.compose.foundation.layout.fillMaxWidth 21 | import androidx.compose.foundation.layout.padding 22 | import androidx.compose.foundation.layout.wrapContentHeight 23 | import androidx.compose.material.Button 24 | import androidx.compose.material.ButtonDefaults 25 | import androidx.compose.material.Text 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.graphics.Color 29 | import androidx.compose.ui.tooling.preview.Preview 30 | import androidx.compose.ui.unit.Dp 31 | import androidx.compose.ui.unit.dp 32 | import io.getstream.streamdraw.ui.theme.DefaultButtonColor 33 | import io.getstream.streamdraw.ui.theme.DefaultTextColor 34 | import io.getstream.streamdraw.ui.theme.LightTextColor 35 | import io.getstream.streamdraw.ui.theme.PrimaryButtonColor 36 | import io.getstream.streamdraw.ui.theme.SecondaryButtonColor 37 | 38 | @Composable 39 | fun PrimaryButton( 40 | onClick: (() -> Unit)? = null, 41 | text: String, 42 | enabled: Boolean = true, 43 | marginTop: Dp = 0.dp, 44 | marginBottom: Dp = 0.dp, 45 | marginLeft: Dp = 0.dp, 46 | marginRight: Dp = 0.dp 47 | ) { 48 | AppButton( 49 | onClick = onClick, 50 | text = text, 51 | enabled = enabled, 52 | backgroundColor = PrimaryButtonColor, 53 | textColor = LightTextColor, 54 | marginTop = marginTop, 55 | marginBottom = marginBottom, 56 | marginLeft = marginLeft, 57 | marginRight = marginRight 58 | ) 59 | } 60 | 61 | @Composable 62 | fun SecondaryButton( 63 | onClick: (() -> Unit)? = null, 64 | text: String, 65 | enabled: Boolean = true, 66 | marginTop: Dp = 0.dp, 67 | marginBottom: Dp = 0.dp, 68 | marginLeft: Dp = 0.dp, 69 | marginRight: Dp = 0.dp 70 | ) { 71 | AppButton( 72 | onClick = onClick, 73 | text = text, 74 | enabled = enabled, 75 | backgroundColor = SecondaryButtonColor, 76 | textColor = DefaultTextColor, 77 | marginTop = marginTop, 78 | marginBottom = marginBottom, 79 | marginLeft = marginLeft, 80 | marginRight = marginRight 81 | ) 82 | } 83 | 84 | @Composable 85 | private fun AppButton( 86 | onClick: (() -> Unit)? = null, 87 | text: String, 88 | enabled: Boolean = true, 89 | backgroundColor: Color = DefaultButtonColor, 90 | textColor: Color = DefaultTextColor, 91 | marginTop: Dp = 0.dp, 92 | marginBottom: Dp = 0.dp, 93 | marginLeft: Dp = 0.dp, 94 | marginRight: Dp = 0.dp 95 | ) { 96 | Button( 97 | onClick = { onClick?.invoke() }, 98 | modifier = Modifier 99 | .fillMaxWidth() 100 | .wrapContentHeight() 101 | .padding(top = marginTop, bottom = marginBottom, start = marginLeft, end = marginRight), 102 | enabled = enabled, 103 | contentPadding = PaddingValues(vertical = 24.dp), 104 | colors = ButtonDefaults.buttonColors( 105 | backgroundColor = backgroundColor, 106 | ) 107 | ) { 108 | Text( 109 | text = text, 110 | color = textColor 111 | ) 112 | } 113 | } 114 | 115 | @Preview 116 | @Composable 117 | fun ButtonPreview() { 118 | SecondaryButton(text = "Sample Button") 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/main/MainScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.main 18 | 19 | import androidx.compose.foundation.Image 20 | import androidx.compose.foundation.background 21 | import androidx.compose.foundation.layout.Box 22 | import androidx.compose.foundation.layout.Column 23 | import androidx.compose.foundation.layout.Row 24 | import androidx.compose.foundation.layout.fillMaxSize 25 | import androidx.compose.foundation.layout.fillMaxWidth 26 | import androidx.compose.foundation.layout.height 27 | import androidx.compose.foundation.layout.padding 28 | import androidx.compose.foundation.layout.width 29 | import androidx.compose.material.MaterialTheme 30 | import androidx.compose.material.Text 31 | import androidx.compose.runtime.Composable 32 | import androidx.compose.ui.Alignment 33 | import androidx.compose.ui.Modifier 34 | import androidx.compose.ui.graphics.ImageBitmap 35 | import androidx.compose.ui.res.imageResource 36 | import androidx.compose.ui.res.stringResource 37 | import androidx.compose.ui.text.font.FontWeight 38 | import androidx.compose.ui.unit.dp 39 | import androidx.compose.ui.unit.sp 40 | import androidx.constraintlayout.compose.ConstraintLayout 41 | import io.getstream.streamdraw.R 42 | 43 | @Composable 44 | fun MainScreen( 45 | content: @Composable () -> Unit 46 | ) { 47 | 48 | ConstraintLayout( 49 | modifier = Modifier 50 | .background(MaterialTheme.colors.background) 51 | .fillMaxSize() 52 | ) { 53 | val (logo, image, contents) = createRefs() 54 | 55 | Row( 56 | modifier = Modifier.constrainAs(logo) { 57 | start.linkTo(parent.start) 58 | end.linkTo(parent.end) 59 | top.linkTo(parent.top) 60 | bottom.linkTo(image.top) 61 | } 62 | ) { 63 | Image( 64 | modifier = Modifier 65 | .width(140.dp) 66 | .height(65.dp), 67 | bitmap = ImageBitmap.imageResource(R.drawable.stream_logo), 68 | contentDescription = null 69 | ) 70 | 71 | Text( 72 | modifier = Modifier.align(Alignment.CenterVertically), 73 | text = stringResource(R.string.app_name), 74 | color = MaterialTheme.colors.onPrimary, 75 | fontSize = 36.sp, 76 | fontWeight = FontWeight.Bold 77 | ) 78 | } 79 | 80 | Box( 81 | modifier = Modifier 82 | .constrainAs(image) { 83 | start.linkTo(parent.start) 84 | end.linkTo(parent.end) 85 | top.linkTo(logo.bottom) 86 | bottom.linkTo(contents.top) 87 | } 88 | .fillMaxWidth() 89 | .height(320.dp) 90 | ) { 91 | Image( 92 | modifier = Modifier.fillMaxSize(), 93 | bitmap = ImageBitmap.imageResource(R.drawable.sketchbook_bg), 94 | contentDescription = null 95 | ) 96 | 97 | Image( 98 | modifier = Modifier 99 | .fillMaxSize() 100 | .padding(vertical = 30.dp), 101 | bitmap = ImageBitmap.imageResource(R.drawable.drawing_content), 102 | contentDescription = null 103 | ) 104 | } 105 | 106 | Column( 107 | modifier = Modifier 108 | .constrainAs(contents) { 109 | start.linkTo(parent.start) 110 | end.linkTo(parent.end) 111 | top.linkTo(image.bottom) 112 | bottom.linkTo(parent.bottom) 113 | } 114 | ) { 115 | content() 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/group/GroupEntranceSheetContent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.group 18 | 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.fillMaxSize 23 | import androidx.compose.foundation.layout.padding 24 | import androidx.compose.foundation.layout.wrapContentSize 25 | import androidx.compose.foundation.shape.RoundedCornerShape 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.Alignment 28 | import androidx.compose.ui.Modifier 29 | import androidx.compose.ui.platform.LocalContext 30 | import androidx.compose.ui.res.stringResource 31 | import androidx.compose.ui.text.font.FontWeight 32 | import androidx.compose.ui.text.style.TextAlign 33 | import androidx.compose.ui.unit.dp 34 | import androidx.compose.ui.unit.sp 35 | import io.getstream.chat.android.client.models.Channel 36 | import io.getstream.streamdraw.R 37 | import io.getstream.streamdraw.extensions.groupId 38 | import io.getstream.streamdraw.extensions.setClipboard 39 | import io.getstream.streamdraw.ui.components.NormalText 40 | import io.getstream.streamdraw.ui.components.PrimaryButton 41 | import io.getstream.streamdraw.ui.components.SubtitleText 42 | import io.getstream.streamdraw.ui.components.TitleText 43 | import io.getstream.streamdraw.ui.screens.game.GameActivity 44 | import io.getstream.streamdraw.ui.theme.LightColor 45 | import io.getstream.streamdraw.ui.theme.PrimaryColor 46 | 47 | @Composable 48 | fun GroupEntranceSheetContent(channel: Channel?) { 49 | val context = LocalContext.current 50 | Box( 51 | Modifier 52 | .padding(16.dp) 53 | .fillMaxSize() 54 | ) { 55 | if (channel != null) { 56 | Column( 57 | horizontalAlignment = Alignment.CenterHorizontally 58 | ) { 59 | TitleText( 60 | text = stringResource(id = R.string.group_entrance), 61 | marginTop = 12.dp, 62 | marginBottom = 12.dp 63 | ) 64 | SubtitleText( 65 | text = stringResource(id = R.string.group_created_desc), 66 | marginTop = 8.dp, 67 | marginBottom = 18.dp 68 | ) 69 | Box( 70 | modifier = Modifier 71 | .background(PrimaryColor, shape = RoundedCornerShape(8.dp)) 72 | .wrapContentSize() 73 | .padding(12.dp) 74 | ) { 75 | NormalText( 76 | text = channel.groupId, 77 | size = 32.sp, 78 | weight = FontWeight.Bold, 79 | align = TextAlign.Center, 80 | color = LightColor, 81 | onClick = { context.setClipboard(channel.groupId) } 82 | ) 83 | } 84 | NormalText( 85 | text = stringResource(id = R.string.invite_people), 86 | size = 16.sp, 87 | weight = FontWeight.Normal, 88 | align = TextAlign.Center, 89 | marginTop = 16.dp, 90 | onClick = { context.setClipboard(channel.groupId) } 91 | ) 92 | PrimaryButton( 93 | text = stringResource(id = R.string.continue_to_game), 94 | marginTop = 24.dp, 95 | marginBottom = 24.dp, 96 | onClick = { 97 | context.startActivity(GameActivity.getIntent(context, channel.cid)) 98 | } 99 | ) 100 | } 101 | } else { 102 | SubtitleText( 103 | modifier = Modifier.align(Alignment.Center), 104 | text = stringResource(id = R.string.group_created_failed), 105 | ) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/GameWinnerScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import androidx.compose.animation.core.Spring 20 | import androidx.compose.animation.core.animateFloatAsState 21 | import androidx.compose.animation.core.spring 22 | import androidx.compose.foundation.layout.Box 23 | import androidx.compose.foundation.layout.fillMaxSize 24 | import androidx.compose.foundation.layout.padding 25 | import androidx.compose.material.Text 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.runtime.LaunchedEffect 28 | import androidx.compose.runtime.collectAsState 29 | import androidx.compose.runtime.getValue 30 | import androidx.compose.runtime.mutableStateOf 31 | import androidx.compose.runtime.remember 32 | import androidx.compose.runtime.setValue 33 | import androidx.compose.ui.Alignment 34 | import androidx.compose.ui.Modifier 35 | import androidx.compose.ui.draw.scale 36 | import androidx.compose.ui.platform.LocalSoftwareKeyboardController 37 | import androidx.compose.ui.res.stringResource 38 | import androidx.compose.ui.text.font.FontWeight 39 | import androidx.compose.ui.text.style.TextAlign 40 | import androidx.compose.ui.unit.dp 41 | import androidx.compose.ui.unit.sp 42 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 43 | import io.getstream.streamdraw.R 44 | import io.getstream.streamdraw.states.AnimationState 45 | import nl.dionsegijn.konfetti.compose.KonfettiView 46 | import nl.dionsegijn.konfetti.compose.OnParticleSystemUpdateListener 47 | import nl.dionsegijn.konfetti.core.Angle 48 | import nl.dionsegijn.konfetti.core.Party 49 | import nl.dionsegijn.konfetti.core.PartySystem 50 | import nl.dionsegijn.konfetti.core.Position 51 | import nl.dionsegijn.konfetti.core.Spread 52 | import nl.dionsegijn.konfetti.core.emitter.Emitter 53 | import java.util.concurrent.TimeUnit 54 | 55 | @Composable 56 | fun GameWinnerScreen( 57 | gameViewModel: GameViewModel 58 | ) { 59 | val isWinner = gameViewModel.isWinner.collectAsState(initial = false) 60 | val keyboardController = LocalSoftwareKeyboardController.current 61 | if (isWinner.value) { 62 | Box(modifier = Modifier.fillMaxSize()) { 63 | 64 | KonfettiView( 65 | modifier = Modifier.fillMaxSize(), 66 | parties = parties(), 67 | updateListener = object : OnParticleSystemUpdateListener { 68 | override fun onParticleSystemEnded(system: PartySystem, activeSystems: Int) { 69 | gameViewModel.finishWinnerAnimation() 70 | } 71 | } 72 | ) 73 | 74 | var animationState by remember { mutableStateOf(AnimationState.NONE) } 75 | val springValue: Float by animateFloatAsState( 76 | if (animationState == AnimationState.NONE) 0f else 1f, 77 | spring(dampingRatio = 0.3f, stiffness = Spring.StiffnessMediumLow) 78 | ) 79 | LaunchedEffect(Unit) { 80 | animationState = AnimationState.START 81 | keyboardController?.hide() 82 | } 83 | Text( 84 | modifier = Modifier 85 | .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) 86 | .align(Alignment.Center) 87 | .scale(springValue), 88 | text = stringResource(id = R.string.game_winner), 89 | color = ChatTheme.colors.textHighEmphasis, 90 | textAlign = TextAlign.Center, 91 | fontWeight = FontWeight.Bold, 92 | fontSize = 30.sp, 93 | maxLines = 2, 94 | ) 95 | } 96 | } 97 | } 98 | 99 | private fun parties(): List { 100 | val parade = Party( 101 | speed = 10f, 102 | maxSpeed = 30f, 103 | damping = 0.9f, 104 | angle = Angle.RIGHT - 45, 105 | spread = Spread.SMALL, 106 | colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def), 107 | emitter = Emitter(duration = 3, TimeUnit.SECONDS).perSecond(30), 108 | position = Position.Relative(0.0, 0.5) 109 | ) 110 | 111 | val explode = Party( 112 | speed = 0f, 113 | maxSpeed = 30f, 114 | damping = 0.9f, 115 | spread = 360, 116 | colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def), 117 | emitter = Emitter(duration = 100, TimeUnit.MILLISECONDS).max(100), 118 | position = Position.Relative(0.5, 0.3) 119 | ) 120 | 121 | return listOf( 122 | parade, 123 | parade.copy( 124 | angle = parade.angle - 90, // flip angle from right to left 125 | position = Position.Relative(1.0, 0.5) 126 | ), 127 | explode 128 | ) 129 | } 130 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | GetStream. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.main 18 | 19 | import androidx.lifecycle.ViewModel 20 | import androidx.lifecycle.viewModelScope 21 | import dagger.hilt.android.lifecycle.HiltViewModel 22 | import io.getstream.chat.android.client.ChatClient 23 | import io.getstream.chat.android.client.models.Channel 24 | import io.getstream.chat.android.client.models.ConnectionData 25 | import io.getstream.chat.android.client.models.User 26 | import io.getstream.chat.android.client.utils.Result 27 | import io.getstream.streamdraw.data.GameConnectionState 28 | import io.getstream.streamdraw.extensions.groupName 29 | import io.getstream.streamdraw.extensions.image 30 | import io.getstream.streamdraw.extensions.toChannelId 31 | import io.getstream.streamdraw.persistence.AppPreferences 32 | import io.getstream.streamdraw.ui.screens.game.GameStatus 33 | import io.getstream.streamdraw.utils.CHANNEL_MESSAGING 34 | import io.getstream.streamdraw.utils.KEY_GAME_STATUS 35 | import io.getstream.streamdraw.utils.KEY_HOST_NAME 36 | import io.getstream.streamdraw.utils.KEY_NAME 37 | import io.getstream.streamdraw.utils.generateGroupId 38 | import io.getstream.streamdraw.utils.generateUserId 39 | import kotlinx.coroutines.flow.Flow 40 | import kotlinx.coroutines.flow.MutableStateFlow 41 | import kotlinx.coroutines.flow.StateFlow 42 | import kotlinx.coroutines.flow.filterIsInstance 43 | import kotlinx.coroutines.flow.mapNotNull 44 | import kotlinx.coroutines.launch 45 | import javax.inject.Inject 46 | 47 | @HiltViewModel 48 | class MainViewModel @Inject constructor( 49 | private val prefs: AppPreferences, 50 | private val chatClient: ChatClient 51 | ) : ViewModel() { 52 | 53 | private val userId: String 54 | get() = prefs.userId ?: generateUserId().also { prefs.userId = it } 55 | 56 | private val _gameConnectionState = 57 | MutableStateFlow(GameConnectionState.None) 58 | val gameConnectionState: StateFlow = _gameConnectionState 59 | 60 | val connectedChannel: Flow = 61 | _gameConnectionState.filterIsInstance() 62 | .mapNotNull { it.channel } 63 | 64 | /** Connect a user to the Stream server. */ 65 | private suspend fun connectUser(displayName: String): Result { 66 | val currentUser = chatClient.getCurrentUser() 67 | if (currentUser != null) { 68 | chatClient.disconnect(true) 69 | } 70 | val user = User( 71 | id = userId, 72 | name = displayName, 73 | image = displayName.image 74 | ) 75 | val token = chatClient.devToken(userId) 76 | return chatClient 77 | .connectUser(user, token) 78 | .await() 79 | } 80 | 81 | /** create a new channel. */ 82 | private suspend fun createChannel( 83 | groupId: String, 84 | displayName: String, 85 | ): Result { 86 | return chatClient.createChannel( 87 | channelType = CHANNEL_MESSAGING, 88 | channelId = groupId, 89 | memberIds = listOf(userId), 90 | extraData = mapOf( 91 | KEY_NAME to displayName.groupName, 92 | KEY_HOST_NAME to displayName, 93 | KEY_GAME_STATUS to GameStatus.START.status 94 | ) 95 | ).await() 96 | } 97 | 98 | /** create a new game group with the [displayName] as a host. */ 99 | fun createGameGroup(displayName: String) { 100 | viewModelScope.launch { 101 | val connection = connectUser(displayName) 102 | if (connection.isSuccess) { 103 | _gameConnectionState.emit(GameConnectionState.Loading) 104 | val groupId = generateGroupId() 105 | val result = createChannel(groupId, displayName) 106 | if (result.isSuccess) { 107 | _gameConnectionState.emit(GameConnectionState.Success(result.data())) 108 | } else { 109 | _gameConnectionState.emit(GameConnectionState.Failure(result.error())) 110 | } 111 | } 112 | } 113 | } 114 | 115 | /** join the game group with the [displayName] and specific [groupId]. */ 116 | fun joinGameGroup(displayName: String, groupId: String) { 117 | viewModelScope.launch { 118 | val connection = connectUser(displayName) 119 | if (connection.isSuccess) { 120 | _gameConnectionState.emit(GameConnectionState.Loading) 121 | val channelClient = chatClient.channel(groupId.toChannelId()) 122 | val result = channelClient.addMembers(listOf(userId)).await() 123 | if (result.isSuccess) { 124 | _gameConnectionState.emit(GameConnectionState.Success(result.data())) 125 | } else { 126 | _gameConnectionState.emit(GameConnectionState.Failure(result.error())) 127 | } 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/game/GameDrawing.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.game 18 | 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.foundation.layout.fillMaxSize 22 | import androidx.compose.foundation.layout.fillMaxWidth 23 | import androidx.compose.foundation.layout.padding 24 | import androidx.compose.foundation.layout.size 25 | import androidx.compose.foundation.lazy.LazyListState 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.runtime.collectAsState 28 | import androidx.compose.runtime.getValue 29 | import androidx.compose.ui.Modifier 30 | import androidx.compose.ui.graphics.ImageBitmap 31 | import androidx.compose.ui.res.imageResource 32 | import androidx.compose.ui.unit.dp 33 | import androidx.hilt.navigation.compose.hiltViewModel 34 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 35 | import io.getstream.chat.android.compose.ui.util.rememberMessageListState 36 | import io.getstream.chat.android.compose.viewmodel.messages.MessageListViewModel 37 | import io.getstream.sketchbook.ColorPickerPaletteIcon 38 | import io.getstream.sketchbook.PaintColorPalette 39 | import io.getstream.sketchbook.SketchbookController 40 | import io.getstream.streamdraw.R 41 | import io.getstream.streamdraw.extensions.toBitmap 42 | import io.getstream.streamdraw.ui.components.SketchbookControlMenu 43 | import io.getstream.streamdraw.ui.components.SketchbookScreen 44 | import io.getstream.streamdraw.ui.components.TitleText 45 | import io.getstream.streamdraw.ui.screens.chat.ChatScreen 46 | 47 | @Composable 48 | fun GameDrawing( 49 | isHost: Boolean, 50 | gameViewModel: GameViewModel, 51 | listViewModel: MessageListViewModel, 52 | sketchbookController: SketchbookController, 53 | exitGame: () -> Unit 54 | ) { 55 | val gameStatus = gameViewModel.gameStatus.value 56 | val newDrawingImage = gameViewModel.newDrawingImage.value 57 | 58 | Column( 59 | modifier = Modifier 60 | .fillMaxWidth() 61 | .background(ChatTheme.colors.appBackground) 62 | ) { 63 | if (isHost && gameStatus == GameStatus.START) { 64 | GameDrawingHost(viewModel = gameViewModel, sketchbookController = sketchbookController) 65 | } else if (gameStatus != GameStatus.DELETED) { 66 | if (isHost) { 67 | GameRestartButton(gameViewModel = gameViewModel) 68 | } 69 | GameDrawingGuest( 70 | drawingImage = newDrawingImage, 71 | gameViewModel = gameViewModel, 72 | listViewModel = listViewModel 73 | ) 74 | } else { 75 | GameDeletedScreen(exitGame = exitGame) 76 | } 77 | } 78 | 79 | GameExitDialog(gameViewModel = gameViewModel) 80 | } 81 | 82 | @Composable 83 | private fun GameDrawingHost( 84 | viewModel: GameViewModel, 85 | sketchbookController: SketchbookController 86 | ) { 87 | val randomWords by viewModel.randomWords.collectAsState() 88 | val selectedWord by viewModel.selectedWord.collectAsState() 89 | if (randomWords != null && selectedWord == null) { 90 | WordSelectionDialog( 91 | words = randomWords!!, 92 | wordSelected = { selection -> viewModel.setSelectedWord(selection) } 93 | ) 94 | } else { 95 | Column { 96 | if (selectedWord != null) { 97 | TitleText( 98 | text = selectedWord!!, 99 | modifier = Modifier 100 | .fillMaxWidth() 101 | .padding(vertical = 12.dp), 102 | ) 103 | } 104 | 105 | SketchbookScreen( 106 | modifier = Modifier 107 | .fillMaxSize() 108 | .weight(1f, fill = false), 109 | controller = sketchbookController, 110 | onEventListener = { bitmap -> 111 | viewModel.broadcastToChannel(bitmap) 112 | } 113 | ) 114 | 115 | PaintColorPalette( 116 | modifier = Modifier.padding(6.dp), 117 | controller = sketchbookController, 118 | initialSelectedIndex = 3, 119 | onColorSelected = { _, _ -> sketchbookController.setPaintShader(null) }, 120 | header = { 121 | ColorPickerPaletteIcon( 122 | modifier = Modifier 123 | .size(60.dp) 124 | .padding(6.dp), 125 | controller = sketchbookController, 126 | bitmap = ImageBitmap.imageResource(R.drawable.palette) 127 | ) 128 | } 129 | ) 130 | 131 | SketchbookControlMenu(controller = sketchbookController) 132 | 133 | NewGameMessage(viewModel = hiltViewModel()) 134 | } 135 | } 136 | } 137 | 138 | @Composable 139 | private fun GameDrawingGuest( 140 | drawingImage: String?, 141 | gameViewModel: GameViewModel, 142 | listViewModel: MessageListViewModel, 143 | lazyListState: LazyListState = rememberMessageListState(parentMessageId = listViewModel.currentMessagesState.parentMessageId) 144 | ) { 145 | Column(modifier = Modifier.fillMaxSize()) { 146 | 147 | DrawingScreen( 148 | modifier = Modifier 149 | .fillMaxSize() 150 | .weight(0.9f), 151 | bitmap = drawingImage?.toBitmap() 152 | ) 153 | 154 | PlayerScreen(gameViewModel.members.value) 155 | 156 | ChatScreen( 157 | modifier = Modifier 158 | .fillMaxSize() 159 | .weight(0.85f), 160 | lazyListState = lazyListState, 161 | currentState = listViewModel.currentMessagesState, 162 | onScrollToBottom = { listViewModel.clearNewMessageState() } 163 | ) 164 | 165 | GameWinnerScreen(gameViewModel = gameViewModel) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/components/SketchbookControlMenu.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.components 18 | 19 | import android.widget.Toast 20 | import androidx.compose.foundation.Image 21 | import androidx.compose.foundation.background 22 | import androidx.compose.foundation.clickable 23 | import androidx.compose.foundation.layout.Arrangement 24 | import androidx.compose.foundation.layout.Box 25 | import androidx.compose.foundation.layout.Row 26 | import androidx.compose.foundation.layout.fillMaxWidth 27 | import androidx.compose.foundation.layout.padding 28 | import androidx.compose.foundation.layout.size 29 | import androidx.compose.material.DropdownMenu 30 | import androidx.compose.material.DropdownMenuItem 31 | import androidx.compose.material.Icon 32 | import androidx.compose.material.Text 33 | import androidx.compose.runtime.Composable 34 | import androidx.compose.runtime.mutableStateOf 35 | import androidx.compose.runtime.remember 36 | import androidx.compose.ui.Alignment 37 | import androidx.compose.ui.Modifier 38 | import androidx.compose.ui.graphics.ImageBitmap 39 | import androidx.compose.ui.graphics.PathEffect 40 | import androidx.compose.ui.graphics.vector.ImageVector 41 | import androidx.compose.ui.platform.LocalContext 42 | import androidx.compose.ui.res.imageResource 43 | import androidx.compose.ui.res.vectorResource 44 | import androidx.compose.ui.unit.dp 45 | import androidx.hilt.navigation.compose.hiltViewModel 46 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 47 | import io.getstream.sketchbook.SketchbookController 48 | import io.getstream.streamdraw.R 49 | import io.getstream.streamdraw.ui.screens.game.GameChatDialog 50 | import io.getstream.streamdraw.ui.theme.hostAccent 51 | 52 | @Composable 53 | fun SketchbookControlMenu( 54 | controller: SketchbookController, 55 | ) { 56 | val context = LocalContext.current 57 | Row( 58 | modifier = Modifier 59 | .fillMaxWidth() 60 | .padding(10.dp), 61 | horizontalArrangement = Arrangement.SpaceAround, 62 | verticalAlignment = Alignment.CenterVertically 63 | ) { 64 | Icon( 65 | modifier = Modifier 66 | .clickable { 67 | controller.setEraseMode(false) 68 | controller.setRainbowShader() 69 | Toast 70 | .makeText(context, "Rainbow Shader", Toast.LENGTH_SHORT) 71 | .show() 72 | } 73 | .size(40.dp), 74 | tint = ChatTheme.colors.textHighEmphasis, 75 | imageVector = ImageVector.vectorResource(R.drawable.ic_brush), 76 | contentDescription = null 77 | ) 78 | 79 | Box { 80 | val expanded = remember { mutableStateOf(false) } 81 | val widths = listOf(10f, 20f, 30f, 40f, 50f) 82 | Icon( 83 | modifier = Modifier 84 | .clickable { expanded.value = true } 85 | .size(40.dp), 86 | imageVector = ImageVector.vectorResource(R.drawable.ic_line_weight), 87 | tint = ChatTheme.colors.textHighEmphasis, 88 | contentDescription = null 89 | ) 90 | DropdownMenu( 91 | expanded = expanded.value, 92 | modifier = Modifier.background(ChatTheme.colors.appBackground), 93 | onDismissRequest = { expanded.value = false } 94 | ) { 95 | widths.forEach { width -> 96 | DropdownMenuItem( 97 | onClick = { 98 | controller.setPaintStrokeWidth(width) 99 | expanded.value = false 100 | } 101 | ) { 102 | Text(text = width.toString(), color = ChatTheme.colors.textHighEmphasis) 103 | } 104 | } 105 | } 106 | } 107 | 108 | Box { 109 | val expanded = remember { mutableStateOf(false) } 110 | Icon( 111 | modifier = Modifier 112 | .clickable { expanded.value = true } 113 | .size(40.dp), 114 | imageVector = ImageVector.vectorResource(R.drawable.ic_line_style), 115 | tint = ChatTheme.colors.textHighEmphasis, 116 | contentDescription = null 117 | ) 118 | 119 | DropdownMenu( 120 | expanded = expanded.value, 121 | modifier = Modifier.background(ChatTheme.colors.appBackground), 122 | onDismissRequest = { expanded.value = false } 123 | ) { 124 | 125 | DropdownMenuItem( 126 | onClick = { 127 | controller.setPathEffect(PathEffect.cornerPathEffect(60f)) 128 | expanded.value = false 129 | } 130 | ) { 131 | Text(text = "Normal", color = ChatTheme.colors.textHighEmphasis) 132 | } 133 | 134 | DropdownMenuItem( 135 | onClick = { 136 | controller.setPathEffect( 137 | PathEffect.dashPathEffect( 138 | floatArrayOf(20f, 40f), 139 | 40f 140 | ) 141 | ) 142 | expanded.value = false 143 | } 144 | ) { 145 | Text(text = "Dash Effect", color = ChatTheme.colors.textHighEmphasis) 146 | } 147 | } 148 | } 149 | 150 | Image( 151 | modifier = Modifier 152 | .clickable { 153 | controller.toggleEraseMode() 154 | Toast 155 | .makeText(context, "Erase Mode", Toast.LENGTH_SHORT) 156 | .show() 157 | } 158 | .size(40.dp), 159 | bitmap = ImageBitmap.imageResource(R.drawable.eraser), 160 | contentDescription = null 161 | ) 162 | 163 | val expand = remember { mutableStateOf(false) } 164 | Icon( 165 | modifier = Modifier 166 | .clickable { 167 | expand.value = true 168 | } 169 | .size(40.dp), 170 | imageVector = ImageVector.vectorResource(R.drawable.ic_bubble), 171 | tint = hostAccent, 172 | contentDescription = null 173 | ) 174 | 175 | GameChatDialog(listViewModel = hiltViewModel(), expanded = expand) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 |

6 | 7 |

8 | License 9 | API 10 | Build Status 11 | Kotlin Weekly 12 | Android Weekly 13 |

14 | 15 |

16 | 🛥 Stream Draw is a real-time multiplayer drawing & chat game app built entirely with Jetpack Compose.
17 | Draw and guess words with your friends! This project was inspired by Skribbl.io. 18 |

19 | 20 | ## Previews 21 |

22 | 23 |

24 | 25 | ## Download 26 | Go to the [Releases](https://github.com/GetStream/stream-draw-android/releases) to download the latest APK. 27 | 28 | 29 | 30 | 31 | 32 | ## 🛥 Stream Chat SDK 33 | Stream Draw was built with __[Android Chat SDK for Messaging](https://getstream.io/chat/sdk/android/?utm_source=Github&utm_medium=Jaewoong_OSS&utm_content=Developer&utm_campaign=Github_Mar2022_AndroidSDK&utm_term=DevRelOss)__ to implement messaging systems. 34 | If you’re interested in adding powerful In-App Messaging to your app, check out the __[Android Chat Messaging Tutorial](https://getstream.io/tutorials/android-chat/?utm_source=Github&utm_medium=Jaewoong_OSS&utm_content=Developer&utm_campaign=Github_Mar2022_AndroidSDK&utm_term=DevRelOss)__! 35 | 36 | - [Stream Chat SDK for Android on GitHub](https://github.com/getStream/stream-chat-android) 37 | - [Android Samples for Stream Chat SDK on GitHub](https://github.com/getStream/android-samples) 38 | - [Stream Chat Compose UI Componenets Guidelines](https://getstream.io/chat/docs/sdk/android/compose/overview/) 39 | 40 | ## 🛠 Tech Stack & Open Source Libraries 41 | - Minimum SDK level 21. 42 | - 100% [Jetpack Compose](https://developer.android.com/jetpack/compose) based + [Coroutines](https://github.com/Kotlin/kotlinx.coroutines) + [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/) for asynchronous. 43 | - [Compose Chat SDK for Messaging](https://getstream.io/chat/compose/tutorial/?utm_source=Github&utm_medium=Jaewoong_OSS&utm_content=Developer&utm_campaign=Github_Mar2022_AndroidSDK&utm_term=DevRelOss) - The Jetpack Compose Chat Messaging SDK is built on a low-level chat client and provides modular, customizable Compose UI components that you can easily drop into your app. 44 | - Jetpack 45 | - Compose - Android’s modern toolkit for building native UI. 46 | - Lifecycle - Observe lifecycle changes. 47 | - ViewModel - UI related data holder and lifecycle aware. 48 | - App Startup - Provides a straightforward, performant way to initialize components at application startup. 49 | - [Hilt](https://dagger.dev/hilt/) - Dependency Injection. 50 | - [sketchbook-compose](https://github.com/getStream/sketchbook-compose) - Jetpack Compose canvas library that helps you draw paths, images on canvas with color pickers and palettes. 51 | - [landscapist](https://github.com/skydoves/landscapist) - Jetpack Compose image loading library that fetches and displays network images with Glide, Coil, and Fresco. 52 | - [Retrofit2 & OkHttp3](https://github.com/square/retrofit) - Construct the REST APIs and paging network data. 53 | - [Moshi](https://github.com/square/moshi/) - A modern JSON library for Kotlin and Java. 54 | - [Konfetti](https://github.com/DanielMartinus/Konfetti) - Celebrate more with this lightweight confetti particle system. 55 | - [Timber](https://github.com/JakeWharton/timber) - A logger with a small, extensible API which provides utility. 56 | 57 | ## ✅ Available Features 58 | - Light and Dark themes. 59 | - Creating and joining a group channel. 60 | - Supports host mode and guest mode. 61 | - Welcome and exit messages. 62 | - Fetches a list of words from the network. 63 | - Real-time drawing on the sketchbook. 64 | - Real-time chat messaging with multiple users. 65 | - Real-time participants' list of a connected channel. 66 | - Guessing a word and congratulation animations. 67 | - Single message notification and real-time chat dialog for the host. 68 | - Restarting game by the host. 69 | - Exiting and deleting the channel by the host. 70 | - And a lot of additional features using Stream Chat SDK for Android! 71 | 72 | ## ☑️ TODO 73 | - [ ] Available channel list. 74 | - [ ] Game rounds. 75 | - [ ] Time limits. 76 | 77 | ## 💙 Contribution 78 | Anyone can contribute to improving code, docs, or something following our [Contributing Guideline](/CONTRIBUTING.md). 79 | 80 | ## 📱 Game Screenshots 81 | 82 |

83 | 84 | 85 | 86 |

87 | 88 | ## 💯 MAD Score 89 | 90 | ![summary](https://user-images.githubusercontent.com/24237865/158918011-bc766482-ec83-47dd-9237-d8a226cab263.png) 91 | 92 | 93 | 94 | 95 | ## Find this repository useful? 💙 96 | Support it by joining __[stargazers](https://github.com/GetStream/stream-draw-android/stargazers)__ for this repository. :star:
97 | Also, follow __[maintainers](https://github.com/GetStream/stream-draw-android/graphs/contributors)__ on GitHub for our next creations! 🤩 98 | 99 | # License 100 | ```xml 101 | Copyright 2022 Stream.IO, Inc. All Rights Reserved. 102 | 103 | Licensed under the Apache License, Version 2.0 (the "License"); 104 | you may not use this file except in compliance with the License. 105 | You may obtain a copy of the License at 106 | 107 | http://www.apache.org/licenses/LICENSE-2.0 108 | 109 | Unless required by applicable law or agreed to in writing, software 110 | distributed under the License is distributed on an "AS IS" BASIS, 111 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 112 | See the License for the specific language governing permissions and 113 | limitations under the License. 114 | ``` 115 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/streamdraw/ui/screens/chat/ChatScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.streamdraw.ui.screens.chat 18 | 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.clickable 21 | import androidx.compose.foundation.interaction.MutableInteractionSource 22 | import androidx.compose.foundation.layout.Arrangement 23 | import androidx.compose.foundation.layout.Box 24 | import androidx.compose.foundation.layout.PaddingValues 25 | import androidx.compose.foundation.layout.Row 26 | import androidx.compose.foundation.layout.fillMaxSize 27 | import androidx.compose.foundation.layout.fillMaxWidth 28 | import androidx.compose.foundation.layout.padding 29 | import androidx.compose.foundation.layout.size 30 | import androidx.compose.foundation.layout.wrapContentHeight 31 | import androidx.compose.foundation.lazy.LazyColumn 32 | import androidx.compose.foundation.lazy.LazyListState 33 | import androidx.compose.foundation.lazy.itemsIndexed 34 | import androidx.compose.material.Icon 35 | import androidx.compose.material.Scaffold 36 | import androidx.compose.material.Text 37 | import androidx.compose.material.ripple.rememberRipple 38 | import androidx.compose.runtime.Composable 39 | import androidx.compose.runtime.LaunchedEffect 40 | import androidx.compose.runtime.mutableStateOf 41 | import androidx.compose.runtime.remember 42 | import androidx.compose.ui.Alignment 43 | import androidx.compose.ui.Modifier 44 | import androidx.compose.ui.res.painterResource 45 | import androidx.compose.ui.res.stringResource 46 | import androidx.compose.ui.text.font.FontWeight 47 | import androidx.compose.ui.unit.dp 48 | import androidx.compose.ui.unit.sp 49 | import androidx.hilt.navigation.compose.hiltViewModel 50 | import io.getstream.chat.android.compose.R 51 | import io.getstream.chat.android.compose.state.messages.MessagesState 52 | import io.getstream.chat.android.compose.state.messages.list.MessageItemState 53 | import io.getstream.chat.android.compose.state.messages.list.MessageListItemState 54 | import io.getstream.chat.android.compose.ui.components.composer.InputField 55 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 56 | import io.getstream.streamdraw.data.GameMessage 57 | import io.getstream.streamdraw.data.toGameMessage 58 | import io.getstream.streamdraw.ui.components.LoadingIndicator 59 | import io.getstream.streamdraw.ui.screens.game.GameViewModel 60 | import io.getstream.streamdraw.ui.theme.hostAccent 61 | 62 | @Composable 63 | fun ChatScreen( 64 | modifier: Modifier, 65 | lazyListState: LazyListState, 66 | currentState: MessagesState, 67 | onScrollToBottom: () -> Unit, 68 | ) { 69 | Box(modifier = modifier) { 70 | Scaffold( 71 | modifier = Modifier.fillMaxSize(), 72 | bottomBar = { 73 | ChatInput( 74 | modifier = Modifier 75 | .fillMaxWidth() 76 | .wrapContentHeight() 77 | .padding(6.dp) 78 | .align(Alignment.Center), 79 | gameViewModel = hiltViewModel() 80 | ) 81 | } 82 | ) { 83 | val (isLoading, _, _, messages) = currentState 84 | if (isLoading) { 85 | LoadingIndicator(Modifier.fillMaxSize()) 86 | } else { 87 | MessageList( 88 | modifier = Modifier 89 | .fillMaxSize() 90 | .background(ChatTheme.colors.appBackground) 91 | .padding(it), 92 | lazyListState = lazyListState, 93 | gameViewModel = hiltViewModel(), 94 | onScrollToBottom = onScrollToBottom, 95 | messages = messages 96 | ) 97 | } 98 | } 99 | } 100 | } 101 | 102 | @Composable 103 | private fun MessageList( 104 | modifier: Modifier, 105 | gameViewModel: GameViewModel, 106 | lazyListState: LazyListState, 107 | onScrollToBottom: () -> Unit, 108 | messages: List, 109 | ) { 110 | val newMessage = gameViewModel.newSingleMessage.value 111 | LaunchedEffect(newMessage) { 112 | lazyListState.animateScrollToItem(0) 113 | } 114 | 115 | LazyColumn( 116 | modifier = modifier, 117 | state = lazyListState, 118 | horizontalAlignment = Alignment.Start, 119 | verticalArrangement = Arrangement.Bottom, 120 | reverseLayout = true, 121 | contentPadding = PaddingValues(vertical = 16.dp) 122 | ) { 123 | itemsIndexed( 124 | messages, 125 | key = { _, item -> 126 | if (item is MessageItemState) item.message.id else item.toString() 127 | } 128 | ) { index, item -> 129 | if (item is MessageItemState) { 130 | val isHostMessage = item.message.user.name == gameViewModel.host 131 | val gameMessage = item.message.toGameMessage() 132 | if (isHostMessage) { 133 | HostChatMessage(gameMessage) 134 | } else { 135 | GeneralChatMessage(gameMessage) 136 | } 137 | } 138 | 139 | if (index == 0 && lazyListState.isScrollInProgress) { 140 | onScrollToBottom() 141 | } 142 | } 143 | } 144 | } 145 | 146 | @Composable 147 | private fun GeneralChatMessage(message: GameMessage) { 148 | Text( 149 | modifier = Modifier.padding(horizontal = 14.dp, vertical = 5.dp), 150 | text = "${message.name}: ${message.message}", 151 | color = ChatTheme.colors.textHighEmphasis, 152 | fontSize = 16.sp, 153 | ) 154 | } 155 | 156 | @Composable 157 | private fun HostChatMessage(message: GameMessage) { 158 | Text( 159 | modifier = Modifier.padding(horizontal = 14.dp, vertical = 5.dp), 160 | text = "${message.name}(host): ${message.message}", 161 | fontWeight = FontWeight.Bold, 162 | color = hostAccent, 163 | fontSize = 16.sp, 164 | ) 165 | } 166 | 167 | @Composable 168 | private fun ChatInput( 169 | modifier: Modifier = Modifier, 170 | gameViewModel: GameViewModel, 171 | ) { 172 | val onInputValue = remember { mutableStateOf("") } 173 | InputField( 174 | value = onInputValue.value, 175 | modifier = modifier, 176 | maxLines = 1, 177 | innerPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), 178 | onValueChange = { onInputValue.value = it }, 179 | decorationBox = { innerTextField -> 180 | Row( 181 | modifier = Modifier.fillMaxWidth(), 182 | verticalAlignment = Alignment.CenterVertically, 183 | ) { 184 | Box(modifier = Modifier.weight(1f)) { 185 | innerTextField() 186 | 187 | if (onInputValue.value.isEmpty()) { 188 | Text( 189 | text = stringResource(id = R.string.stream_compose_message_label), 190 | color = ChatTheme.colors.textLowEmphasis 191 | ) 192 | } 193 | } 194 | 195 | Icon( 196 | modifier = Modifier 197 | .size(24.dp) 198 | .clickable( 199 | interactionSource = remember { MutableInteractionSource() }, 200 | indication = rememberRipple() 201 | ) { 202 | val message = onInputValue.value 203 | if (message.isNotEmpty()) { 204 | gameViewModel.sendChatMessage(message) 205 | onInputValue.value = "" 206 | } 207 | }, 208 | painter = painterResource(id = R.drawable.stream_compose_ic_send), 209 | tint = ChatTheme.colors.primaryAccent, 210 | contentDescription = null 211 | ) 212 | } 213 | } 214 | ) 215 | } 216 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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 | --------------------------------------------------------------------------------