├── .github
├── CODEOWNERS
└── workflows
│ └── android.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── wisemuji
│ │ └── zoomclone
│ │ ├── ZoomCloneComposeApplication.kt
│ │ ├── model
│ │ └── MeetingOptions.kt
│ │ └── ui
│ │ ├── AppNavHost.kt
│ │ ├── MainActivity.kt
│ │ ├── component
│ │ ├── DefaultHorizontalDivider.kt
│ │ ├── StatusBarColor.kt
│ │ ├── ZoomFullSizeButton.kt
│ │ ├── ZoomSwitch.kt
│ │ ├── ZoomTextField.kt
│ │ └── ZoomVideoTheme.kt
│ │ ├── joinmeeting
│ │ ├── JoinMeetingScreen.kt
│ │ └── navigation
│ │ │ └── JoinMeetingNavigation.kt
│ │ ├── lobby
│ │ ├── LobbyScreen.kt
│ │ └── navigation
│ │ │ └── LobbyNavigation.kt
│ │ ├── meetingroom
│ │ ├── MeetingRoomScreen.kt
│ │ ├── MeetingRoomViewModel.kt
│ │ ├── ReactionItemData.kt
│ │ ├── ToggleButton.kt
│ │ └── navigation
│ │ │ └── MeetingRoomNavigation.kt
│ │ ├── newmeeting
│ │ ├── NewMeetingScreen.kt
│ │ └── navigation
│ │ │ └── NewMeetingNavigation.kt
│ │ └── theme
│ │ ├── Color.kt
│ │ ├── Theme.kt
│ │ └── Type.kt
│ └── res
│ ├── drawable
│ ├── ic_launcher_background.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_zoom_breakout.xml
│ ├── ic_zoom_chat.xml
│ ├── ic_zoom_join_meeting.xml
│ ├── ic_zoom_mic_off.xml
│ ├── ic_zoom_mic_on.xml
│ ├── ic_zoom_participants.xml
│ ├── ic_zoom_reaction.xml
│ ├── ic_zoom_video_off.xml
│ └── ic_zoom_video_on.xml
│ ├── mipmap-anydpi
│ ├── ic_launcher.xml
│ └── ic_launcher_round.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
│ ├── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── build-logic
├── build.gradle.kts
├── settings.gradle.kts
└── src
│ └── main
│ └── kotlin
│ ├── com
│ └── wisemuji
│ │ └── zoomclone
│ │ ├── ComposeAndroid.kt
│ │ ├── Extension.kt
│ │ ├── HiltAndroid.kt
│ │ ├── KotlinAndroid.kt
│ │ └── Spotless.kt
│ ├── zoomclone.android.application.gradle.kts
│ └── zoomclone.android.compose.gradle.kts
├── build.gradle.kts
├── figures
├── cover.jpg
├── stream0.png
├── stream1.png
├── stream2.png
├── stream3.png
├── stream4.png
├── stream5.png
└── stream6.png
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── previews
├── preview0.png
└── preview1.png
├── secrets.defaults.properties
├── settings.gradle.kts
└── spotless
├── copyright.kt
├── copyright.kts
└── spotless.gradle.kts
/.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 | * @wisemuji
--------------------------------------------------------------------------------
/.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 | distribution: adopt
19 | java-version: 17
20 |
21 | - name: Cache Gradle and wrapper
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.gradle/caches
26 | ~/.gradle/wrapper
27 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
28 | restore-keys: |
29 | ${{ runner.os }}-gradle-
30 | - name: Make Gradle executable
31 | run: chmod +x ./gradlew
32 |
33 | - name: Build with Gradle
34 | run: ./gradlew build
35 |
--------------------------------------------------------------------------------
/.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 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 | secrets.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/*
39 | !.idea/codeInsightSettings.xml
40 | app/.idea/
41 |
42 | # Mac
43 | *.DS_Store
44 |
45 | # Keystore files
46 | *.jks
47 |
48 | # External native build folder generated in Android Studio 2.2 and later
49 | .externalNativeBuild
50 |
51 | # Google Services (e.g. APIs or Firebase)
52 | google-services.json
53 |
54 | # Temporary API docs
55 | docs/api
56 |
--------------------------------------------------------------------------------
/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 | Suhyeon.
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 |
--------------------------------------------------------------------------------
/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 | ## Code reviews
5 | 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.
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | This is a [Zoom](https://zoom.us/) clone app built with __[Stream Video SDK for Compose](https://getstream.io/video/sdk/android/?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon)__ to implement real-time video meeting features using Jetpack Compose.
11 |
12 | The goal of this repository is to showcase the following:
13 |
14 | - The development of comprehensive UI elements utilizing Jetpack Compose.
15 | - The use of Android architecture components alongside Jetpack libraries, including androidx ViewModel and Hilt.
16 | - Execution of background operations using Kotlin Coroutines.
17 | - Integration of real-time video meeting room functionalities through the Stream Video SDK, powered by WebRTC technology.
18 |
19 | ## ✍️ Technical Content
20 |
21 | If you're interested in building this project from the scratch, check out the blog posts below:
22 |
23 | - **[Build a Real-Time Zoom Clone with Jetpack Compose](https://getstream.io/blog/zoom-clone-compose/)**
24 |
25 | ## 📲 Download APK
26 | Go to the [Releases](https://github.com/wisemuji/zoom-clone-compose/releases) to download the latest APK.
27 |
28 |
29 |
30 |
31 |
32 | ## 🛥 Stream Video SDK
33 | **Zoom Clone Compose** is built with __[Stream Video SDK for Compose](https://getstream.io/video/sdk/android/?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon)__ to implement a real-time video meeting features. If you’re interested in adding powerful In-App Video calling, audio room, livestreaming to your app, check out the __[Android Video Calling Tutorial](https://getstream.io/video/sdk/android/tutorial/video-calling/?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon)__!
34 |
35 | ### Stream Video
36 |
37 | - [Stream Video SDK for Android on GitHub](https://github.com/getstream/stream-video-android?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon)
38 | - [Video Call Tutorial](https://getstream.io/video/docs/android/tutorials/video-calling?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon)
39 | - [Audio Room Tutorial](https://getstream.io/video/docs/android/tutorials/audio-room?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon)
40 | - [Livestream Tutorial](https://getstream.io/video/docs/android/tutorials/livestream?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon)
41 |
42 | ## 📷 Previews
43 |
44 | 
45 | 
46 |
47 | ## 💻 Build Your Own Chat Project
48 |
49 | If you want to build your own chat project, you should follow the instructions below:
50 |
51 | 1. Go to the __[Stream login page](https://getstream.io/try-for-free?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon)__.
52 | 2. If you have your GitHub account, click the **SIGN UP WITH GITHUB** button and you can sign up within a couple of seconds.
53 |
54 | 
55 |
56 | 3. If you don't have a GitHub account, fill in the inputs and click the **START FREE TRIAL** button.
57 | 4. Go to the __[Dashboard](https://dashboard.getstream.io?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon)__ and click the **Create App** button like the below.
58 |
59 | 
60 |
61 | 5. Fill in the blanks like the below and click the **Create App** button.
62 |
63 | 
64 |
65 | 6. You will see the **Key** like the figure below and then copy it.
66 |
67 | 
68 |
69 | 7. Create a `secrets.properties` file on the root project directory with the text below using your API key:
70 |
71 | ```
72 | STREAM_API_KEY=REPLACE WITH YOUR API KEY
73 | ```
74 |
75 | 8. Build and run the project.
76 |
77 | ## 🛠 Tech Stack & Open Source Libraries
78 | - Minimum SDK level 26.
79 | - 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.
80 | - [Compose Video SDK for Real-Time Meeting](https://getstream.io/video/docs/android/tutorials/video-calling?utm_source=Github&utm_medium=external_write[…]_campaign=Github_Mar2024_ZoomAndroidClone&utm_term=suhyeon): 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.
81 | - Jetpack
82 | - Compose: Android’s modern toolkit for building native UI.
83 | - ViewModel: UI related data holder and lifecycle aware.
84 | - Navigation: For navigating screens and [Hilt Navigation Compose](https://developer.android.com/jetpack/compose/libraries#hilt) for injecting dependencies.
85 | - [Hilt](https://dagger.dev/hilt/): Dependency Injection.
86 | - [Spotless gradle plugin](https://github.com/diffplug/spotless/tree/main/plugin-gradle) for formatting kotlin files.
87 |
88 | ## 🤝 Contribution
89 |
90 | Most of the features are not completed except the chat feature, so anyone can contribute and improve this project following the [Contributing Guideline](https://github.com/wisemuji/zoom-clone-compose/blob/main/CONTRIBUTING.md).
91 |
92 | # License
93 | ```xml
94 | Copyright 2024 Suhyeon(wisemuji) and Stream.IO, Inc. All Rights Reserved.
95 |
96 | Licensed under the Apache License, Version 2.0 (the "License");
97 | you may not use this file except in compliance with the License.
98 | You may obtain a copy of the License at
99 |
100 | http://www.apache.org/licenses/LICENSE-2.0
101 |
102 | Unless required by applicable law or agreed to in writing, software
103 | distributed under the License is distributed on an "AS IS" BASIS,
104 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
105 | See the License for the specific language governing permissions and
106 | limitations under the License.
107 | ```
108 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 |
18 | plugins {
19 | id("zoomclone.android.application")
20 | id("zoomclone.android.compose")
21 | id(libs.plugins.kotlin.serialization.get().pluginId)
22 | id(libs.plugins.google.secrets.get().pluginId)
23 | }
24 |
25 | android {
26 | namespace = "com.wisemuji.zoomclone"
27 | defaultConfig {
28 | applicationId = "com.wisemuji.zoomclone"
29 | targetSdk = 34
30 | versionCode = 1
31 | versionName = "1.0"
32 |
33 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
34 | vectorDrawables {
35 | useSupportLibrary = true
36 | }
37 | }
38 | buildFeatures {
39 | buildConfig = true
40 | }
41 | packaging {
42 | resources {
43 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
44 | }
45 | }
46 | }
47 |
48 | secrets {
49 | propertiesFileName = "secrets.properties"
50 | defaultPropertiesFileName = "secrets.defaults.properties"
51 | ignoreList.add("keyToIgnore") // Ignore the key "keyToIgnore"
52 | ignoreList.add("sdk.*") // Ignore all keys matching the regexp "sdk.*"
53 | }
54 |
55 | dependencies {
56 | // stream
57 | implementation(libs.stream.video.ui.compose)
58 | implementation(libs.stream.video.ui.previewdata)
59 |
60 | // androidx
61 | implementation(libs.androidx.core.ktx)
62 | implementation(libs.androidx.activity.compose)
63 | implementation(libs.androidx.lifecycle.runtimeCompose)
64 | implementation(libs.androidx.lifecycle.viewModelCompose)
65 | implementation(libs.androidx.compose.navigation)
66 |
67 | // accompanist
68 | implementation(libs.accompanist.permissions)
69 |
70 | // kotlin
71 | implementation(libs.kotlinx.serialization.json)
72 | }
73 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ZoomCloneComposeApplication.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone
18 |
19 | import android.app.Application
20 | import dagger.hilt.android.HiltAndroidApp
21 | import io.getstream.video.android.core.StreamVideo
22 | import io.getstream.video.android.core.StreamVideoBuilder
23 | import io.getstream.video.android.model.User
24 |
25 | @HiltAndroidApp
26 | class ZoomCloneComposeApplication : Application() {
27 | override fun onCreate() {
28 | super.onCreate()
29 |
30 | initStreamVideo()
31 | }
32 |
33 | private fun initStreamVideo() {
34 | val userId = "wisemuji"
35 | StreamVideoBuilder(
36 | context = applicationContext,
37 | apiKey = BuildConfig.STREAM_API_KEY,
38 | token = StreamVideo.devToken(userId),
39 | user = User(
40 | id = userId,
41 | name = "Wisemuji",
42 | image = "http://placekitten.com/200/300",
43 | role = "admin",
44 | ),
45 | ).build()
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/model/MeetingOptions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.model
18 |
19 | import kotlinx.serialization.Serializable
20 |
21 | @Serializable
22 | data class MeetingOptions(
23 | val meetingId: String = "",
24 | val username: String = "",
25 | val audioOn: Boolean = true,
26 | val videoOn: Boolean = true,
27 | )
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/AppNavHost.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.Modifier
21 | import androidx.navigation.NavHostController
22 | import androidx.navigation.compose.NavHost
23 | import com.wisemuji.zoomclone.ui.joinmeeting.navigation.joinMeetingScreen
24 | import com.wisemuji.zoomclone.ui.joinmeeting.navigation.navigateToJoinMeeting
25 | import com.wisemuji.zoomclone.ui.lobby.navigation.LobbyRoute
26 | import com.wisemuji.zoomclone.ui.lobby.navigation.lobbyScreen
27 | import com.wisemuji.zoomclone.ui.meetingroom.navigation.meetingRoomScreen
28 | import com.wisemuji.zoomclone.ui.meetingroom.navigation.navigateToMeetingRoom
29 | import com.wisemuji.zoomclone.ui.newmeeting.navigation.navigateToNewMeeting
30 | import com.wisemuji.zoomclone.ui.newmeeting.navigation.newMeetingScreen
31 |
32 | @Composable
33 | fun AppNavHost(
34 | navController: NavHostController,
35 | modifier: Modifier = Modifier,
36 | startDestination: String = LobbyRoute.ROUTE,
37 | ) {
38 | NavHost(
39 | modifier = modifier,
40 | navController = navController,
41 | startDestination = startDestination,
42 | ) {
43 | lobbyScreen(
44 | onNewMeetingClick = navController::navigateToNewMeeting,
45 | onJoinMeetingClick = navController::navigateToJoinMeeting,
46 | )
47 | newMeetingScreen(
48 | onBackPressed = navController::popBackStack,
49 | onJoinMeetingClick = navController::navigateToMeetingRoom,
50 | )
51 | joinMeetingScreen(
52 | onBackPressed = navController::popBackStack,
53 | onJoinMeetingClick = navController::navigateToMeetingRoom,
54 | )
55 | meetingRoomScreen(onBackPressed = navController::popBackStack)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui
18 |
19 | import android.os.Bundle
20 | import androidx.activity.ComponentActivity
21 | import androidx.activity.compose.setContent
22 | import androidx.compose.foundation.layout.fillMaxSize
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.Surface
25 | import androidx.compose.ui.Modifier
26 | import androidx.navigation.compose.rememberNavController
27 | import com.wisemuji.zoomclone.ui.theme.ZoomCloneComposeTheme
28 | import dagger.hilt.android.AndroidEntryPoint
29 |
30 | @AndroidEntryPoint
31 | class MainActivity : ComponentActivity() {
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | setContent {
35 | ZoomCloneComposeTheme {
36 | Surface(
37 | modifier = Modifier.fillMaxSize(),
38 | color = MaterialTheme.colorScheme.background,
39 | ) {
40 | AppNavHost(navController = rememberNavController())
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/component/DefaultHorizontalDivider.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.component
18 |
19 | import androidx.compose.material3.HorizontalDivider
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.unit.dp
24 |
25 | @Composable
26 | fun DefaultHorizontalDivider(
27 | modifier: Modifier = Modifier,
28 | ) {
29 | HorizontalDivider(
30 | color = MaterialTheme.colorScheme.outlineVariant,
31 | thickness = 1.dp,
32 | modifier = modifier,
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/component/StatusBarColor.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.component
18 |
19 | import android.app.Activity
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.SideEffect
22 | import androidx.compose.ui.graphics.Color
23 | import androidx.compose.ui.graphics.toArgb
24 | import androidx.compose.ui.platform.LocalView
25 | import androidx.core.view.WindowCompat
26 |
27 | @Composable
28 | fun StatusBarColor(
29 | color: Color,
30 | isIconLight: Boolean = false,
31 | ) {
32 | val view = LocalView.current
33 | if (!view.isInEditMode) {
34 | SideEffect {
35 | val window = (view.context as Activity).window
36 | window.statusBarColor = color.toArgb()
37 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = isIconLight
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/component/ZoomFullSizeButton.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.component
18 |
19 | import androidx.compose.foundation.layout.fillMaxWidth
20 | import androidx.compose.foundation.layout.padding
21 | import androidx.compose.foundation.shape.RoundedCornerShape
22 | import androidx.compose.material3.Button
23 | import androidx.compose.material3.Text
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.text.font.FontWeight
27 | import androidx.compose.ui.tooling.preview.Preview
28 | import androidx.compose.ui.unit.dp
29 | import androidx.compose.ui.unit.sp
30 | import com.wisemuji.zoomclone.ui.theme.ZoomCloneComposeTheme
31 |
32 | @Composable
33 | fun ZoomFullSizeButton(
34 | text: String,
35 | onClick: () -> Unit,
36 | modifier: Modifier = Modifier,
37 | enabled: Boolean = true,
38 | ) {
39 | Button(
40 | onClick = onClick,
41 | enabled = enabled,
42 | shape = RoundedCornerShape(14.dp),
43 | modifier = modifier
44 | .fillMaxWidth(),
45 | ) {
46 | Text(
47 | text = text,
48 | fontSize = 17.sp,
49 | fontWeight = FontWeight.Bold,
50 | modifier = Modifier.padding(vertical = 8.dp),
51 | )
52 | }
53 | }
54 |
55 | @Preview
56 | @Composable
57 | private fun FullSizeButtonPreview() {
58 | ZoomCloneComposeTheme {
59 | ZoomFullSizeButton(text = "Start a Meeting", onClick = {})
60 | }
61 | }
62 |
63 | @Preview(showBackground = true)
64 | @Composable
65 | private fun FullSizeButtonPreviewDisabled() {
66 | ZoomCloneComposeTheme {
67 | ZoomFullSizeButton(text = "Start a Meeting", onClick = {}, enabled = false)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/component/ZoomSwitch.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.component
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.Row
23 | import androidx.compose.foundation.layout.padding
24 | import androidx.compose.material3.MaterialTheme
25 | import androidx.compose.material3.Switch
26 | import androidx.compose.material3.SwitchDefaults
27 | import androidx.compose.material3.Text
28 | import androidx.compose.runtime.Composable
29 | import androidx.compose.ui.Alignment
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.tooling.preview.Preview
32 | import androidx.compose.ui.tooling.preview.PreviewParameter
33 | import androidx.compose.ui.tooling.preview.PreviewParameterProvider
34 | import androidx.compose.ui.unit.dp
35 | import androidx.compose.ui.unit.sp
36 | import com.wisemuji.zoomclone.ui.theme.Gray60
37 | import com.wisemuji.zoomclone.ui.theme.ZoomCloneComposeTheme
38 |
39 | @Composable
40 | fun ZoomSwitch(
41 | checked: Boolean,
42 | onCheckedChange: (Boolean) -> Unit,
43 | modifier: Modifier = Modifier,
44 | ) {
45 | Switch(
46 | checked = checked,
47 | onCheckedChange = onCheckedChange,
48 | colors = SwitchDefaults.colors(
49 | checkedThumbColor = MaterialTheme.colorScheme.onTertiary,
50 | checkedTrackColor = MaterialTheme.colorScheme.tertiary,
51 | uncheckedThumbColor = MaterialTheme.colorScheme.outlineVariant,
52 | uncheckedBorderColor = MaterialTheme.colorScheme.outlineVariant,
53 | ),
54 | modifier = modifier,
55 | )
56 | }
57 |
58 | @Composable
59 | fun ZoomSwitchRow(
60 | title: String,
61 | checked: Boolean,
62 | onCheckedChange: (Boolean) -> Unit,
63 | modifier: Modifier = Modifier,
64 | subtitle: String? = null,
65 | ) {
66 | Row(
67 | verticalAlignment = Alignment.CenterVertically,
68 | modifier = modifier
69 | .clickable { onCheckedChange(!checked) }
70 | .background(MaterialTheme.colorScheme.surface)
71 | .padding(horizontal = 14.dp),
72 | ) {
73 | Column(
74 | modifier = Modifier
75 | .weight(1f)
76 | .padding(vertical = 10.dp),
77 | ) {
78 | Text(
79 | text = title,
80 | fontSize = 15.sp,
81 | color = MaterialTheme.colorScheme.onSurface,
82 | )
83 | subtitle?.let {
84 | Text(
85 | text = it,
86 | fontSize = 13.sp,
87 | color = Gray60,
88 | )
89 | }
90 | }
91 | ZoomSwitch(checked, onCheckedChange = onCheckedChange)
92 | }
93 | }
94 |
95 | class CheckedPreviewParameterProvider : PreviewParameterProvider {
96 | override val values = sequenceOf(true, false)
97 | }
98 |
99 | @Preview
100 | @Composable
101 | private fun ZoomSwitchPreview(
102 | @PreviewParameter(CheckedPreviewParameterProvider::class) checked: Boolean,
103 | ) {
104 | ZoomCloneComposeTheme {
105 | ZoomSwitch(checked = checked, onCheckedChange = {})
106 | }
107 | }
108 |
109 | @Preview
110 | @Composable
111 | private fun ZoomSwitchRowPreview(
112 | @PreviewParameter(CheckedPreviewParameterProvider::class) checked: Boolean,
113 | ) {
114 | ZoomCloneComposeTheme {
115 | ZoomSwitchRow(
116 | title = "Video On",
117 | checked = checked,
118 | onCheckedChange = {},
119 | )
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/component/ZoomTextField.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.component
18 |
19 | import androidx.compose.foundation.layout.fillMaxWidth
20 | import androidx.compose.material3.LocalTextStyle
21 | import androidx.compose.material3.Text
22 | import androidx.compose.material3.TextField
23 | import androidx.compose.material3.TextFieldDefaults
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.text.style.TextAlign
27 | import androidx.compose.ui.tooling.preview.Preview
28 | import com.wisemuji.zoomclone.ui.theme.Gray20
29 | import com.wisemuji.zoomclone.ui.theme.Gray50
30 | import com.wisemuji.zoomclone.ui.theme.ZoomCloneComposeTheme
31 |
32 | @Composable
33 | fun ZoomTextField(
34 | value: String,
35 | onValueChange: (String) -> Unit,
36 | placeholderText: String,
37 | modifier: Modifier = Modifier,
38 | ) {
39 | TextField(
40 | value = value,
41 | onValueChange = onValueChange,
42 | placeholder = {
43 | Text(
44 | text = placeholderText,
45 | textAlign = TextAlign.Center,
46 | modifier = Modifier.fillMaxWidth(),
47 | )
48 | },
49 | textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
50 | colors = TextFieldDefaults.colors(
51 | focusedPlaceholderColor = Gray50,
52 | unfocusedPlaceholderColor = Gray50,
53 | focusedIndicatorColor = Gray20,
54 | unfocusedIndicatorColor = Gray20,
55 | ),
56 | modifier = modifier.fillMaxWidth(),
57 | )
58 | }
59 |
60 | @Preview
61 | @Composable
62 | fun ZoomTextFieldPreview() {
63 | ZoomCloneComposeTheme {
64 | ZoomTextField(
65 | value = "",
66 | onValueChange = {},
67 | placeholderText = "Meeting ID",
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/component/ZoomVideoTheme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.component
18 |
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.ui.graphics.RectangleShape
22 | import io.getstream.video.android.compose.theme.StreamColors
23 | import io.getstream.video.android.compose.theme.StreamShapes
24 | import io.getstream.video.android.compose.theme.VideoTheme
25 |
26 | @Composable
27 | fun ZoomVideoTheme(
28 | content: @Composable () -> Unit,
29 | ) {
30 | VideoTheme(
31 | colors = StreamColors
32 | .defaultColors()
33 | .copy(
34 | appBackground = MaterialTheme.colorScheme.inverseSurface,
35 | barsBackground = MaterialTheme.colorScheme.inverseSurface,
36 | inputBackground = MaterialTheme.colorScheme.inverseSurface,
37 | textHighEmphasis = MaterialTheme.colorScheme.inverseOnSurface,
38 | ),
39 | shapes = StreamShapes
40 | .defaultShapes()
41 | .copy(
42 | participantContainerShape = RectangleShape,
43 | callControls = RectangleShape,
44 | ),
45 | content = content,
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/joinmeeting/JoinMeetingScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.joinmeeting
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.Spacer
23 | import androidx.compose.foundation.layout.fillMaxSize
24 | import androidx.compose.foundation.layout.fillMaxWidth
25 | import androidx.compose.foundation.layout.height
26 | import androidx.compose.foundation.layout.padding
27 | import androidx.compose.foundation.layout.size
28 | import androidx.compose.material.icons.Icons
29 | import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
30 | import androidx.compose.material3.Icon
31 | import androidx.compose.material3.IconButton
32 | import androidx.compose.material3.MaterialTheme
33 | import androidx.compose.material3.Scaffold
34 | import androidx.compose.material3.Text
35 | import androidx.compose.runtime.Composable
36 | import androidx.compose.runtime.getValue
37 | import androidx.compose.runtime.mutableStateOf
38 | import androidx.compose.runtime.remember
39 | import androidx.compose.runtime.setValue
40 | import androidx.compose.ui.Alignment
41 | import androidx.compose.ui.Modifier
42 | import androidx.compose.ui.res.stringResource
43 | import androidx.compose.ui.text.font.FontWeight
44 | import androidx.compose.ui.tooling.preview.Preview
45 | import androidx.compose.ui.unit.dp
46 | import androidx.compose.ui.unit.sp
47 | import com.wisemuji.zoomclone.R
48 | import com.wisemuji.zoomclone.model.MeetingOptions
49 | import com.wisemuji.zoomclone.ui.component.DefaultHorizontalDivider
50 | import com.wisemuji.zoomclone.ui.component.StatusBarColor
51 | import com.wisemuji.zoomclone.ui.component.ZoomFullSizeButton
52 | import com.wisemuji.zoomclone.ui.component.ZoomSwitchRow
53 | import com.wisemuji.zoomclone.ui.component.ZoomTextField
54 | import com.wisemuji.zoomclone.ui.theme.Gray60
55 | import com.wisemuji.zoomclone.ui.theme.ZoomCloneComposeTheme
56 |
57 | @Composable
58 | fun JoinMeetingScreen(
59 | onBackPressed: () -> Unit,
60 | onJoinMeetingClick: (MeetingOptions) -> Unit,
61 | ) {
62 | var meetingId by remember { mutableStateOf("") }
63 | var name by remember { mutableStateOf("") }
64 | var audioOn by remember { mutableStateOf(true) }
65 | var videoOn by remember { mutableStateOf(true) }
66 |
67 | StatusBarColor(color = MaterialTheme.colorScheme.surface, isIconLight = true)
68 | Scaffold(
69 | topBar = { JoinMeetingTopAppBar(onBack = onBackPressed) },
70 | ) { innerPadding ->
71 | Column(
72 | modifier = Modifier
73 | .padding(innerPadding)
74 | .fillMaxSize()
75 | .background(MaterialTheme.colorScheme.surfaceContainer),
76 | ) {
77 | DefaultHorizontalDivider()
78 | Spacer(modifier = Modifier.padding(12.dp))
79 | DefaultHorizontalDivider()
80 | ZoomTextField(
81 | value = meetingId,
82 | onValueChange = { meetingId = it },
83 | placeholderText = stringResource(R.string.meeting_id_placeholder),
84 | )
85 | ZoomTextField(
86 | value = name,
87 | onValueChange = { name = it },
88 | placeholderText = stringResource(R.string.your_name_placeholder),
89 | )
90 | ZoomFullSizeButton(
91 | text = stringResource(R.string.join),
92 | onClick = { onJoinMeetingClick(MeetingOptions(meetingId, name, audioOn, videoOn)) },
93 | modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp),
94 | )
95 | Text(
96 | text = stringResource(R.string.join_notes),
97 | fontSize = 12.sp,
98 | color = Gray60,
99 | lineHeight = 14.sp,
100 | modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 10.dp, bottom = 42.dp),
101 | )
102 | JoinOptions(
103 | doNotConnectAudio = !audioOn,
104 | onCheckedDoNotConnectAudio = { audioOn = !it },
105 | turnOffVideo = !videoOn,
106 | onCheckedTurnOffVideo = { videoOn = !it },
107 | )
108 | }
109 | }
110 | }
111 |
112 | @Composable
113 | private fun JoinMeetingTopAppBar(onBack: () -> Unit) {
114 | Box(
115 | contentAlignment = Alignment.CenterStart,
116 | modifier = Modifier
117 | .fillMaxWidth()
118 | .height(56.dp)
119 | .padding(horizontal = 4.dp),
120 | ) {
121 | IconButton(onClick = onBack) {
122 | Icon(
123 | imageVector = Icons.AutoMirrored.Filled.KeyboardArrowLeft,
124 | contentDescription = "Back",
125 | tint = MaterialTheme.colorScheme.primary,
126 | modifier = Modifier.size(52.dp),
127 | )
128 | }
129 | Text(
130 | text = stringResource(R.string.join_meeting_title),
131 | fontSize = 17.sp,
132 | fontWeight = FontWeight.Medium,
133 | modifier = Modifier.align(Alignment.Center),
134 | )
135 | }
136 | }
137 |
138 | @Composable
139 | private fun JoinOptions(
140 | doNotConnectAudio: Boolean = false,
141 | onCheckedDoNotConnectAudio: (Boolean) -> Unit = {},
142 | turnOffVideo: Boolean = false,
143 | onCheckedTurnOffVideo: (Boolean) -> Unit = {},
144 | ) {
145 | Column {
146 | Text(
147 | text = stringResource(R.string.join_options),
148 | fontSize = 12.sp,
149 | color = Gray60,
150 | fontWeight = FontWeight.Medium,
151 | modifier = Modifier.padding(start = 16.dp),
152 | )
153 | DefaultHorizontalDivider()
154 | ZoomSwitchRow(
155 | title = stringResource(R.string.do_not_connect_audio),
156 | checked = doNotConnectAudio,
157 | onCheckedChange = onCheckedDoNotConnectAudio,
158 | )
159 | DefaultHorizontalDivider()
160 | ZoomSwitchRow(
161 | title = stringResource(R.string.turn_off_video),
162 | checked = turnOffVideo,
163 | onCheckedChange = onCheckedTurnOffVideo,
164 | )
165 | DefaultHorizontalDivider()
166 | }
167 | }
168 |
169 | @Preview
170 | @Composable
171 | private fun JoinMeetingScreenPreview() {
172 | ZoomCloneComposeTheme {
173 | JoinMeetingScreen({}, {})
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/joinmeeting/navigation/JoinMeetingNavigation.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.joinmeeting.navigation
18 |
19 | import androidx.navigation.NavController
20 | import androidx.navigation.NavGraphBuilder
21 | import androidx.navigation.compose.composable
22 | import com.wisemuji.zoomclone.model.MeetingOptions
23 | import com.wisemuji.zoomclone.ui.joinmeeting.JoinMeetingScreen
24 | import com.wisemuji.zoomclone.ui.joinmeeting.navigation.JoinMeetingRoute.ROUTE
25 |
26 | fun NavController.navigateToJoinMeeting() {
27 | navigate(ROUTE)
28 | }
29 |
30 | fun NavGraphBuilder.joinMeetingScreen(
31 | onBackPressed: () -> Unit,
32 | onJoinMeetingClick: (MeetingOptions) -> Unit,
33 | ) {
34 | composable(route = ROUTE) {
35 | JoinMeetingScreen(onBackPressed, onJoinMeetingClick)
36 | }
37 | }
38 |
39 | object JoinMeetingRoute {
40 | const val ROUTE = "join_meeting"
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/lobby/LobbyScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.lobby
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.clickable
21 | import androidx.compose.foundation.layout.Arrangement
22 | import androidx.compose.foundation.layout.Box
23 | import androidx.compose.foundation.layout.Column
24 | import androidx.compose.foundation.layout.Row
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.size
29 | import androidx.compose.foundation.shape.RoundedCornerShape
30 | import androidx.compose.material.icons.Icons
31 | import androidx.compose.material.icons.outlined.Info
32 | import androidx.compose.material3.AlertDialog
33 | import androidx.compose.material3.Icon
34 | import androidx.compose.material3.IconButton
35 | import androidx.compose.material3.MaterialTheme
36 | import androidx.compose.material3.Scaffold
37 | import androidx.compose.material3.Text
38 | import androidx.compose.material3.TextButton
39 | import androidx.compose.runtime.Composable
40 | import androidx.compose.runtime.getValue
41 | import androidx.compose.runtime.mutableStateOf
42 | import androidx.compose.runtime.remember
43 | import androidx.compose.runtime.setValue
44 | import androidx.compose.ui.Alignment
45 | import androidx.compose.ui.Modifier
46 | import androidx.compose.ui.draw.clip
47 | import androidx.compose.ui.graphics.Color
48 | import androidx.compose.ui.graphics.painter.Painter
49 | import androidx.compose.ui.res.painterResource
50 | import androidx.compose.ui.res.stringResource
51 | import androidx.compose.ui.text.font.FontWeight
52 | import androidx.compose.ui.tooling.preview.Preview
53 | import androidx.compose.ui.unit.dp
54 | import androidx.compose.ui.unit.sp
55 | import com.wisemuji.zoomclone.R
56 | import com.wisemuji.zoomclone.ui.component.DefaultHorizontalDivider
57 | import com.wisemuji.zoomclone.ui.component.StatusBarColor
58 | import com.wisemuji.zoomclone.ui.theme.Gray50
59 | import com.wisemuji.zoomclone.ui.theme.ZoomCloneComposeTheme
60 |
61 | @Composable
62 | fun LobbyScreen(
63 | onNewMeetingClick: () -> Unit,
64 | onJoinMeetingClick: () -> Unit,
65 | ) {
66 | var showInfoDialog by remember { mutableStateOf(false) }
67 |
68 | if (showInfoDialog) {
69 | LobbyInfoDialog(onDismissRequest = { showInfoDialog = false })
70 | }
71 | StatusBarColor(color = MaterialTheme.colorScheme.surfaceContainerHighest, isIconLight = false)
72 | Scaffold(
73 | topBar = { LobbyScreenTopAppBar { showInfoDialog = true } },
74 | ) { innerPadding ->
75 | Column {
76 | Row(
77 | horizontalArrangement = Arrangement.SpaceAround,
78 | modifier = Modifier
79 | .padding(innerPadding)
80 | .background(MaterialTheme.colorScheme.surfaceContainer)
81 | .padding(16.dp)
82 | .fillMaxWidth(),
83 | ) {
84 | LobbyItem(
85 | icon = painterResource(id = R.drawable.ic_zoom_video_on),
86 | caption = stringResource(R.string.new_meeting_navigator),
87 | color = MaterialTheme.colorScheme.secondary,
88 | ) { onNewMeetingClick() }
89 | LobbyItem(
90 | icon = painterResource(id = R.drawable.ic_zoom_join_meeting),
91 | caption = stringResource(R.string.join_meeting_navigator),
92 | color = MaterialTheme.colorScheme.primary,
93 | ) { onJoinMeetingClick() }
94 | LobbyItem(
95 | icon = painterResource(id = R.drawable.ic_zoom_breakout),
96 | caption = stringResource(R.string.working_in_progress),
97 | color = Gray50,
98 | )
99 | LobbyItem(
100 | icon = painterResource(id = R.drawable.ic_zoom_breakout),
101 | caption = stringResource(R.string.working_in_progress),
102 | color = Gray50,
103 | )
104 | }
105 | DefaultHorizontalDivider()
106 | }
107 | }
108 | }
109 |
110 | @Composable
111 | private fun LobbyScreenTopAppBar(
112 | onInfoClick: () -> Unit,
113 | ) {
114 | Box(
115 | contentAlignment = Alignment.CenterEnd,
116 | modifier = Modifier
117 | .background(MaterialTheme.colorScheme.surfaceContainerHighest)
118 | .fillMaxWidth()
119 | .height(56.dp)
120 | .padding(horizontal = 4.dp),
121 | ) {
122 | Text(
123 | text = stringResource(R.string.lobby_title),
124 | fontSize = 17.sp,
125 | fontWeight = FontWeight.Medium,
126 | modifier = Modifier.align(Alignment.Center),
127 | color = MaterialTheme.colorScheme.inverseOnSurface,
128 | )
129 | IconButton(onClick = onInfoClick) {
130 | Icon(
131 | imageVector = Icons.Outlined.Info,
132 | contentDescription = "information",
133 | tint = MaterialTheme.colorScheme.inverseOnSurface,
134 | )
135 | }
136 | }
137 | }
138 |
139 | @Composable
140 | private fun LobbyItem(
141 | icon: Painter,
142 | caption: String,
143 | color: Color,
144 | modifier: Modifier = Modifier,
145 | onClick: () -> Unit = {},
146 | ) {
147 | Column(
148 | horizontalAlignment = Alignment.CenterHorizontally,
149 | verticalArrangement = Arrangement.spacedBy(4.dp),
150 | modifier = modifier,
151 | ) {
152 | Box(
153 | contentAlignment = Alignment.Center,
154 | modifier = Modifier
155 | .clip(RoundedCornerShape(16.dp))
156 | .background(color)
157 | .size(64.dp)
158 | .clickable { onClick() },
159 | ) {
160 | Icon(
161 | painter = icon,
162 | contentDescription = caption,
163 | tint = MaterialTheme.colorScheme.onPrimary,
164 | )
165 | }
166 | Text(
167 | text = caption,
168 | fontSize = 12.sp,
169 | color = MaterialTheme.colorScheme.onSurfaceVariant,
170 | letterSpacing = (-0.45).sp,
171 | )
172 | }
173 | }
174 |
175 | @Composable
176 | private fun LobbyInfoDialog(
177 | onDismissRequest: () -> Unit,
178 | ) {
179 | AlertDialog(
180 | title = {
181 | Text(text = stringResource(id = R.string.app_information_title))
182 | },
183 | text = {
184 | Text(text = stringResource(id = R.string.app_information_content))
185 | },
186 | onDismissRequest = {
187 | onDismissRequest()
188 | },
189 | confirmButton = {
190 | TextButton(onClick = { onDismissRequest() }) {
191 | Text(text = stringResource(id = R.string.dialog_confirm))
192 | }
193 | },
194 | )
195 | }
196 |
197 | @Preview
198 | @Composable
199 | private fun LobbyScreenPreview() {
200 | ZoomCloneComposeTheme {
201 | LobbyScreen({}, {})
202 | }
203 | }
204 |
205 | @Preview(showBackground = true)
206 | @Composable
207 | private fun LobbyScreenTopAppBarPreview() {
208 | ZoomCloneComposeTheme {
209 | LobbyScreenTopAppBar {}
210 | }
211 | }
212 |
213 | @Preview(showBackground = true)
214 | @Composable
215 | private fun LobbyItemPreview() {
216 | ZoomCloneComposeTheme {
217 | LobbyItem(
218 | icon = painterResource(id = R.drawable.ic_zoom_video_on),
219 | caption = "New Meeting",
220 | color = MaterialTheme.colorScheme.secondary,
221 | ) {}
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/lobby/navigation/LobbyNavigation.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.lobby.navigation
18 |
19 | import androidx.navigation.NavGraphBuilder
20 | import androidx.navigation.compose.composable
21 | import com.wisemuji.zoomclone.ui.lobby.LobbyScreen
22 | import com.wisemuji.zoomclone.ui.lobby.navigation.LobbyRoute.ROUTE
23 |
24 | fun NavGraphBuilder.lobbyScreen(
25 | onNewMeetingClick: () -> Unit,
26 | onJoinMeetingClick: () -> Unit,
27 | ) {
28 | composable(route = ROUTE) {
29 | LobbyScreen(
30 | onNewMeetingClick = onNewMeetingClick,
31 | onJoinMeetingClick = onJoinMeetingClick,
32 | )
33 | }
34 | }
35 |
36 | object LobbyRoute {
37 | const val ROUTE = "lobby"
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/meetingroom/MeetingRoomScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.meetingroom
18 |
19 | import android.Manifest
20 | import android.os.Build
21 | import androidx.compose.foundation.background
22 | import androidx.compose.foundation.layout.fillMaxSize
23 | import androidx.compose.foundation.layout.fillMaxWidth
24 | import androidx.compose.foundation.layout.navigationBarsPadding
25 | import androidx.compose.foundation.layout.padding
26 | import androidx.compose.foundation.layout.size
27 | import androidx.compose.material.icons.Icons
28 | import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
29 | import androidx.compose.material3.Icon
30 | import androidx.compose.material3.IconButton
31 | import androidx.compose.material3.MaterialTheme
32 | import androidx.compose.material3.Scaffold
33 | import androidx.compose.material3.Text
34 | import androidx.compose.runtime.Composable
35 | import androidx.compose.runtime.DisposableEffect
36 | import androidx.compose.runtime.LaunchedEffect
37 | import androidx.compose.runtime.getValue
38 | import androidx.compose.runtime.mutableStateOf
39 | import androidx.compose.runtime.remember
40 | import androidx.compose.runtime.setValue
41 | import androidx.compose.ui.Alignment
42 | import androidx.compose.ui.Modifier
43 | import androidx.compose.ui.platform.LocalContext
44 | import androidx.compose.ui.res.stringResource
45 | import androidx.compose.ui.text.style.TextAlign
46 | import androidx.compose.ui.tooling.preview.Preview
47 | import androidx.compose.ui.unit.dp
48 | import androidx.hilt.navigation.compose.hiltViewModel
49 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
50 | import com.google.accompanist.permissions.ExperimentalPermissionsApi
51 | import com.google.accompanist.permissions.rememberMultiplePermissionsState
52 | import com.wisemuji.zoomclone.R
53 | import com.wisemuji.zoomclone.ui.component.StatusBarColor
54 | import com.wisemuji.zoomclone.ui.component.ZoomVideoTheme
55 | import io.getstream.video.android.compose.ui.components.call.CallAppBar
56 | import io.getstream.video.android.compose.ui.components.call.controls.ControlActions
57 | import io.getstream.video.android.compose.ui.components.call.controls.actions.DefaultOnCallActionHandler
58 | import io.getstream.video.android.compose.ui.components.call.controls.actions.FlipCameraAction
59 | import io.getstream.video.android.compose.ui.components.call.controls.actions.LeaveCallAction
60 | import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantVideo
61 | import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantsLayout
62 | import io.getstream.video.android.compose.ui.components.call.renderer.RegularVideoRendererStyle
63 | import io.getstream.video.android.core.Call
64 | import io.getstream.video.android.core.call.state.LeaveCall
65 | import io.getstream.video.android.core.mapper.ReactionMapper
66 | import io.getstream.video.android.mock.StreamPreviewDataUtils
67 | import io.getstream.video.android.mock.previewCall
68 |
69 | @Composable
70 | fun MeetingRoomScreen(
71 | onBackPressed: () -> Unit,
72 | viewModel: MeetingRoomViewModel = hiltViewModel(),
73 | ) {
74 | val uiState by viewModel.uiState.collectAsStateWithLifecycle()
75 |
76 | StatusBarColor(color = MaterialTheme.colorScheme.inverseSurface, isIconLight = false)
77 | EnsureVideoCallPermissions {
78 | viewModel.loadMeeting()
79 | }
80 | when (uiState) {
81 | is MeetingUiState.Success -> {
82 | val call = (uiState as MeetingUiState.Success).call
83 | MeetingRoomContent(call = call, onBackPressed)
84 | }
85 |
86 | MeetingUiState.Loading -> {
87 | MeetingRoomPlaceholder(text = stringResource(R.string.connecting))
88 | }
89 |
90 | MeetingUiState.Error -> {
91 | MeetingRoomPlaceholder(text = stringResource(R.string.error))
92 | }
93 | }
94 | }
95 |
96 | @Composable
97 | fun MeetingRoomContent(
98 | call: Call,
99 | onLeaveCall: () -> Unit,
100 | ) {
101 | var isShowingReactionDialog by remember { mutableStateOf(false) }
102 | DisposableEffect(key1 = call.id) {
103 | onDispose { call.leave() }
104 | }
105 | ZoomVideoTheme {
106 | Scaffold(
107 | topBar = {
108 | MeetingRoomTopAppBar(
109 | call = call,
110 | onLeaveCall = onLeaveCall,
111 | )
112 | },
113 | bottomBar = {
114 | MeetingRoomBottomBar(
115 | call = call,
116 | onLeaveCall = onLeaveCall,
117 | toggleReactions = { isShowingReactionDialog = true },
118 | )
119 | },
120 | ) {
121 | ParticipantsLayout(
122 | call = call,
123 | modifier = Modifier
124 | .padding(it)
125 | .fillMaxSize(),
126 | style = RegularVideoRendererStyle(reactionPosition = Alignment.Center),
127 | videoRenderer = { videoModifier, videoCall, videoParticipant, videoStyle ->
128 | ParticipantVideo(
129 | modifier = videoModifier,
130 | call = videoCall,
131 | participant = videoParticipant,
132 | style = videoStyle,
133 | connectionIndicatorContent = {},
134 | )
135 | },
136 | )
137 | if (isShowingReactionDialog) {
138 | ReactionsMenu(
139 | call = call,
140 | reactionMapper = ReactionMapper.defaultReactionMapper(),
141 | onDismiss = { isShowingReactionDialog = false },
142 | )
143 | }
144 | }
145 | }
146 | }
147 |
148 | @Composable
149 | private fun MeetingRoomTopAppBar(
150 | call: Call,
151 | onLeaveCall: () -> Unit,
152 | ) {
153 | CallAppBar(
154 | call = call,
155 | leadingContent = {
156 | IconButton(onClick = { onLeaveCall() }) {
157 | Icon(
158 | imageVector = Icons.AutoMirrored.Filled.KeyboardArrowLeft,
159 | contentDescription = "Back",
160 | tint = MaterialTheme.colorScheme.inverseOnSurface,
161 | modifier = Modifier.size(52.dp),
162 | )
163 | }
164 | FlipCameraAction(
165 | onCallAction = { call.camera.flip() },
166 | )
167 | },
168 | title = stringResource(R.string.meeting_room_title),
169 | trailingContent = {
170 | LeaveCallAction(
171 | modifier = Modifier.size(52.dp),
172 | onCallAction = { onLeaveCall() },
173 | )
174 | },
175 | modifier = Modifier
176 | .background(MaterialTheme.colorScheme.inverseSurface),
177 | )
178 | }
179 |
180 | @Composable
181 | private fun MeetingRoomBottomBar(
182 | call: Call,
183 | onLeaveCall: () -> Unit,
184 | toggleReactions: () -> Unit,
185 | ) {
186 | ControlActions(
187 | modifier = Modifier.navigationBarsPadding(),
188 | call = call,
189 | onCallAction = { action ->
190 | when (action) {
191 | is LeaveCall -> {
192 | onLeaveCall()
193 | }
194 |
195 | else -> DefaultOnCallActionHandler.onCallAction(call, action)
196 | }
197 | },
198 | actions = listOf(
199 | { ToggleMicrophoneButton(call) },
200 | { ToggleCameraButton(call) },
201 | { ToggleReactionsButton(toggleReactions) },
202 | ),
203 | )
204 | }
205 |
206 | @Composable
207 | private fun MeetingRoomPlaceholder(
208 | text: String,
209 | ) {
210 | Scaffold(
211 | containerColor = MaterialTheme.colorScheme.inverseSurface,
212 | ) { innerPadding ->
213 | Text(
214 | text = text,
215 | textAlign = TextAlign.Center,
216 | color = MaterialTheme.colorScheme.inverseOnSurface,
217 | modifier = Modifier
218 | .padding(innerPadding)
219 | .padding(16.dp)
220 | .fillMaxWidth(),
221 | )
222 | }
223 | }
224 |
225 | @OptIn(ExperimentalPermissionsApi::class)
226 | @Composable
227 | private fun EnsureVideoCallPermissions(onPermissionsGranted: () -> Unit) {
228 | // While the SDK will handle the microphone permission,
229 | // its not a bad idea to do it prior to entering any call UIs
230 | val permissionsState = rememberMultiplePermissionsState(
231 | permissions = buildList {
232 | // Access to camera & microphone
233 | add(Manifest.permission.CAMERA)
234 | add(Manifest.permission.RECORD_AUDIO)
235 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
236 | // Allow for foreground service for notification on API 26+
237 | add(Manifest.permission.FOREGROUND_SERVICE)
238 | }
239 | },
240 | )
241 |
242 | LaunchedEffect(key1 = Unit) {
243 | permissionsState.launchMultiplePermissionRequest()
244 | }
245 |
246 | LaunchedEffect(key1 = permissionsState.allPermissionsGranted) {
247 | if (permissionsState.allPermissionsGranted) {
248 | onPermissionsGranted()
249 | }
250 | }
251 | }
252 |
253 | @Preview
254 | @Composable
255 | private fun MeetingRoomContentPreview() {
256 | StreamPreviewDataUtils.initializeStreamVideo(LocalContext.current)
257 | MeetingRoomContent(previewCall) {}
258 | }
259 |
260 | @Preview
261 | @Composable
262 | private fun MeetingRoomError() {
263 | MeetingRoomPlaceholder(text = stringResource(R.string.error))
264 | }
265 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/meetingroom/MeetingRoomViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.meetingroom
18 |
19 | import androidx.lifecycle.SavedStateHandle
20 | import androidx.lifecycle.ViewModel
21 | import androidx.lifecycle.viewModelScope
22 | import com.wisemuji.zoomclone.model.MeetingOptions
23 | import com.wisemuji.zoomclone.ui.meetingroom.navigation.MeetingRoomRoute
24 | import dagger.hilt.android.lifecycle.HiltViewModel
25 | import io.getstream.video.android.core.Call
26 | import io.getstream.video.android.core.StreamVideo
27 | import kotlinx.coroutines.flow.MutableStateFlow
28 | import kotlinx.coroutines.flow.StateFlow
29 | import kotlinx.coroutines.launch
30 | import kotlinx.serialization.json.Json
31 | import javax.inject.Inject
32 |
33 | @HiltViewModel
34 | class MeetingRoomViewModel @Inject constructor(
35 | savedStateHandle: SavedStateHandle,
36 | ) : ViewModel() {
37 | private val meetingOptions = savedStateHandle
38 | .get(MeetingRoomRoute.ARGS_MEETING_OPTIONS)
39 | ?.let { Json.decodeFromString(it) }
40 | ?: error("MeetingOptions not found")
41 |
42 | private val _uiState =
43 | MutableStateFlow(MeetingUiState.Loading)
44 | val uiState: StateFlow = _uiState
45 |
46 | fun loadMeeting(
47 | type: String = DEFAULT_TYPE,
48 | meetingOptions: MeetingOptions = this.meetingOptions,
49 | ) {
50 | updateUsername(meetingOptions.username)
51 | val id = meetingOptions.meetingId
52 | val call = StreamVideo.instance().call(type = type, id = id)
53 | viewModelScope.launch {
54 | val result = call.join(create = true)
55 | result.onSuccess {
56 | call.applyMeetingOptions(meetingOptions)
57 | _uiState.value = MeetingUiState.Success(call)
58 | }.onError {
59 | _uiState.value = MeetingUiState.Error
60 | }
61 | }
62 | }
63 |
64 | private fun updateUsername(username: String) {
65 | if (username.isEmpty()) return
66 | // TODO: Update the user's name
67 | }
68 |
69 | private fun Call.applyMeetingOptions(meetingOptions: MeetingOptions) {
70 | camera.setEnabled(meetingOptions.videoOn)
71 | microphone.setEnabled(meetingOptions.audioOn)
72 | }
73 |
74 | companion object {
75 | private const val DEFAULT_TYPE = "default"
76 | }
77 | }
78 |
79 | sealed interface MeetingUiState {
80 | data object Loading : MeetingUiState
81 | data class Success(val call: Call) : MeetingUiState
82 | data object Error : MeetingUiState
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/meetingroom/ReactionItemData.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.meetingroom
18 |
19 | import androidx.compose.foundation.clickable
20 | import androidx.compose.foundation.layout.Arrangement
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.fillMaxWidth
25 | import androidx.compose.foundation.layout.padding
26 | import androidx.compose.foundation.layout.wrapContentWidth
27 | import androidx.compose.material3.Card
28 | import androidx.compose.material3.CardDefaults
29 | import androidx.compose.material3.MaterialTheme
30 | import androidx.compose.material3.Text
31 | import androidx.compose.runtime.Composable
32 | import androidx.compose.runtime.rememberCoroutineScope
33 | import androidx.compose.ui.Modifier
34 | import androidx.compose.ui.platform.LocalContext
35 | import androidx.compose.ui.text.style.TextAlign
36 | import androidx.compose.ui.tooling.preview.Preview
37 | import androidx.compose.ui.unit.dp
38 | import androidx.compose.ui.window.Dialog
39 | import com.wisemuji.zoomclone.ui.component.ZoomVideoTheme
40 | import com.wisemuji.zoomclone.ui.theme.ZoomCloneComposeTheme
41 | import io.getstream.video.android.core.Call
42 | import io.getstream.video.android.core.mapper.ReactionMapper
43 | import io.getstream.video.android.mock.StreamPreviewDataUtils
44 | import io.getstream.video.android.mock.previewCall
45 | import kotlinx.coroutines.CoroutineScope
46 | import kotlinx.coroutines.launch
47 |
48 | private data class ReactionItemData(val emojiDescription: String, val emojiCode: String)
49 |
50 | private object DefaultReactionsMenuData {
51 | val mainReaction = ReactionItemData("Raise hand", ":raise-hand:")
52 | val defaultReactions = listOf(
53 | ReactionItemData("Wave", ":hello:"),
54 | ReactionItemData("Like", ":raise-hand:"),
55 | ReactionItemData("Heart", ":heart:"),
56 | ReactionItemData("Joy", "😂"),
57 | ReactionItemData("Opened mouth", "😮"),
58 | ReactionItemData("Fireworks", ":fireworks:"),
59 | )
60 | }
61 |
62 | @Composable
63 | internal fun ReactionsMenu(
64 | call: Call,
65 | reactionMapper: ReactionMapper,
66 | onDismiss: () -> Unit,
67 | ) {
68 | val scope = rememberCoroutineScope()
69 | val modifier = Modifier
70 | .wrapContentWidth()
71 | val onEmojiSelected: (emoji: String) -> Unit = {
72 | sendReaction(scope, call, it, onDismiss)
73 | }
74 |
75 | Dialog(onDismiss) {
76 | Card(
77 | colors = CardDefaults.cardColors().copy(
78 | containerColor = MaterialTheme.colorScheme.surfaceContainerHighest,
79 | contentColor = MaterialTheme.colorScheme.inverseOnSurface,
80 | ),
81 | modifier = modifier.wrapContentWidth(),
82 | ) {
83 | Column(Modifier.padding(16.dp)) {
84 | Row(horizontalArrangement = Arrangement.Center) {
85 | ReactionItem(
86 | modifier = Modifier
87 | .fillMaxWidth(),
88 | textModifier = Modifier.fillMaxWidth(),
89 | reactionMapper = reactionMapper,
90 | reaction = DefaultReactionsMenuData.mainReaction,
91 | showDescription = true,
92 | onEmojiSelected = onEmojiSelected,
93 | )
94 | }
95 | Row(
96 | horizontalArrangement = Arrangement.Center,
97 | ) {
98 | DefaultReactionsMenuData.defaultReactions.forEach {
99 | ReactionItem(
100 | modifier = modifier.weight(1f),
101 | reactionMapper = reactionMapper,
102 | onEmojiSelected = onEmojiSelected,
103 | reaction = it,
104 | )
105 | }
106 | }
107 | }
108 | }
109 | }
110 | }
111 |
112 | @Composable
113 | private fun ReactionItem(
114 | modifier: Modifier = Modifier,
115 | textModifier: Modifier = Modifier,
116 | reactionMapper: ReactionMapper,
117 | reaction: ReactionItemData,
118 | onEmojiSelected: (emoji: String) -> Unit,
119 | showDescription: Boolean = false,
120 | ) {
121 | val mappedEmoji = reactionMapper.map(reaction.emojiCode)
122 | Box(
123 | modifier = modifier
124 | .clickable {
125 | onEmojiSelected(reaction.emojiCode)
126 | }
127 | .padding(2.dp),
128 | ) {
129 | Text(
130 | textAlign = TextAlign.Center,
131 | modifier = textModifier.padding(12.dp),
132 | text = "$mappedEmoji${if (showDescription) reaction.emojiDescription else ""}",
133 | )
134 | }
135 | }
136 |
137 | private fun sendReaction(scope: CoroutineScope, call: Call, emoji: String, onDismiss: () -> Unit) {
138 | scope.launch {
139 | call.sendReaction("default", emoji)
140 | onDismiss()
141 | }
142 | }
143 |
144 | @Preview
145 | @Composable
146 | private fun ReactionMenuPreview() {
147 | ZoomCloneComposeTheme {
148 | ZoomVideoTheme {
149 | StreamPreviewDataUtils.initializeStreamVideo(LocalContext.current)
150 | ReactionsMenu(
151 | call = previewCall,
152 | reactionMapper = ReactionMapper.defaultReactionMapper(),
153 | onDismiss = { },
154 | )
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/meetingroom/ToggleButton.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.meetingroom
18 |
19 | import androidx.compose.material3.FilledIconButton
20 | import androidx.compose.material3.Icon
21 | import androidx.compose.material3.IconButtonDefaults
22 | import androidx.compose.material3.MaterialTheme
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.runtime.getValue
25 | import androidx.compose.ui.res.painterResource
26 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
27 | import com.wisemuji.zoomclone.R
28 | import io.getstream.video.android.core.Call
29 |
30 | @Composable
31 | fun ToggleMicrophoneButton(call: Call) {
32 | val isMicrophoneEnabled by call.microphone.isEnabled.collectAsStateWithLifecycle()
33 | ToggleButton(
34 | onClick = { call.microphone.setEnabled(!isMicrophoneEnabled) },
35 | enabled = isMicrophoneEnabled,
36 | enabledIcon = R.drawable.ic_zoom_mic_on,
37 | disabledIcon = R.drawable.ic_zoom_mic_off,
38 | )
39 | }
40 |
41 | @Composable
42 | fun ToggleCameraButton(call: Call) {
43 | val isCameraEnabled by call.camera.isEnabled.collectAsStateWithLifecycle()
44 | ToggleButton(
45 | onClick = { call.camera.setEnabled(!isCameraEnabled) },
46 | enabled = isCameraEnabled,
47 | enabledIcon = R.drawable.ic_zoom_video_on,
48 | disabledIcon = R.drawable.ic_zoom_video_off,
49 | )
50 | }
51 |
52 | @Composable
53 | fun ToggleReactionsButton(toggleReactions: () -> Unit) {
54 | ToggleButton(
55 | onClick = toggleReactions,
56 | enabled = true,
57 | enabledIcon = R.drawable.ic_zoom_reaction,
58 | )
59 | }
60 |
61 | @Composable
62 | private fun ToggleButton(
63 | onClick: () -> Unit,
64 | enabled: Boolean,
65 | enabledIcon: Int,
66 | disabledIcon: Int = enabledIcon,
67 | ) {
68 | FilledIconButton(
69 | onClick = onClick,
70 | colors = IconButtonDefaults.filledIconButtonColors(
71 | containerColor = MaterialTheme.colorScheme.inverseSurface,
72 | ),
73 | ) {
74 | if (enabled) {
75 | Icon(
76 | painterResource(enabledIcon),
77 | contentDescription = "enable toggle",
78 | )
79 | } else {
80 | Icon(
81 | painterResource(disabledIcon),
82 | contentDescription = "disable toggle",
83 | )
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/meetingroom/navigation/MeetingRoomNavigation.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.meetingroom.navigation
18 |
19 | import androidx.navigation.NavController
20 | import androidx.navigation.NavGraphBuilder
21 | import androidx.navigation.NavType
22 | import androidx.navigation.compose.composable
23 | import androidx.navigation.navArgument
24 | import com.wisemuji.zoomclone.model.MeetingOptions
25 | import com.wisemuji.zoomclone.ui.meetingroom.MeetingRoomScreen
26 | import com.wisemuji.zoomclone.ui.meetingroom.navigation.MeetingRoomRoute.ARGS_MEETING_OPTIONS
27 | import com.wisemuji.zoomclone.ui.meetingroom.navigation.MeetingRoomRoute.ROUTE
28 | import kotlinx.serialization.encodeToString
29 | import kotlinx.serialization.json.Json
30 |
31 | fun NavController.navigateToMeetingRoom(meetingOptions: MeetingOptions) {
32 | val encoded = Json.encodeToString(meetingOptions)
33 | popBackStack()
34 | navigate(ROUTE.replace("{$ARGS_MEETING_OPTIONS}", encoded)) {
35 | launchSingleTop = true
36 | }
37 | }
38 |
39 | fun NavGraphBuilder.meetingRoomScreen(
40 | onBackPressed: () -> Unit,
41 | ) {
42 | composable(
43 | route = ROUTE,
44 | arguments = listOf(
45 | navArgument(ARGS_MEETING_OPTIONS) { type = NavType.StringType },
46 | ),
47 | ) {
48 | MeetingRoomScreen(onBackPressed)
49 | }
50 | }
51 |
52 | object MeetingRoomRoute {
53 | const val ARGS_MEETING_OPTIONS = "meeting_options"
54 | const val ROUTE = "meeting_room/{${ARGS_MEETING_OPTIONS}}"
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/newmeeting/NewMeetingScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.newmeeting
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.Spacer
23 | import androidx.compose.foundation.layout.fillMaxSize
24 | import androidx.compose.foundation.layout.fillMaxWidth
25 | import androidx.compose.foundation.layout.height
26 | import androidx.compose.foundation.layout.padding
27 | import androidx.compose.material3.MaterialTheme
28 | import androidx.compose.material3.Scaffold
29 | import androidx.compose.material3.SnackbarHost
30 | import androidx.compose.material3.SnackbarHostState
31 | import androidx.compose.material3.Text
32 | import androidx.compose.material3.TextButton
33 | import androidx.compose.runtime.Composable
34 | import androidx.compose.runtime.LaunchedEffect
35 | import androidx.compose.runtime.getValue
36 | import androidx.compose.runtime.mutableStateOf
37 | import androidx.compose.runtime.remember
38 | import androidx.compose.runtime.setValue
39 | import androidx.compose.ui.Alignment.Companion.Center
40 | import androidx.compose.ui.Alignment.Companion.CenterStart
41 | import androidx.compose.ui.Modifier
42 | import androidx.compose.ui.res.stringResource
43 | import androidx.compose.ui.text.font.FontWeight.Companion.Medium
44 | import androidx.compose.ui.tooling.preview.Preview
45 | import androidx.compose.ui.unit.dp
46 | import androidx.compose.ui.unit.sp
47 | import com.wisemuji.zoomclone.R
48 | import com.wisemuji.zoomclone.model.MeetingOptions
49 | import com.wisemuji.zoomclone.ui.component.DefaultHorizontalDivider
50 | import com.wisemuji.zoomclone.ui.component.StatusBarColor
51 | import com.wisemuji.zoomclone.ui.component.ZoomFullSizeButton
52 | import com.wisemuji.zoomclone.ui.component.ZoomSwitchRow
53 | import com.wisemuji.zoomclone.ui.theme.ZoomCloneComposeTheme
54 |
55 | @Composable
56 | fun NewMeetingScreen(
57 | onBackPressed: () -> Unit,
58 | onJoinMeetingClick: (MeetingOptions) -> Unit,
59 | ) {
60 | val snackbarHostState = remember { SnackbarHostState() }
61 | var showNotImplementedSnackbar by remember { mutableStateOf(false) }
62 | var videoOn by remember { mutableStateOf(true) }
63 |
64 | if (showNotImplementedSnackbar) {
65 | LaunchedEffect(snackbarHostState) {
66 | snackbarHostState.showSnackbar("Not implemented yet. Do you want to contribute? :)")
67 | showNotImplementedSnackbar = false
68 | }
69 | }
70 | StatusBarColor(color = MaterialTheme.colorScheme.surface, isIconLight = true)
71 | Scaffold(
72 | snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
73 | topBar = { NewMeetingTopAppBar(onBack = onBackPressed) },
74 | ) { innerPadding ->
75 | Column(
76 | modifier = Modifier
77 | .padding(innerPadding)
78 | .fillMaxSize()
79 | .background(MaterialTheme.colorScheme.surfaceContainer),
80 | ) {
81 | DefaultHorizontalDivider()
82 | Spacer(modifier = Modifier.padding(12.dp))
83 | DefaultHorizontalDivider()
84 | ZoomSwitchRow(
85 | title = stringResource(R.string.video_on),
86 | checked = videoOn,
87 | onCheckedChange = { videoOn = it },
88 | )
89 | DefaultHorizontalDivider()
90 | ZoomSwitchRow(
91 | title = stringResource(R.string.use_personal_meeting_id),
92 | subtitle = stringResource(R.string.personal_meeting_id),
93 | checked = false,
94 | onCheckedChange = { showNotImplementedSnackbar = true },
95 | )
96 | DefaultHorizontalDivider()
97 | ZoomFullSizeButton(
98 | text = stringResource(R.string.new_meeting_title),
99 | onClick = { onJoinMeetingClick(MeetingOptions(videoOn = videoOn)) },
100 | modifier = Modifier.padding(24.dp),
101 | )
102 | }
103 | }
104 | }
105 |
106 | @Composable
107 | private fun NewMeetingTopAppBar(onBack: () -> Unit) {
108 | Box(
109 | contentAlignment = CenterStart,
110 | modifier = Modifier
111 | .fillMaxWidth()
112 | .height(56.dp)
113 | .padding(horizontal = 4.dp),
114 | ) {
115 | TextButton(onClick = onBack) {
116 | Text(text = stringResource(R.string.cancel), fontSize = 17.sp)
117 | }
118 | Text(
119 | text = stringResource(R.string.new_meeting_title),
120 | fontSize = 17.sp,
121 | fontWeight = Medium,
122 | modifier = Modifier.align(Center),
123 | )
124 | }
125 | }
126 |
127 | @Preview
128 | @Composable
129 | private fun NewMeetingScreenPreview() {
130 | ZoomCloneComposeTheme {
131 | NewMeetingScreen({}, {})
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/newmeeting/navigation/NewMeetingNavigation.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.newmeeting.navigation
18 |
19 | import androidx.navigation.NavController
20 | import androidx.navigation.NavGraphBuilder
21 | import androidx.navigation.compose.composable
22 | import com.wisemuji.zoomclone.model.MeetingOptions
23 | import com.wisemuji.zoomclone.ui.newmeeting.NewMeetingScreen
24 | import com.wisemuji.zoomclone.ui.newmeeting.navigation.NewMeetingRoute.ROUTE
25 |
26 | fun NavController.navigateToNewMeeting() {
27 | navigate(ROUTE)
28 | }
29 |
30 | fun NavGraphBuilder.newMeetingScreen(
31 | onBackPressed: () -> Unit,
32 | onJoinMeetingClick: (MeetingOptions) -> Unit,
33 | ) {
34 | composable(route = ROUTE) {
35 | NewMeetingScreen(onBackPressed, onJoinMeetingClick)
36 | }
37 | }
38 |
39 | object NewMeetingRoute {
40 | const val ROUTE = "new_meeting"
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.theme
18 |
19 | import androidx.compose.ui.graphics.Color
20 |
21 | val Orange = Color(0xFFFF742F)
22 | val Blue = Color(0xFF0E72EC)
23 | val Red = Color(0xFFDE2827)
24 | val Green = Color(0xFF4FD963)
25 |
26 | val Black = Color(0xFF000000)
27 | val Gray80 = Color(0xFF38394D)
28 | val Gray60 = Color(0xFF717075)
29 | val Gray50 = Color(0xFF929197)
30 | val Gray30 = Color(0xFFE4E4E4)
31 | val Gray20 = Color(0xFFEBEAEF)
32 | val Gray10 = Color(0xFFF9F9F9)
33 | val White = Color(0xFFFFFFFF)
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.theme
18 |
19 | import android.app.Activity
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.lightColorScheme
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.runtime.SideEffect
24 | import androidx.compose.ui.graphics.toArgb
25 | import androidx.compose.ui.platform.LocalView
26 | import androidx.core.view.WindowCompat
27 |
28 | private val LightColorScheme = lightColorScheme(
29 | primary = Blue,
30 | onPrimary = White,
31 | secondary = Orange,
32 | onSecondary = White,
33 | tertiary = Green,
34 | onTertiary = White,
35 | background = White,
36 | onBackground = Black,
37 | surface = White,
38 | onSurface = Black,
39 | onSurfaceVariant = Gray80,
40 | surfaceContainer = Gray10,
41 | surfaceVariant = White,
42 | surfaceContainerHighest = Gray80,
43 | inverseSurface = Black,
44 | inverseOnSurface = White,
45 | outlineVariant = Gray30,
46 | )
47 |
48 | // TODO: Add dark color scheme
49 | private val DarkColorScheme = LightColorScheme
50 |
51 | @Composable
52 | fun ZoomCloneComposeTheme(
53 | darkTheme: Boolean = false,
54 | content: @Composable () -> Unit,
55 | ) {
56 | val colorScheme = when {
57 | darkTheme -> DarkColorScheme
58 | else -> LightColorScheme
59 | }
60 | val view = LocalView.current
61 | if (!view.isInEditMode) {
62 | SideEffect {
63 | val window = (view.context as Activity).window
64 | window.statusBarColor = colorScheme.primary.toArgb()
65 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
66 | }
67 | }
68 |
69 | MaterialTheme(
70 | colorScheme = colorScheme,
71 | typography = Typography,
72 | content = content,
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wisemuji/zoomclone/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 com.wisemuji.zoomclone.ui.theme
18 |
19 | import androidx.compose.material3.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 | bodyLarge = TextStyle(
28 | fontFamily = FontFamily.Default,
29 | fontWeight = FontWeight.Normal,
30 | fontSize = 16.sp,
31 | lineHeight = 24.sp,
32 | letterSpacing = 0.5.sp,
33 | ),
34 | /* Other default text styles to override
35 | titleLarge = TextStyle(
36 | fontFamily = FontFamily.Default,
37 | fontWeight = FontWeight.Normal,
38 | fontSize = 22.sp,
39 | lineHeight = 28.sp,
40 | letterSpacing = 0.sp
41 | ),
42 | labelSmall = TextStyle(
43 | fontFamily = FontFamily.Default,
44 | fontWeight = FontWeight.Medium,
45 | fontSize = 11.sp,
46 | lineHeight = 16.sp,
47 | letterSpacing = 0.5.sp
48 | )
49 | */
50 | )
51 |
--------------------------------------------------------------------------------
/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/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zoom_breakout.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zoom_chat.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zoom_join_meeting.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zoom_mic_off.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zoom_mic_on.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zoom_participants.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zoom_reaction.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zoom_video_off.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_zoom_video_on.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ZoomCloneCompose
3 |
4 | Meetings
5 | New Meeting
6 | Join
7 | WIP
8 |
9 | Start a Meeting
10 | Cancel
11 | Video on
12 | Use personal meeting ID (PMI)
13 | XXX XXX XXXX
14 |
15 | Join a Meeting
16 | Meeting ID
17 | Your Name
18 | Join
19 | If you received an invitation link, tap on the link to join the meeting
20 | Join options
21 | Don\'t Connect To Audio
22 | Turn Off My Video
23 |
24 | Zoom Clone
25 | Connecting…
26 | Oops! Something went wrong.
27 |
28 | About
29 | This is a Zoom clone app built with Stream Video SDK for Compose to implement real-time video meeting features using Jetpack Compose.
30 | OK
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
20 |
--------------------------------------------------------------------------------
/build-logic/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | `kotlin-dsl-precompiled-script-plugins`
4 | }
5 |
6 | dependencies {
7 | implementation(libs.android.gradlePlugin)
8 | implementation(libs.kotlin.gradlePlugin)
9 | implementation(libs.spotless.gradlePlugin)
10 | }
11 |
12 | gradlePlugin {
13 | plugins {
14 | register("androidHilt") {
15 | id = "zoomclone.android.hilt"
16 | implementationClass = "com.wisemuji.zoomclone.HiltAndroidPlugin"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
2 | dependencyResolutionManagement {
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | versionCatalogs {
8 | create("libs") {
9 | from(files("../gradle/libs.versions.toml"))
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/wisemuji/zoomclone/ComposeAndroid.kt:
--------------------------------------------------------------------------------
1 | package com.wisemuji.zoomclone
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.kotlin.dsl.dependencies
5 |
6 | internal fun Project.configureComposeAndroid() {
7 | val libs = extensions.libs
8 | androidExtension.apply {
9 | buildFeatures {
10 | compose = true
11 | }
12 | composeOptions {
13 | kotlinCompilerExtensionVersion =
14 | libs.findVersion("androidxComposeCompiler").get().toString()
15 | }
16 |
17 | dependencies {
18 | val bom = libs.findLibrary("androidx-compose-bom").get()
19 | add("implementation", platform(bom))
20 | add("androidTestImplementation", platform(bom))
21 |
22 | add("implementation", libs.findLibrary("androidx.compose.material3").get())
23 | add("implementation", libs.findLibrary("androidx.compose.ui").get())
24 | add("implementation", libs.findLibrary("androidx.compose.ui.tooling.preview").get())
25 | add("androidTestImplementation", libs.findLibrary("androidx.test.ext").get())
26 | add("androidTestImplementation", libs.findLibrary("androidx.compose.ui.test").get())
27 | add("debugImplementation", libs.findLibrary("androidx.compose.ui.tooling").get())
28 | add("debugImplementation", libs.findLibrary("androidx.compose.ui.testManifest").get())
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/wisemuji/zoomclone/Extension.kt:
--------------------------------------------------------------------------------
1 | package com.wisemuji.zoomclone
2 |
3 | import com.android.build.api.dsl.ApplicationExtension
4 | import com.android.build.api.dsl.CommonExtension
5 | import com.android.build.api.dsl.LibraryExtension
6 | import org.gradle.api.Project
7 | import org.gradle.api.artifacts.VersionCatalog
8 | import org.gradle.api.artifacts.VersionCatalogsExtension
9 | import org.gradle.api.plugins.ExtensionContainer
10 | import org.gradle.kotlin.dsl.getByType
11 |
12 | internal val Project.applicationExtension: CommonExtension<*, *, *, *, *>
13 | get() = extensions.getByType()
14 |
15 | internal val Project.libraryExtension: CommonExtension<*, *, *, *, *>
16 | get() = extensions.getByType()
17 |
18 | internal val Project.androidExtension: CommonExtension<*, *, *, *, *>
19 | get() = runCatching { libraryExtension }
20 | .recoverCatching { applicationExtension }
21 | .onFailure { println("Could not find Library or Application extension from this project") }
22 | .getOrThrow()
23 |
24 | internal val ExtensionContainer.libs: VersionCatalog
25 | get() = getByType().named("libs")
26 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/wisemuji/zoomclone/HiltAndroid.kt:
--------------------------------------------------------------------------------
1 | package com.wisemuji.zoomclone
2 |
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.dependencies
6 |
7 | internal fun Project.configureHiltAndroid() {
8 | with(pluginManager) {
9 | apply("dagger.hilt.android.plugin")
10 | apply("org.jetbrains.kotlin.kapt")
11 | }
12 |
13 | val libs = extensions.libs
14 | dependencies {
15 | "implementation"(libs.findLibrary("hilt.android").get())
16 | "implementation"(libs.findLibrary("androidx.hilt.navigation.compose").get())
17 | "kapt"(libs.findLibrary("hilt.android.compiler").get())
18 | "kaptAndroidTest"(libs.findLibrary("hilt.android.compiler").get())
19 | }
20 | }
21 |
22 | internal class HiltAndroidPlugin : Plugin {
23 |
24 | override fun apply(target: Project) {
25 | with(target) {
26 | configureHiltAndroid()
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/wisemuji/zoomclone/KotlinAndroid.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | package com.wisemuji.zoomclone
4 |
5 | import org.gradle.api.JavaVersion
6 | import org.gradle.api.Project
7 | import org.gradle.kotlin.dsl.dependencies
8 | import org.gradle.kotlin.dsl.provideDelegate
9 | import org.gradle.kotlin.dsl.withType
10 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
11 |
12 | /**
13 | * https://github.com/android/nowinandroid/blob/main/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt
14 | */
15 | internal fun Project.configureKotlinAndroid() {
16 | // Plugins
17 | pluginManager.apply("org.jetbrains.kotlin.android")
18 |
19 | // Android settings
20 | androidExtension.apply {
21 | compileSdk = 34
22 |
23 | defaultConfig {
24 | minSdk = 26
25 | }
26 |
27 | compileOptions {
28 | sourceCompatibility = JavaVersion.VERSION_17
29 | targetCompatibility = JavaVersion.VERSION_17
30 | isCoreLibraryDesugaringEnabled = true
31 | }
32 |
33 | buildTypes {
34 | getByName("release") {
35 | isMinifyEnabled = false
36 | proguardFiles(
37 | getDefaultProguardFile("proguard-android-optimize.txt"),
38 | "proguard-rules.pro"
39 | )
40 | }
41 | }
42 | }
43 |
44 | configureKotlin()
45 |
46 | val libs = extensions.libs
47 |
48 | dependencies {
49 | add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get())
50 | }
51 | }
52 |
53 | internal fun Project.configureKotlin() {
54 | tasks.withType().configureEach {
55 | kotlinOptions {
56 | jvmTarget = JavaVersion.VERSION_17.toString()
57 | // Treat all Kotlin warnings as errors (disabled by default)
58 | // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties
59 | val warningsAsErrors: String? by project
60 | allWarningsAsErrors = warningsAsErrors.toBoolean()
61 | freeCompilerArgs = freeCompilerArgs + listOf(
62 | "-opt-in=kotlin.RequiresOptIn",
63 | )
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/wisemuji/zoomclone/Spotless.kt:
--------------------------------------------------------------------------------
1 | package com.wisemuji.zoomclone
2 |
3 | import com.diffplug.gradle.spotless.SpotlessExtension
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.configure
6 |
7 | internal fun Project.configureSpotless() {
8 | pluginManager.apply("com.diffplug.spotless")
9 |
10 | extensions.configure {
11 | kotlin {
12 | target("**/*.kt")
13 | targetExclude("**/build/**/*.kt")
14 | ktlint("1.2.1")
15 | .editorConfigOverride(
16 | mapOf(
17 | "ktlint_standard_max-line-length" to "disabled",
18 | "ktlint_function_naming_ignore_when_annotated_with" to "Composable"
19 | )
20 | )
21 |
22 | trimTrailingWhitespace()
23 | indentWithSpaces()
24 | endWithNewline()
25 |
26 | licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
27 | }
28 | kotlinGradle {
29 | target("**/*.kts")
30 | targetExclude("**/build/**/*.kts")
31 | ktlint("1.2.1")
32 |
33 | trimTrailingWhitespace()
34 | indentWithSpaces()
35 | endWithNewline()
36 |
37 | licenseHeaderFile(
38 | rootProject.file("spotless/copyright.kts"),
39 | "(^(?![\\/ ]\\*).*$)"
40 | )
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/zoomclone.android.application.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.wisemuji.zoomclone.configureHiltAndroid
2 | import com.wisemuji.zoomclone.configureKotlinAndroid
3 | import com.wisemuji.zoomclone.configureSpotless
4 |
5 | plugins {
6 | id("com.android.application")
7 | }
8 |
9 | configureKotlinAndroid()
10 | configureHiltAndroid()
11 | configureSpotless()
12 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/zoomclone.android.compose.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.wisemuji.zoomclone.configureComposeAndroid
2 |
3 | configureComposeAndroid()
4 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("DSL_SCOPE_VIOLATION")
2 |
3 | plugins {
4 | alias(libs.plugins.android.application) apply false
5 | alias(libs.plugins.kotlin.android) apply false
6 | alias(libs.plugins.kotlin.jvm) apply false
7 | alias(libs.plugins.kotlin.serialization) apply false
8 | alias(libs.plugins.hilt) apply false
9 | alias(libs.plugins.google.secrets) apply false
10 | alias(libs.plugins.spotless) apply false
11 | }
12 |
--------------------------------------------------------------------------------
/figures/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/figures/cover.jpg
--------------------------------------------------------------------------------
/figures/stream0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/figures/stream0.png
--------------------------------------------------------------------------------
/figures/stream1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/figures/stream1.png
--------------------------------------------------------------------------------
/figures/stream2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/figures/stream2.png
--------------------------------------------------------------------------------
/figures/stream3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/figures/stream3.png
--------------------------------------------------------------------------------
/figures/stream4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/figures/stream4.png
--------------------------------------------------------------------------------
/figures/stream5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/figures/stream5.png
--------------------------------------------------------------------------------
/figures/stream6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/figures/stream6.png
--------------------------------------------------------------------------------
/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 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | androidGradlePlugin = "8.2.2"
3 | androidDesugarJdkLibs = "2.0.4"
4 | androidxCore = "1.12.0"
5 | androidxLifecycle = "2.7.0"
6 | androidxComposeBom = "2024.02.00"
7 | androidxComposeCompiler = "1.5.9"
8 | androidxComposeNavigation = "2.7.7"
9 | androidxComposeMaterial3 = "1.2.0"
10 | androidxHiltNavigationCompose = "1.0.0"
11 | androidxActivity = "1.8.2"
12 | androidxTestExt = "1.1.5"
13 | accompanist = "0.34.0"
14 | googleSecretPlugin = "2.0.1"
15 | hilt = "2.50"
16 |
17 | kotlinxSerializationJson = "1.6.2"
18 | kotlinxDatetime = "0.2.1"
19 | kotlinxImmutable = "0.3.5"
20 |
21 | kotlin = "1.9.22"
22 |
23 | coroutine = "1.7.3"
24 |
25 | stream-video-sdk = "0.5.1"
26 |
27 | spotless = "6.25.0"
28 |
29 | [libraries]
30 | android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
31 | android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
32 | kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
33 | spotless-gradlePlugin = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotless" }
34 |
35 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
36 | androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
37 | androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
38 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
39 |
40 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
41 | androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidxComposeMaterial3" }
42 | androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
43 | androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4" }
44 | androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
45 | androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
46 | androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
47 | androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxComposeNavigation" }
48 | androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
49 |
50 | accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist" }
51 |
52 | hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
53 | hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
54 | hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
55 |
56 | kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
57 | kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
58 | kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" }
59 |
60 | coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutine" }
61 | coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutine" }
62 | coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutine" }
63 |
64 | androidx-test-ext = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxTestExt" }
65 |
66 | stream-video-ui-compose = { group = "io.getstream", name = "stream-video-android-ui-compose", version.ref = "stream-video-sdk" }
67 | stream-video-ui-previewdata = { group = "io.getstream", name = "stream-video-android-previewdata", version.ref = "stream-video-sdk" }
68 |
69 | [plugins]
70 | android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
71 | android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
72 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
73 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
74 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
75 | hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
76 | google-secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "googleSecretPlugin" }
77 | spotless = { id = "com.diffplug.gradle.spotless", version.ref = "spotless" }
78 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jan 30 23:28:58 KST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or 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 UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/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 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/previews/preview0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/previews/preview0.png
--------------------------------------------------------------------------------
/previews/preview1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisemuji/zoom-clone-compose/890f44f2d2954746c15cf796fec928c753e0e1da/previews/preview1.png
--------------------------------------------------------------------------------
/secrets.defaults.properties:
--------------------------------------------------------------------------------
1 | STREAM_API_KEY=STREAM_API_KEY
2 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | includeBuild("build-logic")
3 | repositories {
4 | google()
5 | mavenCentral()
6 | gradlePluginPortal()
7 | }
8 | }
9 | dependencyResolutionManagement {
10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
11 | repositories {
12 | google()
13 | mavenCentral()
14 | }
15 | }
16 |
17 | rootProject.name = "ZoomCloneCompose"
18 | include(":app")
19 |
--------------------------------------------------------------------------------
/spotless/copyright.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 |
--------------------------------------------------------------------------------
/spotless/copyright.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Suhyeon(wisemuji) and 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 |
--------------------------------------------------------------------------------
/spotless/spotless.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.diffplug.spotless")
3 | }
4 |
5 | spotless {
6 | kotlin {
7 | target("**/*.kt", "**/*.kts")
8 | targetExclude("**/build/**/*.*")
9 | ktlint().setUseExperimental(true).editorConfigOverride(['indent_size': '2', 'continuation_indent_size': '2'])
10 | licenseHeaderFile "$rootDir/spotless/copyright.kt"
11 | trimTrailingWhitespace()
12 | endWithNewline()
13 | }
14 | }
15 |
--------------------------------------------------------------------------------