├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── pull_request_template.md
├── scripts
│ └── gradlew_recursive.sh
└── workflows
│ ├── deploy.yml
│ └── test&build.yml
├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── copyright
│ ├── nphau.xml
│ └── profiles_settings.xml
├── deploymentTargetDropDown.xml
├── deploymentTargetSelector.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── kotlinc.xml
├── migrations.xml
├── misc.xml
├── runConfigurations.xml
├── studiobot.xml
└── vcs.xml
├── .project
├── .settings
└── org.eclipse.buildship.core.prefs
├── LICENSE
├── README.md
├── SECURITY.md
├── app
├── .classpath
├── .gitignore
├── .project
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── nphausg
│ │ └── app
│ │ └── embeddedserver
│ │ ├── EmbeddedServer.kt
│ │ ├── data
│ │ ├── BaseResponse.kt
│ │ ├── Database.kt
│ │ └── models
│ │ │ ├── Cart.kt
│ │ │ └── Fruit.kt
│ │ ├── ui
│ │ ├── AnimatedLogo.kt
│ │ └── MainActivity.kt
│ │ └── utils
│ │ ├── FileUtils.kt
│ │ └── NetworkUtils.kt
│ ├── res
│ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ └── logo.png
│ ├── mipmap-anydpi-v26
│ │ └── ic_launcher.xml
│ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ ├── values
│ │ └── strings.xml
│ └── xml
│ │ └── file_provider_paths.xml
│ └── resources
│ ├── data.json
│ ├── docs
│ ├── demo.gif
│ ├── detail.jpg
│ ├── edge_get.gif
│ └── fruits.jpg
│ ├── favicon.ico
│ ├── files
│ └── file.jpg
│ └── index.html
├── build.gradle
├── docs
├── demo.gif
├── detail.jpg
├── edge_get.gif
├── fruits.jpg
├── static_config.jpg
└── static_demo.jpg
├── foundation
└── ui
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── masewsg
│ │ └── app
│ │ └── ui
│ │ ├── ComposeApp.kt
│ │ └── components
│ │ ├── Background.kt
│ │ ├── button
│ │ └── Button.kt
│ │ ├── color
│ │ ├── Color.kt
│ │ ├── Gradient.kt
│ │ └── Tint.kt
│ │ ├── icon
│ │ └── Icons.kt
│ │ ├── theme
│ │ └── Theme.kt
│ │ └── typography
│ │ └── Typography.kt
│ └── res
│ ├── values-night
│ └── themes.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ └── themes.xml
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── scripts
├── cleanup.sh
└── rebase.sh
└── settings.gradle
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: nphausg
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## :bug: Describe the bug
11 | A clear and concise description of what the bug is.
12 |
13 | ## :runner: Steps To Reproduce
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | ## :white_check_mark: Expected behavior
21 | A clear and concise description of what you expected to happen.
22 |
23 | ## :framed_picture: Screenshots
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | ## :iphone: Devices (please complete the following information):**
27 | - Device: [e.g. iPhone6]
28 | - OS: [e.g. iOS8.1]
29 | - Version [e.g. 22]
30 |
31 | ## :construction: Additional context
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | ## :bulb: Is your feature request related to a problem? Please describe.
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | ## :white_check_mark: Describe the solution you'd like
13 | A clear and concise description of what you want to happen.
14 |
15 | ## :part_alternation_mark: Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | ## :construction: Additional context
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## :rocket: Summary
2 | Describe things you did in this Pull Request
3 |
4 |
5 | ## :recycle: Changes
6 | Describe your changes more detailed (Bullet points are preferred)
7 |
8 |
9 | ## :framed_picture: Screenshots:
10 | Provide screenshots to make it visible to reviewer if possible (Optional)
11 | Ex:
12 |
13 | | Before | After |
14 | | :---: | :---: |
15 | | Screenshot 1 | Screenshot 2 |
16 |
--------------------------------------------------------------------------------
/.github/scripts/gradlew_recursive.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright (C) 2020 The Android Open Source Project
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -xe
18 |
19 | # Default Gradle settings are not optimal for Android builds, override them
20 | # here to make the most out of the GitHub Actions build servers
21 | GRADLE_OPTS="$GRADLE_OPTS -Xms4g -Xmx4g"
22 | GRADLE_OPTS="$GRADLE_OPTS -XX:+HeapDumpOnOutOfMemoryError"
23 | GRADLE_OPTS="$GRADLE_OPTS -Dorg.gradle.daemon=false"
24 | GRADLE_OPTS="$GRADLE_OPTS -Dorg.gradle.workers.max=2"
25 | GRADLE_OPTS="$GRADLE_OPTS -Dkotlin.incremental=false"
26 | GRADLE_OPTS="$GRADLE_OPTS -Dkotlin.compiler.execution.strategy=in-process"
27 | GRADLE_OPTS="$GRADLE_OPTS -Dfile.encoding=UTF-8"
28 | export GRADLE_OPTS
29 |
30 | # Crawl all gradlew files which indicate an Android project
31 | # You may edit this if your repo has a different project structure
32 | for GRADLEW in `find . -name "gradlew"` ; do
33 | SAMPLE=$(dirname "${GRADLEW}")
34 | # Tell Gradle that this is a CI environment and disable parallel compilation
35 | bash "$GRADLEW" -p "$SAMPLE" -Pci --no-parallel --stacktrace $@
36 | done
37 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 | on:
3 | push:
4 | branches: [ master ]
5 |
6 | permissions:
7 | contents: write
8 |
9 | jobs:
10 | build:
11 | name: Build & Deploy Artifacts
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | - name: set up JDK 17
17 | uses: actions/setup-java@v3
18 | with:
19 | distribution: 'temurin'
20 | java-version: '17'
21 | - name: Set up environment variables
22 | run: |
23 | echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 -d > app/keystore.jks
24 | echo "ANDROID_KEYSTORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
25 | echo "ANDROID_KEY_ALIAS=${{ secrets.ANDROID_KEY_ALIAS }}" >> $GITHUB_ENV
26 | echo "ANDROID_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}" >> $GITHUB_ENV
27 | - name: Build AAB
28 | run: |
29 | ./gradlew :app:bundleRelease --no-daemon
30 | - name: Build APK
31 | run: |
32 | ./gradlew :app:assembleRelease --no-daemon
33 | - name: Move files
34 | run: |
35 | mv app/build/outputs/apk/release/app-release-unsigned.apk app/build/app-release-unsigned.apk
36 | mv app/build/outputs/bundle/release/app-release.aab app/build/app-release.aab
37 | - name: Upload AAB
38 | uses: actions/upload-artifact@v4
39 | with:
40 | name: AABs
41 | path: app/build/app-release.aab
42 | - name: Sign APK
43 | run: |
44 | jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 \
45 | -keystore app/keystore.jks \
46 | -storepass ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} \
47 | -keypass ${{ secrets.ANDROID_KEY_PASSWORD }} \
48 | app/build/app-release-unsigned.apk \
49 | ${{ secrets.ANDROID_KEY_ALIAS }}
50 | - name: Align APK
51 | run: |
52 | ${ANDROID_HOME}/build-tools/34.0.0/zipalign -v 4 \
53 | app/build/app-release-unsigned.apk \
54 | app/build/app-release.apk \
55 | - name: Upload APK
56 | uses: actions/upload-artifact@v4
57 | with:
58 | name: APKs
59 | path: app/build/app-release.apk
60 |
61 |
--------------------------------------------------------------------------------
/.github/workflows/test&build.yml:
--------------------------------------------------------------------------------
1 | name: Test&Build
2 | on:
3 | pull_request:
4 | branches:
5 | - master
6 | permissions:
7 | contents: write
8 |
9 | jobs:
10 | housekeeping:
11 | name: HouseKeeping
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | - name: set up JDK 17
17 | uses: actions/setup-java@v3
18 | with:
19 | distribution: 'temurin'
20 | java-version: '17'
21 |
22 | - name: ktLint
23 | run: |
24 | echo "✅ ktLint pass"
25 | security:
26 | name: Security
27 | runs-on: ubuntu-latest
28 | steps:
29 | - name: Checkout
30 | uses: actions/checkout@v4
31 | - name: Check CODE_OWNERS
32 | run: |
33 | echo "✅ CODE_OWNERS passed"
34 | # if [ ! -f ".github/CODEOWNERS" ]; then
35 | # echo "❌ CODE OWNERS file is missing"
36 | # exit 1
37 | # else
38 | # echo "✅ CODE OWNERS file exists"
39 | # fi
40 |
41 | test:
42 | name: Unit test
43 | runs-on: ubuntu-latest
44 | needs: [housekeeping, security]
45 | timeout-minutes: 60
46 | steps:
47 | - name: Checkout
48 | uses: actions/checkout@v4
49 | - name: set up JDK 17
50 | uses: actions/setup-java@v3
51 | with:
52 | distribution: 'temurin'
53 | java-version: '17'
54 | - name: Run unit test
55 | run: |
56 | ./gradlew test --no-daemon
57 | build:
58 | name: Build APKs
59 | runs-on: ubuntu-latest
60 | steps:
61 | - name: Checkout
62 | uses: actions/checkout@v4
63 | - name: set up JDK 17
64 | uses: actions/setup-java@v3
65 | with:
66 | distribution: 'temurin'
67 | java-version: '17'
68 | - name: Build APK
69 | run: |
70 | ./gradlew :app:assembleDebug --no-daemon
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | xmlns:android
15 |
16 | ^$
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | xmlns:.*
26 |
27 | ^$
28 |
29 |
30 | BY_NAME
31 |
32 |
33 |
34 |
35 |
36 |
37 | .*:id
38 |
39 | http://schemas.android.com/apk/res/android
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | .*:name
49 |
50 | http://schemas.android.com/apk/res/android
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | name
60 |
61 | ^$
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | style
71 |
72 | ^$
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | .*
82 |
83 | ^$
84 |
85 |
86 | BY_NAME
87 |
88 |
89 |
90 |
91 |
92 |
93 | .*
94 |
95 | http://schemas.android.com/apk/res/android
96 |
97 |
98 | ANDROID_ATTRIBUTE_ORDER
99 |
100 |
101 |
102 |
103 |
104 |
105 | .*
106 |
107 | .*
108 |
109 |
110 | BY_NAME
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/copyright/nphau.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetSelector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/studiobot.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | ims.android.embeddedserver
4 | Project ims.android.embeddedserver created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
19 | 1712741849042
20 |
21 | 30
22 |
23 | org.eclipse.core.resources.regexFilterMatcher
24 | node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=--init-script /var/folders/sn/hccb_ctx26jc6wndz45qcbkm0000gn/T/db3b08fc4a9ef609cb16b96b200fa13e563f396e9bb1ed0905fdab7bc3bc513b.gradle --init-script /var/folders/sn/hccb_ctx26jc6wndz45qcbkm0000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/Library/Java/JavaVirtualMachines/jdk-15.0.1.jdk/Contents/Home
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Hau NGUYEN (Leo)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Android Embedded Server
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 
13 |
14 | ## 👉 Overview
15 |
16 | A minimal way to create HTTP server in android with Kotlin. Create asynchronous client and server applications. Anything from microservices to multiplatform HTTP client apps in a simple way. Open Source, free, and fun!
17 |
18 | ```kotlin
19 | embeddedServer(Netty, PORT, watchPaths = emptyList()) {
20 | install(WebSockets)
21 | install(CallLogging)
22 | routing {
23 | get("/") {
24 | call.respondText(
25 | text = "Hello!! You are here in ${Build.MODEL}",
26 | contentType = ContentType.Text.Plain
27 | )
28 | }
29 | }
30 | }
31 | ```
32 |
33 | ## 🚀 How to use
34 |
35 | Cloning the repository into a local directory and checkout the desired branch:
36 |
37 | ```
38 | git clone git@github.com:nphausg/android.embeddedserver.git
39 | cd android.embeddedserver
40 | git checkout master
41 | ```
42 |
43 | ## 🍲 Static resource
44 |
45 | Config | Demo |
46 | --- | --- |
47 | | |
48 |
49 | ```kotlin
50 | staticResources("/static", ""){
51 | default("index.html")
52 | }
53 | ```
54 |
55 | ## 🍲 Screenshots
56 |
57 |
58 |
59 | Fruits | Detail |
60 | --- | --- |
61 | | |
62 |
63 | Device | Connect |
64 | --- | --- |
65 | | |
66 |
67 | ## ✨ Contributing
68 |
69 | Please feel free to contact me or make a pull request.
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | ## 👀 Author
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Since this is an Android app, only the latest version is supported.
6 |
7 | ## Reporting a Vulnerability
8 |
9 | Please open an issue on GitHub to report a vulnerability.
10 |
--------------------------------------------------------------------------------
/app/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | app
4 | Project app created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
25 | 1712741849052
26 |
27 | 30
28 |
29 | org.eclipse.core.resources.regexFilterMatcher
30 | node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id 'org.jetbrains.kotlin.plugin.serialization'
5 | }
6 |
7 | android {
8 |
9 | namespace 'com.nphausg.app.embeddedserver'
10 |
11 | compileSdk 34
12 |
13 | defaultConfig {
14 | applicationId "com.nphausg.app.embeddedserver"
15 | minSdk 21
16 | targetSdk 34
17 | versionCode 1
18 | versionName "1.0.0"
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_17
31 | targetCompatibility JavaVersion.VERSION_17
32 | }
33 | kotlinOptions {
34 | jvmTarget = JavaVersion.VERSION_17
35 | }
36 | packagingOptions {
37 | jniLibs {
38 | excludes += ['META-INF/*', 'META-INF/licenses/*']
39 | }
40 | resources {
41 | excludes += ['META-INF/*', 'META-INF/licenses/*', '**/attach_hotspot_windows.dll']
42 | }
43 | }
44 | buildFeatures {
45 | compose true
46 | }
47 | composeOptions {
48 | kotlinCompilerExtensionVersion = "1.5.11"
49 | }
50 | }
51 |
52 | dependencies {
53 |
54 | // Core
55 | implementation libs.androidx.core.ktx
56 | implementation libs.androidx.core.splashscreen
57 | implementation libs.androidx.lifecycle.runtime.ktx
58 | implementation libs.androidx.appcompat
59 |
60 | // Serialization
61 | implementation libs.kotlinx.serialization.json
62 |
63 | // Coroutines
64 | implementation libs.kotlinx.coroutines.core
65 |
66 | // Embedded Server
67 | implementation libs.ktor.server.core
68 | implementation libs.ktor.server.netty
69 | implementation libs.ktor.serialization.kotlinx.json
70 | implementation libs.ktor.client.content.negotiation
71 | implementation libs.ktor.server.content.negotiation
72 | implementation libs.ktor.server.cors
73 |
74 | // Unit testing libraries
75 | testImplementation libs.junit
76 | testImplementation libs.mockito.core
77 | testImplementation libs.mockito.inline
78 | testImplementation libs.kotlin.test
79 | testImplementation libs.kotlin.test.junit
80 | testImplementation libs.androidx.ui.test.manifest
81 | testImplementation libs.androidx.ui.test.junit4
82 |
83 | // UI testing libraries
84 | androidTestImplementation libs.androidx.junit
85 | androidTestImplementation libs.androidx.espresso.core
86 |
87 | // UI
88 | implementation project(':foundation-ui')
89 | }
--------------------------------------------------------------------------------
/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 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nphausg/app/embeddedserver/EmbeddedServer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 4/10/24, 7:04 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 4/10/24, 7:04 PM
5 | */
6 |
7 | package com.nphausg.app.embeddedserver
8 |
9 | import android.os.Build
10 | import com.nphausg.app.embeddedserver.data.Database
11 | import com.nphausg.app.embeddedserver.data.models.Cart
12 | import com.nphausg.app.embeddedserver.utils.FileUtils
13 | import com.nphausg.app.embeddedserver.utils.NetworkUtils
14 | import io.ktor.http.ContentDisposition
15 | import io.ktor.http.ContentType
16 | import io.ktor.http.HttpHeaders
17 | import io.ktor.http.HttpStatusCode
18 | import io.ktor.http.HttpStatusCode.Companion.PartialContent
19 | import io.ktor.serialization.kotlinx.json.json
20 | import io.ktor.server.application.ApplicationCall
21 | import io.ktor.server.application.call
22 | import io.ktor.server.application.install
23 | import io.ktor.server.engine.embeddedServer
24 | import io.ktor.server.http.content.staticResources
25 | import io.ktor.server.netty.Netty
26 | import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
27 | import io.ktor.server.plugins.cors.routing.CORS
28 | import io.ktor.server.response.header
29 | import io.ktor.server.response.respond
30 | import io.ktor.server.response.respondFile
31 | import io.ktor.server.response.respondText
32 | import io.ktor.server.routing.get
33 | import io.ktor.server.routing.routing
34 | import kotlinx.coroutines.CoroutineScope
35 | import kotlinx.coroutines.Dispatchers
36 | import kotlinx.coroutines.launch
37 | import kotlinx.serialization.encodeToString
38 | import kotlinx.serialization.json.Json
39 | import java.io.File
40 |
41 | object EmbeddedServer {
42 |
43 | private const val PORT = 6868
44 | private val ioScope = CoroutineScope(Dispatchers.IO)
45 | private const val FILE_NAME = "file.jpg"
46 | private const val MP3_FILE_NAME = "bye_bye_bye_nsync.mp3"
47 |
48 | private val server by lazy {
49 | embeddedServer(Netty, PORT) {
50 | // configures Cross-Origin Resource Sharing. CORS is needed to make calls from arbitrary
51 | // JavaScript clients, and helps us prevent issues down the line.
52 | install(CORS) {
53 | anyHost()
54 | }
55 | install(ContentNegotiation) {
56 | json(Json {
57 | prettyPrint = true
58 | isLenient = true
59 | })
60 | }
61 | routing {
62 | // staticResources
63 | staticResources("/static", "") {
64 | default("index.html")
65 | }
66 | get("/") {
67 | okText(call, "Hello!! You are here in ${Build.MODEL}")
68 | }
69 | get("/fruits") {
70 | okText(call, FileUtils.readText("data.json").also {
71 | Database.FRUITS.addAll(FileUtils.decode(it).items)
72 | })
73 | }
74 | get("/fruits/{id}") {
75 | val id = call.parameters["id"]
76 | val fruit = Database.FRUITS.find { it.id == id }
77 | if (fruit != null) {
78 | okText(call, Json.encodeToString(fruit))
79 | } else {
80 | call.respond(HttpStatusCode.NotFound)
81 | }
82 | }
83 | get("/download") {
84 | val file = File("files/$FILE_NAME")
85 | call.response.header(
86 | HttpHeaders.ContentDisposition,
87 | ContentDisposition.Attachment.withParameter(
88 | key = ContentDisposition.Parameters.FileName,
89 | value = FILE_NAME
90 | ).toString()
91 | )
92 | call.response.status(HttpStatusCode.OK)
93 | call.respondFile(file)
94 | }
95 | }
96 | }
97 | }
98 |
99 | fun start() {
100 | ioScope.launch {
101 | try {
102 | server.start(wait = true)
103 | } catch (e: Exception) {
104 | e.printStackTrace()
105 | }
106 | }
107 | }
108 |
109 | fun stop() {
110 | try {
111 | server.stop(1_000, 2_000)
112 | } catch (e: Exception) {
113 | e.printStackTrace()
114 | }
115 | }
116 |
117 | val host: String
118 | get() = String.format("%s:%d", NetworkUtils.getLocalIpAddress(), PORT)
119 |
120 | private suspend fun okText(call: ApplicationCall, text: String) {
121 | call.respondText(
122 | text = text,
123 | status = HttpStatusCode.OK,
124 | contentType = ContentType.Application.Json
125 | )
126 | }
127 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nphausg/app/embeddedserver/data/BaseResponse.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 04/02/2022, 23:02
3 | * Copyright (c) 2022 . All rights reserved.
4 | * Last modified 04/02/2022, 23:02
5 | */
6 |
7 | package com.nphausg.app.embeddedserver.data
8 |
9 | import kotlinx.serialization.Serializable
10 |
11 | @Serializable
12 | data class BaseResponse(val data: T? = null, val error: String? = null)
--------------------------------------------------------------------------------
/app/src/main/java/com/nphausg/app/embeddedserver/data/Database.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 11/19/22, 4:16 PM
3 | * Copyright (c) 2022 . All rights reserved.
4 | * Last modified 11/19/22, 3:58 PM
5 | */
6 |
7 | package com.nphausg.app.embeddedserver.data
8 |
9 | import com.nphausg.app.embeddedserver.data.models.Fruit
10 | import java.util.UUID
11 |
12 | object Database {
13 |
14 | val FRUITS = mutableListOf()
15 |
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nphausg/app/embeddedserver/data/models/Cart.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 11/19/22, 4:16 PM
3 | * Copyright (c) 2022 . All rights reserved.
4 | * Last modified 11/19/22, 3:58 PM
5 | */
6 |
7 | package com.nphausg.app.embeddedserver.data.models
8 |
9 | import com.nphausg.app.embeddedserver.data.Database
10 | import kotlinx.serialization.Serializable
11 | import java.util.UUID
12 |
13 | @Serializable
14 | data class Cart(val id: String, val items: List) {
15 |
16 | companion object {
17 | fun sample(): Cart {
18 | return Cart(
19 | id = UUID.randomUUID().toString(),
20 | items = Database.FRUITS
21 | )
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nphausg/app/embeddedserver/data/models/Fruit.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 04/02/2022, 22:50
3 | * Copyright (c) 2022 . All rights reserved.
4 | * Last modified 04/02/2022, 22:50
5 | */
6 |
7 | package com.nphausg.app.embeddedserver.data.models
8 |
9 | import kotlinx.serialization.Serializable
10 |
11 | @Serializable
12 | data class Fruit(val id: String = "", val name: String = "")
--------------------------------------------------------------------------------
/app/src/main/java/com/nphausg/app/embeddedserver/ui/AnimatedLogo.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 2/13/25, 9:45 AM
3 | * Copyright (c) 2025 . All rights reserved.
4 | * Last modified 2/13/25, 9:45 AM
5 | */
6 |
7 | package com.nphausg.app.embeddedserver.ui
8 |
9 | import androidx.compose.animation.core.Animatable
10 | import androidx.compose.animation.core.tween
11 | import androidx.compose.foundation.Image
12 | import androidx.compose.foundation.gestures.detectDragGestures
13 | import androidx.compose.foundation.layout.offset
14 | import androidx.compose.foundation.layout.size
15 | import androidx.compose.foundation.shape.CircleShape
16 | import androidx.compose.material3.Card
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.remember
19 | import androidx.compose.runtime.rememberCoroutineScope
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.input.pointer.pointerInput
22 | import androidx.compose.ui.layout.ContentScale
23 | import androidx.compose.ui.res.painterResource
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import androidx.compose.ui.unit.IntOffset
26 | import androidx.compose.ui.unit.dp
27 | import com.masewsg.app.ui.components.ThemePreviews
28 | import com.nphausg.app.embeddedserver.R
29 | import kotlinx.coroutines.launch
30 |
31 | @Composable
32 | internal fun AnimatedLogo() {
33 |
34 | val coroutineScope = rememberCoroutineScope()
35 | val offsetX = remember { Animatable(0f) }
36 | val offsetY = remember { Animatable(0f) }
37 |
38 | Card(
39 | modifier = Modifier
40 | .size(128.dp)
41 | .offset {
42 | IntOffset(
43 | offsetX.value.toInt(),
44 | offsetY.value.toInt()
45 | )
46 | }
47 | .pointerInput(Unit) {
48 | detectDragGestures(
49 | onDragEnd = {
50 | coroutineScope.launch {
51 | offsetY.animateTo(
52 | targetValue = 0f,
53 | animationSpec = tween(
54 | durationMillis = 1000,
55 | delayMillis = 0
56 | )
57 | )
58 | }
59 | coroutineScope.launch {
60 | offsetX.animateTo(
61 | targetValue = 0f,
62 | animationSpec = tween(
63 | durationMillis = 1000,
64 | delayMillis = 0
65 | )
66 | )
67 | }
68 | },
69 | onDrag = { change, dragAmount ->
70 | change.consume()
71 | coroutineScope.launch {
72 | offsetY.snapTo(offsetY.value + dragAmount.y)
73 | }
74 | coroutineScope.launch {
75 | offsetX.snapTo(offsetX.value + dragAmount.x)
76 | }
77 | }
78 | )
79 | },
80 | shape = CircleShape
81 | ) {
82 | Image(
83 | painterResource(R.drawable.logo),
84 | contentDescription = "",
85 | contentScale = ContentScale.Inside
86 | )
87 | }
88 | }
89 |
90 | @Composable
91 | @ThemePreviews
92 | private fun AnimatedLogoPreview() {
93 | AnimatedLogo()
94 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nphausg/app/embeddedserver/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 11/19/22, 4:16 PM
3 | * Copyright (c) 2022 . All rights reserved.
4 | * Last modified 11/19/22, 3:58 PM
5 | */
6 |
7 | package com.nphausg.app.embeddedserver.ui
8 |
9 | import android.os.Build
10 | import android.os.Bundle
11 | import androidx.activity.compose.setContent
12 | import androidx.appcompat.app.AppCompatActivity
13 | import androidx.compose.animation.core.Animatable
14 | import androidx.compose.animation.core.LinearEasing
15 | import androidx.compose.animation.core.RepeatMode
16 | import androidx.compose.animation.core.animateFloat
17 | import androidx.compose.animation.core.infiniteRepeatable
18 | import androidx.compose.animation.core.rememberInfiniteTransition
19 | import androidx.compose.animation.core.tween
20 | import androidx.compose.foundation.Image
21 | import androidx.compose.foundation.background
22 | import androidx.compose.foundation.gestures.detectDragGestures
23 | import androidx.compose.foundation.layout.Arrangement
24 | import androidx.compose.foundation.layout.Column
25 | import androidx.compose.foundation.layout.Row
26 | import androidx.compose.foundation.layout.Spacer
27 | import androidx.compose.foundation.layout.fillMaxHeight
28 | import androidx.compose.foundation.layout.fillMaxWidth
29 | import androidx.compose.foundation.layout.height
30 | import androidx.compose.foundation.layout.offset
31 | import androidx.compose.foundation.layout.padding
32 | import androidx.compose.foundation.layout.size
33 | import androidx.compose.foundation.layout.width
34 | import androidx.compose.foundation.shape.CircleShape
35 | import androidx.compose.material3.Card
36 | import androidx.compose.material3.Icon
37 | import androidx.compose.material3.LinearProgressIndicator
38 | import androidx.compose.material3.MaterialTheme
39 | import androidx.compose.material3.Text
40 | import androidx.compose.runtime.Composable
41 | import androidx.compose.runtime.LaunchedEffect
42 | import androidx.compose.runtime.getValue
43 | import androidx.compose.runtime.mutableIntStateOf
44 | import androidx.compose.runtime.mutableStateOf
45 | import androidx.compose.runtime.remember
46 | import androidx.compose.runtime.rememberCoroutineScope
47 | import androidx.compose.runtime.setValue
48 | import androidx.compose.ui.Alignment
49 | import androidx.compose.ui.Modifier
50 | import androidx.compose.ui.graphics.Color
51 | import androidx.compose.ui.graphics.graphicsLayer
52 | import androidx.compose.ui.input.pointer.pointerInput
53 | import androidx.compose.ui.layout.ContentScale
54 | import androidx.compose.ui.res.painterResource
55 | import androidx.compose.ui.text.style.TextAlign
56 | import androidx.compose.ui.unit.Dp
57 | import androidx.compose.ui.unit.IntOffset
58 | import androidx.compose.ui.unit.dp
59 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
60 | import com.masewsg.app.ui.ComposeApp
61 | import com.masewsg.app.ui.components.ThemePreviews
62 | import com.masewsg.app.ui.components.button.ComposeButton
63 | import com.masewsg.app.ui.components.button.ComposeOutlinedButton
64 | import com.masewsg.app.ui.components.icon.ComposeIcons
65 | import com.masewsg.app.ui.components.theme.ComposeTheme
66 | import com.nphausg.app.embeddedserver.EmbeddedServer
67 | import com.nphausg.app.embeddedserver.R
68 | import kotlinx.coroutines.delay
69 | import kotlinx.coroutines.launch
70 | import kotlin.time.Duration.Companion.seconds
71 |
72 | private val getRunningServerInfo = { ticks: Int ->
73 | "The server is running on: ${Build.MODEL} at ${EmbeddedServer.host} -> (${ticks}s ....)"
74 | }
75 |
76 | class MainActivity : AppCompatActivity() {
77 |
78 | override fun onCreate(savedInstanceState: Bundle?) {
79 | val splashScreen = installSplashScreen()
80 | super.onCreate(savedInstanceState)
81 | // Keep the splash screen on-screen until the UI state is loaded. This condition is
82 | // evaluated each time the app needs to be redrawn so it should be fast to avoid blocking
83 | // the UI.
84 | splashScreen.setKeepOnScreenCondition {
85 | false
86 | }
87 | // Turn off the decor fitting system windows, which allows us to handle insets,
88 | // including IME animations, and go edge-to-edge
89 | // This also sets up the initial system bar style based on the platform theme
90 | // enableEdgeToEdge()
91 | setContent {
92 | ComposeTheme {
93 | ComposeApp {
94 | MainScreen()
95 | }
96 | }
97 | }
98 | }
99 | }
100 |
101 | @Composable
102 | private fun MainScreen(modifier: Modifier = Modifier) {
103 |
104 | var ticks by remember { mutableIntStateOf(0) }
105 |
106 | LaunchedEffect(Unit) {
107 | while (true) {
108 | delay(1.seconds)
109 | ticks++
110 | }
111 | }
112 |
113 | var hasStarted by remember { mutableStateOf(false) }
114 |
115 | val value by rememberInfiniteTransition(label = "")
116 | .animateFloat(
117 | initialValue = 0.8f,
118 | targetValue = 1f,
119 | animationSpec = infiniteRepeatable(
120 | animation = tween(
121 | durationMillis = 1000,
122 | easing = LinearEasing
123 | ),
124 | repeatMode = RepeatMode.Reverse
125 | ), label = ""
126 | )
127 |
128 | Column(
129 | modifier = Modifier
130 | .fillMaxWidth()
131 | .fillMaxHeight()
132 | .background(Color.White)
133 | .then(modifier),
134 | horizontalAlignment = Alignment.CenterHorizontally,
135 | verticalArrangement = Arrangement.spacedBy(
136 | space = 20.dp,
137 | alignment = Alignment.CenterVertically
138 | )
139 | ) {
140 |
141 | val reusedModifier = Modifier.weight(1f)
142 |
143 | Spacer(modifier = reusedModifier)
144 | AnimatedLogo()
145 | Spacer(modifier = Modifier.weight(0.1f))
146 | Column(
147 | verticalArrangement = Arrangement.spacedBy(
148 | space = 20.dp,
149 | alignment = Alignment.CenterVertically
150 | )
151 | ) {
152 | Row {
153 | Icon(imageVector = ComposeIcons.PlayArrow, contentDescription = null)
154 | Text(
155 | color = Color.Black,
156 | textAlign = TextAlign.Start,
157 | text = String.format("GET: %s", EmbeddedServer.host),
158 | style = MaterialTheme.typography.titleMedium,
159 | )
160 | }
161 | Row {
162 | Icon(imageVector = ComposeIcons.PlayArrow, contentDescription = null)
163 | Text(
164 | color = Color.Black,
165 | textAlign = TextAlign.Start,
166 | text = String.format("GET: %s/fruits", EmbeddedServer.host),
167 | style = MaterialTheme.typography.titleMedium,
168 | )
169 | }
170 |
171 | Row(modifier = Modifier) {
172 | Icon(imageVector = ComposeIcons.PlayArrow, contentDescription = null)
173 | Text(
174 | color = Color.Black,
175 | textAlign = TextAlign.Start,
176 | text = String.format("GET: %s/fruits/{id}", EmbeddedServer.host),
177 | style = MaterialTheme.typography.titleMedium,
178 | )
179 | }
180 |
181 | Row {
182 | Icon(imageVector = ComposeIcons.PlayArrow, contentDescription = null)
183 | Text(
184 | color = Color.Black,
185 | textAlign = TextAlign.Start,
186 | text = String.format("STATIC: %s/static", EmbeddedServer.host),
187 | style = MaterialTheme.typography.titleMedium,
188 | )
189 | }
190 |
191 | }
192 |
193 | Row(
194 | modifier = Modifier
195 | .fillMaxWidth()
196 | .padding(Dp(36f))
197 | ) {
198 | ComposeButton(
199 | enabled = !hasStarted,
200 | modifier = reusedModifier,
201 | onClick = {
202 | hasStarted = true
203 | EmbeddedServer.start()
204 | },
205 | text = { Text("Start") }
206 | )
207 | Spacer(modifier = Modifier.weight(0.1f))
208 | ComposeOutlinedButton(
209 | enabled = hasStarted,
210 | modifier = reusedModifier,
211 | onClick = {
212 | ticks = 0
213 | hasStarted = false
214 | EmbeddedServer.stop()
215 | },
216 | text = { Text("Stop") }
217 | )
218 | }
219 |
220 | Column(modifier = Modifier.height(8.dp)) {
221 | if (hasStarted) {
222 | LinearProgressIndicator(
223 | modifier = Modifier.width(64.dp),
224 | color = MaterialTheme.colorScheme.secondary,
225 | trackColor = MaterialTheme.colorScheme.surfaceVariant,
226 | )
227 | }
228 | }
229 | Text(
230 | modifier = Modifier.graphicsLayer {
231 | if (hasStarted) {
232 | scaleX = value
233 | scaleY = value
234 | }
235 | },
236 | color = Color.Black,
237 | textAlign = TextAlign.Center,
238 | text = if (hasStarted) {
239 | getRunningServerInfo(ticks)
240 | } else {
241 | "Please click 'Start' to start the embedded server"
242 | },
243 | style = MaterialTheme.typography.labelMedium,
244 | )
245 | Spacer(modifier = reusedModifier)
246 | }
247 | }
248 |
249 | @Composable
250 | @ThemePreviews
251 | private fun MainScreenPreview() {
252 | ComposeTheme {
253 | ComposeApp {
254 | MainScreen()
255 | }
256 | }
257 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nphausg/app/embeddedserver/utils/FileUtils.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 4/13/24, 11:46 AM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 4/13/24, 11:46 AM
5 | */
6 |
7 | package com.nphausg.app.embeddedserver.utils
8 |
9 | import kotlinx.serialization.json.Json
10 |
11 | object FileUtils {
12 | fun readText(path: String): String =
13 | this::class.java.classLoader?.getResource(path)?.readText().orEmpty()
14 |
15 | inline fun decode(json: String): T =
16 | Json.decodeFromString(json)
17 |
18 | inline fun readJson(path: String): T =
19 | decode(readText(path))
20 | }
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nphausg/app/embeddedserver/utils/NetworkUtils.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 11/19/22, 4:16 PM
3 | * Copyright (c) 2022 . All rights reserved.
4 | * Last modified 11/19/22, 3:58 PM
5 | */
6 |
7 | package com.nphausg.app.embeddedserver.utils
8 |
9 | import java.net.InetAddress
10 | import java.net.NetworkInterface
11 |
12 | object NetworkUtils {
13 |
14 | fun getLocalIpAddress(): String? = getInetAddresses()
15 | .filter { it.isLocalAddress() }
16 | .map { it.hostAddress }
17 | .firstOrNull()
18 |
19 | private fun getInetAddresses() = NetworkInterface.getNetworkInterfaces()
20 | .iterator()
21 | .asSequence()
22 | .flatMap { networkInterface ->
23 | networkInterface.inetAddresses
24 | .asSequence()
25 | .filter { !it.isLoopbackAddress }
26 | }.toList()
27 | }
28 |
29 | fun InetAddress.isLocalAddress(): Boolean {
30 | try {
31 | return isSiteLocalAddress
32 | && !hostAddress!!.contains(":")
33 | && hostAddress != "127.0.0.1"
34 | } catch (e: Exception) {
35 | e.printStackTrace()
36 | }
37 | return false
38 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/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/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/drawable/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Server
3 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
12 |
15 |
--------------------------------------------------------------------------------
/app/src/main/resources/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "8fc012d9-ad1d-4519-a339-578cf803d509",
3 | "items": [
4 | {
5 | "id": "8fc012d9-ad1d-4519-a339-578cf803d509",
6 | "name": "Cucumbers \uD83E\uDD52"
7 | },
8 | {
9 | "id": "594b8138-ffc5-41cc-b399-a6a699506a73",
10 | "name": "Tomatoes \uD83C\uDF45"
11 | },
12 | {
13 | "id": "743f4a4d-6a36-4db3-a6d9-5f189943fae5",
14 | "name": "Tomatoes \uD83C\uDF45"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/app/src/main/resources/docs/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/resources/docs/demo.gif
--------------------------------------------------------------------------------
/app/src/main/resources/docs/detail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/resources/docs/detail.jpg
--------------------------------------------------------------------------------
/app/src/main/resources/docs/edge_get.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/resources/docs/edge_get.gif
--------------------------------------------------------------------------------
/app/src/main/resources/docs/fruits.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/resources/docs/fruits.jpg
--------------------------------------------------------------------------------
/app/src/main/resources/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/resources/favicon.ico
--------------------------------------------------------------------------------
/app/src/main/resources/files/file.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/app/src/main/resources/files/file.jpg
--------------------------------------------------------------------------------
/app/src/main/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 | Android Embedded Server
14 |
32 |
33 |
34 |
35 |
36 | Android Embedded Server (@nphausg)
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
47 |
48 |
49 |
50 |
52 |
53 |
54 |
55 |
56 |
👉 Overview
57 |
A minimal way to create HTTP server in android with Kotlin. Create asynchronous client and server
58 | applications.
59 | Anything from microservices to multiplatform HTTP client apps in a simple way. Open Source, free, and fun!
60 |
61 |
embeddedServer (Netty, PORT, watchPaths = emptyList() ) {
62 | install(WebSockets)
63 | install(CallLogging)
64 | routing {
65 | get("/" ) {
66 | call.respondText(
67 | text = "Hello!! You are here in ${Build.MODEL}" ,
68 | contentType = ContentType.Text .Plain
69 | )
70 | }
71 | }
72 | }
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
🚀 How to use
83 |
Cloning the repository into a local directory and checkout the desired branch:
84 |
git clone git@github.com :nphausg/android.embeddedserver .git
85 | cd android.embeddedserver
86 | git checkout master
87 |
88 |
✨ Contributing
89 |
Please feel free to contact me or make a pull request.
90 |
👀 Author
91 |
92 |
93 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
104 |
107 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath libs.tools.gradle
9 | classpath libs.tools.kotlin
10 | }
11 |
12 | }
13 |
14 | plugins {
15 | id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
16 | id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.23' apply false
17 | }
18 |
19 | tasks.register('clean', Delete) {
20 | delete rootProject.buildDir
21 | }
--------------------------------------------------------------------------------
/docs/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/docs/demo.gif
--------------------------------------------------------------------------------
/docs/detail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/docs/detail.jpg
--------------------------------------------------------------------------------
/docs/edge_get.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/docs/edge_get.gif
--------------------------------------------------------------------------------
/docs/fruits.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/docs/fruits.jpg
--------------------------------------------------------------------------------
/docs/static_config.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/docs/static_config.jpg
--------------------------------------------------------------------------------
/docs/static_demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/docs/static_demo.jpg
--------------------------------------------------------------------------------
/foundation/ui/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/foundation/ui/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'com.masewsg.app.ui'
8 | compileSdk 34
9 |
10 | defaultConfig {
11 | minSdk 21
12 | targetSdk 34
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_17
25 | targetCompatibility JavaVersion.VERSION_17
26 | }
27 | kotlinOptions {
28 | jvmTarget = JavaVersion.VERSION_17
29 | }
30 | buildFeatures {
31 | compose true
32 | }
33 | composeOptions {
34 | kotlinCompilerExtensionVersion = "1.5.11"
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation libs.androidx.core.ktx
40 | implementation libs.androidx.appcompat
41 |
42 | // Compose
43 | implementation platform(libs.androidx.compose.bom)
44 | api libs.androidx.material3
45 | // Optional - Integration with activities
46 | api libs.androidx.activity.compose
47 | // or only import the main APIs for the underlying toolkit systems,
48 | // such as input and measurement/layout
49 | api libs.androidx.ui
50 | debugApi libs.androidx.ui.tooling
51 | // Android Studio Preview support
52 | api libs.androidx.ui.tooling.preview
53 | api libs.androidx.ui.graphics
54 | api libs.material3
55 | api libs.androidx.foundation
56 | api libs.navigation.compose
57 | }
--------------------------------------------------------------------------------
/foundation/ui/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/foundation/ui/consumer-rules.pro
--------------------------------------------------------------------------------
/foundation/ui/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
--------------------------------------------------------------------------------
/foundation/ui/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/java/com/masewsg/app/ui/ComposeApp.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 5/4/24, 10:36 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 5/4/24, 10:35 PM
5 | */
6 |
7 | package com.masewsg.app.ui
8 |
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.WindowInsets
11 | import androidx.compose.foundation.layout.WindowInsetsSides
12 | import androidx.compose.foundation.layout.consumeWindowInsets
13 | import androidx.compose.foundation.layout.fillMaxSize
14 | import androidx.compose.foundation.layout.only
15 | import androidx.compose.foundation.layout.padding
16 | import androidx.compose.foundation.layout.safeDrawing
17 | import androidx.compose.foundation.layout.windowInsetsPadding
18 | import androidx.compose.material3.ExperimentalMaterial3Api
19 | import androidx.compose.material3.Scaffold
20 | import androidx.compose.material3.SnackbarHost
21 | import androidx.compose.material3.SnackbarHostState
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.runtime.getValue
24 | import androidx.compose.runtime.mutableStateOf
25 | import androidx.compose.runtime.remember
26 | import androidx.compose.runtime.saveable.rememberSaveable
27 | import androidx.compose.ui.ExperimentalComposeUiApi
28 | import androidx.compose.ui.Modifier
29 | import androidx.compose.ui.graphics.Color
30 | import androidx.compose.ui.semantics.semantics
31 | import androidx.compose.ui.semantics.testTagsAsResourceId
32 | import com.masewsg.app.ui.components.ComposeBackground
33 | import com.masewsg.app.ui.components.ComposeGradientBackground
34 | import com.masewsg.app.ui.components.color.GradientColors
35 | import com.masewsg.app.ui.components.color.LocalGradientColors
36 |
37 | @OptIn(ExperimentalComposeUiApi::class)
38 | @Composable
39 | fun ComposeApp(content: @Composable () -> Unit) {
40 |
41 | val shouldShowGradientBackground by rememberSaveable { mutableStateOf(false) }
42 |
43 | ComposeBackground {
44 | ComposeGradientBackground(
45 | gradientColors = if (shouldShowGradientBackground) {
46 | LocalGradientColors.current
47 | } else {
48 | GradientColors()
49 | },
50 | ) {
51 | val snackbarHostState = remember { SnackbarHostState() }
52 | Scaffold(
53 | modifier = Modifier.semantics {
54 | testTagsAsResourceId = true
55 | },
56 | containerColor = Color.Transparent,
57 | contentColor = Color.Green,
58 | contentWindowInsets = WindowInsets(0, 0, 0, 0),
59 | snackbarHost = { SnackbarHost(snackbarHostState) },
60 | bottomBar = {
61 |
62 | },
63 | ) { padding ->
64 | Row(
65 | Modifier
66 | .fillMaxSize()
67 | .padding(padding)
68 | .consumeWindowInsets(padding)
69 | .windowInsetsPadding(
70 | WindowInsets.safeDrawing.only(
71 | WindowInsetsSides.Horizontal,
72 | ),
73 | ),
74 | ) {
75 | content()
76 | }
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/java/com/masewsg/app/ui/components/Background.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 5/4/24, 10:36 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 4/10/24, 7:59 PM
5 | */
6 |
7 | package com.masewsg.app.ui.components
8 |
9 | import android.content.res.Configuration
10 | import androidx.compose.foundation.layout.Box
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.material3.LocalAbsoluteTonalElevation
14 | import androidx.compose.material3.Surface
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.runtime.CompositionLocalProvider
17 | import androidx.compose.runtime.Immutable
18 | import androidx.compose.runtime.getValue
19 | import androidx.compose.runtime.rememberUpdatedState
20 | import androidx.compose.runtime.staticCompositionLocalOf
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.draw.drawWithCache
23 | import androidx.compose.ui.geometry.Offset
24 | import androidx.compose.ui.graphics.Brush
25 | import androidx.compose.ui.graphics.Color
26 | import androidx.compose.ui.tooling.preview.Preview
27 | import androidx.compose.ui.unit.Dp
28 | import androidx.compose.ui.unit.dp
29 | import com.masewsg.app.ui.components.color.GradientColors
30 | import com.masewsg.app.ui.components.color.LocalGradientColors
31 | import com.masewsg.app.ui.components.theme.ComposeTheme
32 | import kotlin.math.tan
33 |
34 | /**
35 | * A class to model background color and tonal elevation values for Now in Android.
36 | */
37 | @Immutable
38 | data class BackgroundTheme(
39 | val color: Color = Color.Unspecified,
40 | val tonalElevation: Dp = Dp.Unspecified,
41 | )
42 |
43 | /**
44 | * A composition local for [BackgroundTheme].
45 | */
46 | val LocalBackgroundTheme = staticCompositionLocalOf { BackgroundTheme() }
47 |
48 |
49 | /**
50 | * The main background for the app.
51 | * Uses [LocalBackgroundTheme] to set the color and tonal elevation of a [Surface].
52 | *
53 | * @param modifier Modifier to be applied to the background.
54 | * @param content The background content.
55 | */
56 | @Composable
57 | fun ComposeBackground(
58 | modifier: Modifier = Modifier,
59 | content: @Composable () -> Unit,
60 | ) {
61 | val color = LocalBackgroundTheme.current.color
62 | val tonalElevation = LocalBackgroundTheme.current.tonalElevation
63 | Surface(
64 | color = if (color == Color.Unspecified) Color.Transparent else color,
65 | tonalElevation = if (tonalElevation == Dp.Unspecified) 0.dp else tonalElevation,
66 | modifier = modifier.fillMaxSize(),
67 | ) {
68 | CompositionLocalProvider(LocalAbsoluteTonalElevation provides 0.dp) {
69 | content()
70 | }
71 | }
72 | }
73 |
74 | /**
75 | * A gradient background for select screens. Uses [LocalBackgroundTheme] to set the gradient colors
76 | * of a [Box] within a [Surface].
77 | *
78 | * @param modifier Modifier to be applied to the background.
79 | * @param gradientColors The gradient colors to be rendered.
80 | * @param content The background content.
81 | */
82 | @Composable
83 | fun ComposeGradientBackground(
84 | modifier: Modifier = Modifier,
85 | gradientColors: GradientColors = LocalGradientColors.current,
86 | content: @Composable () -> Unit,
87 | ) {
88 | val currentTopColor by rememberUpdatedState(gradientColors.top)
89 | val currentBottomColor by rememberUpdatedState(gradientColors.bottom)
90 | Surface(
91 | color = if (gradientColors.container == Color.Unspecified) {
92 | Color.Transparent
93 | } else {
94 | gradientColors.container
95 | },
96 | modifier = modifier.fillMaxSize(),
97 | ) {
98 | Box(
99 | Modifier
100 | .fillMaxSize()
101 | .drawWithCache {
102 | // Compute the start and end coordinates such that the gradients are angled 11.06
103 | // degrees off the vertical axis
104 | val offset = size.height * tan(
105 | Math
106 | .toRadians(11.06)
107 | .toFloat(),
108 | )
109 |
110 | val start = Offset(size.width / 2 + offset / 2, 0f)
111 | val end = Offset(size.width / 2 - offset / 2, size.height)
112 |
113 | // Create the top gradient that fades out after the halfway point vertically
114 | val topGradient = Brush.linearGradient(
115 | 0f to if (currentTopColor == Color.Unspecified) {
116 | Color.Transparent
117 | } else {
118 | currentTopColor
119 | },
120 | 0.724f to Color.Transparent,
121 | start = start,
122 | end = end,
123 | )
124 | // Create the bottom gradient that fades in before the halfway point vertically
125 | val bottomGradient = Brush.linearGradient(
126 | 0.2552f to Color.Transparent,
127 | 1f to if (currentBottomColor == Color.Unspecified) {
128 | Color.Transparent
129 | } else {
130 | currentBottomColor
131 | },
132 | start = start,
133 | end = end,
134 | )
135 |
136 | onDrawBehind {
137 | // There is overlap here, so order is important
138 | drawRect(topGradient)
139 | drawRect(bottomGradient)
140 | }
141 | },
142 | ) {
143 | content()
144 | }
145 | }
146 | }
147 |
148 | /**
149 | * Multi Preview annotation that represents light and dark themes. Add this annotation to a
150 | * composable to render the both themes.
151 | */
152 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Light theme")
153 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark theme")
154 | annotation class ThemePreviews
155 |
156 | @ThemePreviews
157 | @Composable
158 | fun BackgroundDefault() {
159 | ComposeTheme(disableDynamicTheming = true) {
160 | ComposeBackground(Modifier.size(100.dp), content = {})
161 | }
162 | }
163 |
164 | @ThemePreviews
165 | @Composable
166 | fun BackgroundDynamic() {
167 | ComposeTheme(disableDynamicTheming = false) {
168 | ComposeBackground(Modifier.size(100.dp), content = {})
169 | }
170 | }
171 |
172 | @ThemePreviews
173 | @Composable
174 | fun BackgroundAndroid() {
175 | ComposeTheme(androidTheme = true) {
176 | ComposeBackground(Modifier.size(100.dp), content = {})
177 | }
178 | }
179 |
180 | @ThemePreviews
181 | @Composable
182 | fun GradientBackgroundDefault() {
183 | ComposeTheme(disableDynamicTheming = true) {
184 | ComposeGradientBackground(Modifier.size(100.dp), content = {})
185 | }
186 | }
187 |
188 | @ThemePreviews
189 | @Composable
190 | fun GradientBackgroundDynamic() {
191 | ComposeTheme(disableDynamicTheming = false) {
192 | ComposeGradientBackground(Modifier.size(100.dp), content = {})
193 | }
194 | }
195 |
196 | @ThemePreviews
197 | @Composable
198 | fun GradientBackgroundAndroid() {
199 | ComposeTheme(androidTheme = true) {
200 | ComposeGradientBackground(Modifier.size(100.dp), content = {})
201 | }
202 | }
203 |
204 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/java/com/masewsg/app/ui/components/button/Button.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 5/4/24, 10:36 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 4/10/24, 8:38 PM
5 | */
6 |
7 | package com.masewsg.app.ui.components.button
8 |
9 | import androidx.compose.foundation.BorderStroke
10 | import androidx.compose.foundation.layout.Box
11 | import androidx.compose.foundation.layout.PaddingValues
12 | import androidx.compose.foundation.layout.RowScope
13 | import androidx.compose.foundation.layout.padding
14 | import androidx.compose.foundation.layout.size
15 | import androidx.compose.foundation.layout.sizeIn
16 | import androidx.compose.material3.Button
17 | import androidx.compose.material3.ButtonDefaults
18 | import androidx.compose.material3.FilledIconToggleButton
19 | import androidx.compose.material3.Icon
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.OutlinedButton
22 | import androidx.compose.material3.Text
23 | import androidx.compose.material3.TextButton
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.runtime.getValue
26 | import androidx.compose.runtime.mutableStateOf
27 | import androidx.compose.runtime.remember
28 | import androidx.compose.runtime.setValue
29 | import androidx.compose.ui.Modifier
30 | import androidx.compose.ui.unit.dp
31 | import com.masewsg.app.ui.components.ComposeBackground
32 | import com.masewsg.app.ui.components.ThemePreviews
33 | import com.masewsg.app.ui.components.icon.ComposeIcons
34 | import com.masewsg.app.ui.components.theme.ComposeTheme
35 |
36 | /**
37 | * Now in Android filled button with generic content slot. Wraps Material 3 [Button].
38 | *
39 | * @param onClick Will be called when the user clicks the button.
40 | * @param modifier Modifier to be applied to the button.
41 | * @param enabled Controls the enabled state of the button. When `false`, this button will not be
42 | * clickable and will appear disabled to accessibility services.
43 | * @param contentPadding The spacing values to apply internally between the container and the
44 | * content.
45 | * @param content The button content.
46 | */
47 | @Composable
48 | fun ComposeButton(
49 | onClick: () -> Unit,
50 | modifier: Modifier = Modifier,
51 | enabled: Boolean = true,
52 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
53 | content: @Composable RowScope.() -> Unit,
54 | ) {
55 | Button(
56 | onClick = onClick,
57 | modifier = modifier,
58 | enabled = enabled,
59 | colors = ButtonDefaults.buttonColors(
60 | containerColor = MaterialTheme.colorScheme.onBackground,
61 | ),
62 | contentPadding = contentPadding,
63 | content = content,
64 | )
65 | }
66 |
67 | /**
68 | * Now in Android filled button with text and icon content slots.
69 | *
70 | * @param onClick Will be called when the user clicks the button.
71 | * @param modifier Modifier to be applied to the button.
72 | * @param enabled Controls the enabled state of the button. When `false`, this button will not be
73 | * clickable and will appear disabled to accessibility services.
74 | * @param text The button text label content.
75 | * @param leadingIcon The button leading icon content. Pass `null` here for no leading icon.
76 | */
77 | @Composable
78 | fun ComposeButton(
79 | onClick: () -> Unit,
80 | modifier: Modifier = Modifier,
81 | enabled: Boolean = true,
82 | text: @Composable () -> Unit,
83 | leadingIcon: @Composable (() -> Unit)? = null,
84 | ) {
85 | ComposeButton(
86 | onClick = onClick,
87 | modifier = modifier,
88 | enabled = enabled,
89 | contentPadding = if (leadingIcon != null) {
90 | ButtonDefaults.ButtonWithIconContentPadding
91 | } else {
92 | ButtonDefaults.ContentPadding
93 | },
94 | ) {
95 | ComposeButtonContent(
96 | text = text,
97 | leadingIcon = leadingIcon,
98 | )
99 | }
100 | }
101 |
102 | /**
103 | * Now in Android outlined button with generic content slot. Wraps Material 3 [OutlinedButton].
104 | *
105 | * @param onClick Will be called when the user clicks the button.
106 | * @param modifier Modifier to be applied to the button.
107 | * @param enabled Controls the enabled state of the button. When `false`, this button will not be
108 | * clickable and will appear disabled to accessibility services.
109 | * @param contentPadding The spacing values to apply internally between the container and the
110 | * content.
111 | * @param content The button content.
112 | */
113 | @Composable
114 | fun ComposeOutlinedButton(
115 | onClick: () -> Unit,
116 | modifier: Modifier = Modifier,
117 | enabled: Boolean = true,
118 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
119 | content: @Composable RowScope.() -> Unit,
120 | ) {
121 | OutlinedButton(
122 | onClick = onClick,
123 | modifier = modifier,
124 | enabled = enabled,
125 | colors = ButtonDefaults.outlinedButtonColors(
126 | contentColor = MaterialTheme.colorScheme.onBackground,
127 | ),
128 | border = BorderStroke(
129 | width = ComposeButtonDefaults.OutlinedButtonBorderWidth,
130 | color = if (enabled) {
131 | MaterialTheme.colorScheme.outline
132 | } else {
133 | MaterialTheme.colorScheme.onSurface.copy(
134 | alpha = ComposeButtonDefaults.DISABLED_OUTLINED_BUTTON_BORDER_ALPHA,
135 | )
136 | },
137 | ),
138 | contentPadding = contentPadding,
139 | content = content,
140 | )
141 | }
142 |
143 | /**
144 | * Now in Android outlined button with text and icon content slots.
145 | *
146 | * @param onClick Will be called when the user clicks the button.
147 | * @param modifier Modifier to be applied to the button.
148 | * @param enabled Controls the enabled state of the button. When `false`, this button will not be
149 | * clickable and will appear disabled to accessibility services.
150 | * @param text The button text label content.
151 | * @param leadingIcon The button leading icon content. Pass `null` here for no leading icon.
152 | */
153 | @Composable
154 | fun ComposeOutlinedButton(
155 | onClick: () -> Unit,
156 | modifier: Modifier = Modifier,
157 | enabled: Boolean = true,
158 | text: @Composable () -> Unit,
159 | leadingIcon: @Composable (() -> Unit)? = null,
160 | ) {
161 | ComposeOutlinedButton(
162 | onClick = onClick,
163 | modifier = modifier,
164 | enabled = enabled,
165 | contentPadding = if (leadingIcon != null) {
166 | ButtonDefaults.ButtonWithIconContentPadding
167 | } else {
168 | ButtonDefaults.ContentPadding
169 | },
170 | ) {
171 | ComposeButtonContent(
172 | text = text,
173 | leadingIcon = leadingIcon,
174 | )
175 | }
176 | }
177 |
178 | /**
179 | * Now in Android text button with generic content slot. Wraps Material 3 [TextButton].
180 | *
181 | * @param onClick Will be called when the user clicks the button.
182 | * @param modifier Modifier to be applied to the button.
183 | * @param enabled Controls the enabled state of the button. When `false`, this button will not be
184 | * clickable and will appear disabled to accessibility services.
185 | * @param content The button content.
186 | */
187 | @Composable
188 | fun ComposeTextButton(
189 | onClick: () -> Unit,
190 | modifier: Modifier = Modifier,
191 | enabled: Boolean = true,
192 | content: @Composable RowScope.() -> Unit,
193 | ) {
194 | TextButton(
195 | onClick = onClick,
196 | modifier = modifier,
197 | enabled = enabled,
198 | colors = ButtonDefaults.textButtonColors(
199 | contentColor = MaterialTheme.colorScheme.onBackground,
200 | ),
201 | content = content,
202 | )
203 | }
204 |
205 | /**
206 | * Now in Android text button with text and icon content slots.
207 | *
208 | * @param onClick Will be called when the user clicks the button.
209 | * @param modifier Modifier to be applied to the button.
210 | * @param enabled Controls the enabled state of the button. When `false`, this button will not be
211 | * clickable and will appear disabled to accessibility services.
212 | * @param text The button text label content.
213 | * @param leadingIcon The button leading icon content. Pass `null` here for no leading icon.
214 | */
215 | @Composable
216 | fun ComposeTextButton(
217 | onClick: () -> Unit,
218 | modifier: Modifier = Modifier,
219 | enabled: Boolean = true,
220 | text: @Composable () -> Unit,
221 | leadingIcon: @Composable (() -> Unit)? = null,
222 | ) {
223 | ComposeTextButton(
224 | onClick = onClick,
225 | modifier = modifier,
226 | enabled = enabled,
227 | ) {
228 | ComposeButtonContent(
229 | text = text,
230 | leadingIcon = leadingIcon,
231 | )
232 | }
233 | }
234 |
235 | /**
236 | * Internal Now in Android button content layout for arranging the text label and leading icon.
237 | *
238 | * @param text The button text label content.
239 | * @param leadingIcon The button leading icon content. Default is `null` for no leading icon.Ï
240 | */
241 | @Composable
242 | private fun ComposeButtonContent(
243 | text: @Composable () -> Unit,
244 | leadingIcon: @Composable (() -> Unit)? = null,
245 | ) {
246 | if (leadingIcon != null) {
247 | Box(Modifier.sizeIn(maxHeight = ButtonDefaults.IconSize)) {
248 | leadingIcon()
249 | }
250 | }
251 | Box(
252 | Modifier
253 | .padding(
254 | start = if (leadingIcon != null) {
255 | ButtonDefaults.IconSpacing
256 | } else {
257 | 0.dp
258 | },
259 | ),
260 | ) {
261 | text()
262 | }
263 | }
264 |
265 | @Composable
266 | fun ComposeToggleButton(checked: @Composable () -> Unit, unchecked: @Composable () -> Unit) {
267 | var hasChecked by remember { mutableStateOf(false) }
268 | FilledIconToggleButton(checked = hasChecked, onCheckedChange = { hasChecked = it }) {
269 | if (hasChecked) {
270 | checked()
271 | } else {
272 | unchecked()
273 | }
274 | }
275 | }
276 |
277 | @ThemePreviews
278 | @Composable
279 | fun ComposeButtonPreview() {
280 | ComposeTheme {
281 | ComposeBackground(modifier = Modifier.size(150.dp, 50.dp)) {
282 | ComposeButton(onClick = {}, text = { Text("Test button") })
283 | }
284 | }
285 | }
286 |
287 | @ThemePreviews
288 | @Composable
289 | fun ComposeOutlinedButtonPreview() {
290 | ComposeTheme {
291 | ComposeBackground(modifier = Modifier.size(150.dp, 50.dp)) {
292 | ComposeOutlinedButton(onClick = {}, text = { Text("Test button") })
293 | }
294 | }
295 | }
296 |
297 | @ThemePreviews
298 | @Composable
299 | fun ComposeButtonLeadingIconPreview() {
300 | ComposeTheme {
301 | ComposeBackground(modifier = Modifier.size(150.dp, 50.dp)) {
302 | ComposeButton(
303 | onClick = {},
304 | text = { Text("Test button") },
305 | leadingIcon = { Icon(imageVector = ComposeIcons.Info, contentDescription = null) },
306 | )
307 | }
308 | }
309 | }
310 |
311 | /**
312 | * Now in Android button default values.
313 | */
314 | object ComposeButtonDefaults {
315 | // OutlinedButton border color doesn't respect disabled state by default
316 | const val DISABLED_OUTLINED_BUTTON_BORDER_ALPHA = 0.12f
317 |
318 | // OutlinedButton default border width isn't exposed via ButtonDefaults
319 | val OutlinedButtonBorderWidth = 1.dp
320 | }
321 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/java/com/masewsg/app/ui/components/color/Color.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 4/10/24, 8:29 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 4/10/24, 8:29 PM
5 | */
6 |
7 | package com.masewsg.app.ui.components.color
8 |
9 | import androidx.compose.ui.graphics.Color
10 |
11 | internal val Blue10 = Color(0xFF001F28)
12 | internal val Blue20 = Color(0xFF003544)
13 | internal val Blue30 = Color(0xFF004D61)
14 | internal val Blue40 = Color(0xFF006780)
15 | internal val Blue80 = Color(0xFF5DD5FC)
16 | internal val Blue90 = Color(0xFFB8EAFF)
17 | internal val DarkGreen10 = Color(0xFF0D1F12)
18 | internal val DarkGreen20 = Color(0xFF223526)
19 | internal val DarkGreen30 = Color(0xFF394B3C)
20 | internal val DarkGreen40 = Color(0xFF4F6352)
21 | internal val DarkGreen80 = Color(0xFFB7CCB8)
22 | internal val DarkGreen90 = Color(0xFFD3E8D3)
23 | internal val DarkGreenGray10 = Color(0xFF1A1C1A)
24 | internal val DarkGreenGray20 = Color(0xFF2F312E)
25 | internal val DarkGreenGray90 = Color(0xFFE2E3DE)
26 | internal val DarkGreenGray95 = Color(0xFFF0F1EC)
27 | internal val DarkGreenGray99 = Color(0xFFFBFDF7)
28 | internal val DarkPurpleGray10 = Color(0xFF201A1B)
29 | internal val DarkPurpleGray20 = Color(0xFF362F30)
30 | internal val DarkPurpleGray90 = Color(0xFFECDFE0)
31 | internal val DarkPurpleGray95 = Color(0xFFFAEEEF)
32 | internal val DarkPurpleGray99 = Color(0xFFFCFCFC)
33 | internal val Green10 = Color(0xFF00210B)
34 | internal val Green20 = Color(0xFF003919)
35 | internal val Green30 = Color(0xFF005227)
36 | internal val Green40 = Color(0xFF006D36)
37 | internal val Green80 = Color(0xFF0EE37C)
38 | internal val Green90 = Color(0xFF5AFF9D)
39 | internal val GreenGray30 = Color(0xFF414941)
40 | internal val GreenGray50 = Color(0xFF727971)
41 | internal val GreenGray60 = Color(0xFF8B938A)
42 | internal val GreenGray80 = Color(0xFFC1C9BF)
43 | internal val GreenGray90 = Color(0xFFDDE5DB)
44 | internal val Orange10 = Color(0xFF380D00)
45 | internal val Orange20 = Color(0xFF5B1A00)
46 | internal val Orange30 = Color(0xFF812800)
47 | internal val Orange40 = Color(0xFFA23F16)
48 | internal val Orange80 = Color(0xFFFFB59B)
49 | internal val Orange90 = Color(0xFFFFDBCF)
50 | internal val Purple10 = Color(0xFF36003C)
51 | internal val Purple20 = Color(0xFF560A5D)
52 | internal val Purple30 = Color(0xFF702776)
53 | internal val Purple40 = Color(0xFF8B418F)
54 | internal val Purple80 = Color(0xFFFFA9FE)
55 | internal val Purple90 = Color(0xFFFFD6FA)
56 | internal val PurpleGray30 = Color(0xFF4D444C)
57 | internal val PurpleGray50 = Color(0xFF7F747C)
58 | internal val PurpleGray60 = Color(0xFF998D96)
59 | internal val PurpleGray80 = Color(0xFFD0C3CC)
60 | internal val PurpleGray90 = Color(0xFFEDDEE8)
61 | internal val Red10 = Color(0xFF410002)
62 | internal val Red20 = Color(0xFF690005)
63 | internal val Red30 = Color(0xFF93000A)
64 | internal val Red40 = Color(0xFFBA1A1A)
65 | internal val Red80 = Color(0xFFFFB4AB)
66 | internal val Red90 = Color(0xFFFFDAD6)
67 | internal val Teal10 = Color(0xFF001F26)
68 | internal val Teal20 = Color(0xFF02363F)
69 | internal val Teal30 = Color(0xFF214D56)
70 | internal val Teal40 = Color(0xFF3A656F)
71 | internal val Teal80 = Color(0xFFA2CED9)
72 | internal val Teal90 = Color(0xFFBEEAF6)
73 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/java/com/masewsg/app/ui/components/color/Gradient.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 5/4/24, 10:36 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 4/10/24, 7:58 PM
5 | */
6 |
7 | package com.masewsg.app.ui.components.color
8 |
9 | import androidx.compose.runtime.Immutable
10 | import androidx.compose.runtime.staticCompositionLocalOf
11 | import androidx.compose.ui.graphics.Color
12 |
13 | /**
14 | * A class to model gradient color values for Now in Android.
15 | *
16 | * @param top The top gradient color to be rendered.
17 | * @param bottom The bottom gradient color to be rendered.
18 | * @param container The container gradient color over which the gradient will be rendered.
19 | */
20 | @Immutable
21 | data class GradientColors(
22 | val top: Color = Color.Unspecified,
23 | val bottom: Color = Color.Unspecified,
24 | val container: Color = Color.Unspecified,
25 | )
26 |
27 | /**
28 | * A composition local for [GradientColors].
29 | */
30 | val LocalGradientColors = staticCompositionLocalOf { GradientColors() }
31 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/java/com/masewsg/app/ui/components/color/Tint.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 5/4/24, 10:36 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 4/10/24, 7:59 PM
5 | */
6 |
7 | package com.masewsg.app.ui.components.color
8 |
9 | import androidx.compose.runtime.Immutable
10 | import androidx.compose.runtime.staticCompositionLocalOf
11 | import androidx.compose.ui.graphics.Color
12 |
13 | /**
14 | * A class to model background color and tonal elevation values for Now in Android.
15 | */
16 | @Immutable
17 | data class TintTheme(
18 | val iconTint: Color = Color.Unspecified,
19 | )
20 |
21 | /**
22 | * A composition local for [TintTheme].
23 | */
24 | val LocalTintTheme = staticCompositionLocalOf { TintTheme() }
25 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/java/com/masewsg/app/ui/components/icon/Icons.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 5/4/24, 10:40 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 5/4/24, 10:36 PM
5 | */
6 |
7 | package com.masewsg.app.ui.components.icon
8 |
9 | import androidx.compose.material.icons.Icons
10 | import androidx.compose.material.icons.outlined.Info
11 | import androidx.compose.material.icons.outlined.PlayArrow
12 | import androidx.compose.ui.graphics.vector.ImageVector
13 |
14 | /**
15 | * Now in Android icons. Material icons are [ImageVector]s, custom icons are drawable resource IDs.
16 | */
17 | object ComposeIcons {
18 | val Info = Icons.Outlined.Info
19 | val PlayArrow = Icons.Outlined.PlayArrow
20 | }
21 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/java/com/masewsg/app/ui/components/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 5/4/24, 10:36 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 4/10/24, 8:11 PM
5 | */
6 |
7 | package com.masewsg.app.ui.components.theme
8 |
9 | import android.os.Build
10 | import androidx.annotation.ChecksSdkIntAtLeast
11 | import androidx.annotation.VisibleForTesting
12 | import androidx.compose.foundation.isSystemInDarkTheme
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.darkColorScheme
15 | import androidx.compose.material3.dynamicDarkColorScheme
16 | import androidx.compose.material3.dynamicLightColorScheme
17 | import androidx.compose.material3.lightColorScheme
18 | import androidx.compose.material3.surfaceColorAtElevation
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.CompositionLocalProvider
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.platform.LocalContext
23 | import androidx.compose.ui.unit.dp
24 | import com.masewsg.app.ui.components.BackgroundTheme
25 | import com.masewsg.app.ui.components.LocalBackgroundTheme
26 | import com.masewsg.app.ui.components.color.Blue10
27 | import com.masewsg.app.ui.components.color.Blue20
28 | import com.masewsg.app.ui.components.color.Blue30
29 | import com.masewsg.app.ui.components.color.Blue40
30 | import com.masewsg.app.ui.components.color.Blue80
31 | import com.masewsg.app.ui.components.color.Blue90
32 | import com.masewsg.app.ui.components.color.DarkGreen10
33 | import com.masewsg.app.ui.components.color.DarkGreen20
34 | import com.masewsg.app.ui.components.color.DarkGreen30
35 | import com.masewsg.app.ui.components.color.DarkGreen40
36 | import com.masewsg.app.ui.components.color.DarkGreen80
37 | import com.masewsg.app.ui.components.color.DarkGreen90
38 | import com.masewsg.app.ui.components.color.DarkGreenGray10
39 | import com.masewsg.app.ui.components.color.DarkGreenGray20
40 | import com.masewsg.app.ui.components.color.DarkGreenGray90
41 | import com.masewsg.app.ui.components.color.DarkGreenGray95
42 | import com.masewsg.app.ui.components.color.DarkGreenGray99
43 | import com.masewsg.app.ui.components.color.DarkPurpleGray10
44 | import com.masewsg.app.ui.components.color.DarkPurpleGray20
45 | import com.masewsg.app.ui.components.color.DarkPurpleGray90
46 | import com.masewsg.app.ui.components.color.DarkPurpleGray95
47 | import com.masewsg.app.ui.components.color.DarkPurpleGray99
48 | import com.masewsg.app.ui.components.color.GradientColors
49 | import com.masewsg.app.ui.components.color.Green10
50 | import com.masewsg.app.ui.components.color.Green20
51 | import com.masewsg.app.ui.components.color.Green30
52 | import com.masewsg.app.ui.components.color.Green40
53 | import com.masewsg.app.ui.components.color.Green80
54 | import com.masewsg.app.ui.components.color.Green90
55 | import com.masewsg.app.ui.components.color.GreenGray30
56 | import com.masewsg.app.ui.components.color.GreenGray50
57 | import com.masewsg.app.ui.components.color.GreenGray60
58 | import com.masewsg.app.ui.components.color.GreenGray80
59 | import com.masewsg.app.ui.components.color.GreenGray90
60 | import com.masewsg.app.ui.components.color.LocalGradientColors
61 | import com.masewsg.app.ui.components.color.LocalTintTheme
62 | import com.masewsg.app.ui.components.color.Orange10
63 | import com.masewsg.app.ui.components.color.Orange20
64 | import com.masewsg.app.ui.components.color.Orange30
65 | import com.masewsg.app.ui.components.color.Orange40
66 | import com.masewsg.app.ui.components.color.Orange80
67 | import com.masewsg.app.ui.components.color.Orange90
68 | import com.masewsg.app.ui.components.color.Purple10
69 | import com.masewsg.app.ui.components.color.Purple20
70 | import com.masewsg.app.ui.components.color.Purple30
71 | import com.masewsg.app.ui.components.color.Purple40
72 | import com.masewsg.app.ui.components.color.Purple80
73 | import com.masewsg.app.ui.components.color.Purple90
74 | import com.masewsg.app.ui.components.color.PurpleGray30
75 | import com.masewsg.app.ui.components.color.PurpleGray50
76 | import com.masewsg.app.ui.components.color.PurpleGray60
77 | import com.masewsg.app.ui.components.color.PurpleGray80
78 | import com.masewsg.app.ui.components.color.PurpleGray90
79 | import com.masewsg.app.ui.components.color.Red10
80 | import com.masewsg.app.ui.components.color.Red20
81 | import com.masewsg.app.ui.components.color.Red30
82 | import com.masewsg.app.ui.components.color.Red40
83 | import com.masewsg.app.ui.components.color.Red80
84 | import com.masewsg.app.ui.components.color.Red90
85 | import com.masewsg.app.ui.components.color.Teal10
86 | import com.masewsg.app.ui.components.color.Teal20
87 | import com.masewsg.app.ui.components.color.Teal30
88 | import com.masewsg.app.ui.components.color.Teal40
89 | import com.masewsg.app.ui.components.color.Teal80
90 | import com.masewsg.app.ui.components.color.Teal90
91 | import com.masewsg.app.ui.components.color.TintTheme
92 | import com.masewsg.app.ui.components.typography.Typography
93 |
94 | /**
95 | * Light default theme color scheme
96 | */
97 | @VisibleForTesting
98 | val LightDefaultColorScheme = lightColorScheme(
99 | primary = Purple40,
100 | onPrimary = Color.White,
101 | primaryContainer = Purple90,
102 | onPrimaryContainer = Purple10,
103 | secondary = Orange40,
104 | onSecondary = Color.White,
105 | secondaryContainer = Orange90,
106 | onSecondaryContainer = Orange10,
107 | tertiary = Blue40,
108 | onTertiary = Color.White,
109 | tertiaryContainer = Blue90,
110 | onTertiaryContainer = Blue10,
111 | error = Red40,
112 | onError = Color.White,
113 | errorContainer = Red90,
114 | onErrorContainer = Red10,
115 | background = DarkPurpleGray99,
116 | onBackground = DarkPurpleGray10,
117 | surface = DarkPurpleGray99,
118 | onSurface = DarkPurpleGray10,
119 | surfaceVariant = PurpleGray90,
120 | onSurfaceVariant = PurpleGray30,
121 | inverseSurface = DarkPurpleGray20,
122 | inverseOnSurface = DarkPurpleGray95,
123 | outline = PurpleGray50,
124 | )
125 |
126 | /**
127 | * Dark default theme color scheme
128 | */
129 | @VisibleForTesting
130 | val DarkDefaultColorScheme = darkColorScheme(
131 | primary = Purple80,
132 | onPrimary = Purple20,
133 | primaryContainer = Purple30,
134 | onPrimaryContainer = Purple90,
135 | secondary = Orange80,
136 | onSecondary = Orange20,
137 | secondaryContainer = Orange30,
138 | onSecondaryContainer = Orange90,
139 | tertiary = Blue80,
140 | onTertiary = Blue20,
141 | tertiaryContainer = Blue30,
142 | onTertiaryContainer = Blue90,
143 | error = Red80,
144 | onError = Red20,
145 | errorContainer = Red30,
146 | onErrorContainer = Red90,
147 | background = DarkPurpleGray10,
148 | onBackground = DarkPurpleGray90,
149 | surface = DarkPurpleGray10,
150 | onSurface = DarkPurpleGray90,
151 | surfaceVariant = PurpleGray30,
152 | onSurfaceVariant = PurpleGray80,
153 | inverseSurface = DarkPurpleGray90,
154 | inverseOnSurface = DarkPurpleGray10,
155 | outline = PurpleGray60,
156 | )
157 |
158 | /**
159 | * Light Android theme color scheme
160 | */
161 | @VisibleForTesting
162 | val LightAndroidColorScheme = lightColorScheme(
163 | primary = Green40,
164 | onPrimary = Color.White,
165 | primaryContainer = Green90,
166 | onPrimaryContainer = Green10,
167 | secondary = DarkGreen40,
168 | onSecondary = Color.White,
169 | secondaryContainer = DarkGreen90,
170 | onSecondaryContainer = DarkGreen10,
171 | tertiary = Teal40,
172 | onTertiary = Color.White,
173 | tertiaryContainer = Teal90,
174 | onTertiaryContainer = Teal10,
175 | error = Red40,
176 | onError = Color.White,
177 | errorContainer = Red90,
178 | onErrorContainer = Red10,
179 | background = DarkGreenGray99,
180 | onBackground = DarkGreenGray10,
181 | surface = DarkGreenGray99,
182 | onSurface = DarkGreenGray10,
183 | surfaceVariant = GreenGray90,
184 | onSurfaceVariant = GreenGray30,
185 | inverseSurface = DarkGreenGray20,
186 | inverseOnSurface = DarkGreenGray95,
187 | outline = GreenGray50,
188 | )
189 |
190 | /**
191 | * Dark Android theme color scheme
192 | */
193 | @VisibleForTesting
194 | val DarkAndroidColorScheme = darkColorScheme(
195 | primary = Green80,
196 | onPrimary = Green20,
197 | primaryContainer = Green30,
198 | onPrimaryContainer = Green90,
199 | secondary = DarkGreen80,
200 | onSecondary = DarkGreen20,
201 | secondaryContainer = DarkGreen30,
202 | onSecondaryContainer = DarkGreen90,
203 | tertiary = Teal80,
204 | onTertiary = Teal20,
205 | tertiaryContainer = Teal30,
206 | onTertiaryContainer = Teal90,
207 | error = Red80,
208 | onError = Red20,
209 | errorContainer = Red30,
210 | onErrorContainer = Red90,
211 | background = DarkGreenGray10,
212 | onBackground = DarkGreenGray90,
213 | surface = DarkGreenGray10,
214 | onSurface = DarkGreenGray90,
215 | surfaceVariant = GreenGray30,
216 | onSurfaceVariant = GreenGray80,
217 | inverseSurface = DarkGreenGray90,
218 | inverseOnSurface = DarkGreenGray10,
219 | outline = GreenGray60,
220 | )
221 |
222 | /**
223 | * Light Android gradient colors
224 | */
225 | val LightAndroidGradientColors = GradientColors(container = DarkGreenGray95)
226 |
227 | /**
228 | * Dark Android gradient colors
229 | */
230 | val DarkAndroidGradientColors = GradientColors(container = Color.Black)
231 |
232 | /**
233 | * Light Android background theme
234 | */
235 | val LightAndroidBackgroundTheme = BackgroundTheme(color = DarkGreenGray95)
236 |
237 | /**
238 | * Dark Android background theme
239 | */
240 | val DarkAndroidBackgroundTheme = BackgroundTheme(color = Color.Black)
241 |
242 | /**
243 | * Now in Android theme.
244 | *
245 | * @param darkTheme Whether the theme should use a dark color scheme (follows system by default).
246 | * @param androidTheme Whether the theme should use the Android theme color scheme instead of the
247 | * default theme.
248 | * @param disableDynamicTheming If `true`, disables the use of dynamic theming, even when it is
249 | * supported. This parameter has no effect if [androidTheme] is `true`.
250 | */
251 | @Composable
252 | fun ComposeTheme(
253 | darkTheme: Boolean = isSystemInDarkTheme(),
254 | androidTheme: Boolean = false,
255 | disableDynamicTheming: Boolean = true,
256 | content: @Composable () -> Unit,
257 | ) {
258 | // Color scheme
259 | val colorScheme = when {
260 | androidTheme -> if (darkTheme) DarkAndroidColorScheme else LightAndroidColorScheme
261 | !disableDynamicTheming && supportsDynamicTheming() -> {
262 | val context = LocalContext.current
263 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
264 | }
265 |
266 | else -> if (darkTheme) DarkDefaultColorScheme else LightDefaultColorScheme
267 | }
268 | // Gradient colors
269 | val emptyGradientColors = GradientColors(container = colorScheme.surfaceColorAtElevation(2.dp))
270 | val defaultGradientColors = GradientColors(
271 | top = colorScheme.inverseOnSurface,
272 | bottom = colorScheme.primaryContainer,
273 | container = colorScheme.surface,
274 | )
275 | val gradientColors = when {
276 | androidTheme -> if (darkTheme) DarkAndroidGradientColors else LightAndroidGradientColors
277 | !disableDynamicTheming && supportsDynamicTheming() -> emptyGradientColors
278 | else -> defaultGradientColors
279 | }
280 | // Background theme
281 | val defaultBackgroundTheme = BackgroundTheme(
282 | color = colorScheme.surface,
283 | tonalElevation = 2.dp,
284 | )
285 | val backgroundTheme = when {
286 | androidTheme -> if (darkTheme) DarkAndroidBackgroundTheme else LightAndroidBackgroundTheme
287 | else -> defaultBackgroundTheme
288 | }
289 | val tintTheme = when {
290 | androidTheme -> TintTheme()
291 | !disableDynamicTheming && supportsDynamicTheming() -> TintTheme(colorScheme.primary)
292 | else -> TintTheme()
293 | }
294 | // Composition locals
295 | CompositionLocalProvider(
296 | LocalGradientColors provides gradientColors,
297 | LocalBackgroundTheme provides backgroundTheme,
298 | LocalTintTheme provides tintTheme,
299 | ) {
300 | MaterialTheme(
301 | colorScheme = colorScheme,
302 | typography = Typography,
303 | content = content,
304 | )
305 | }
306 | }
307 |
308 | @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
309 | fun supportsDynamicTheming() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
310 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/java/com/masewsg/app/ui/components/typography/Typography.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Created by nphau on 4/10/24, 8:31 PM
3 | * Copyright (c) 2024 . All rights reserved.
4 | * Last modified 4/10/24, 8:31 PM
5 | */
6 |
7 | package com.masewsg.app.ui.components.typography
8 |
9 | import androidx.compose.material3.Typography
10 | import androidx.compose.ui.text.TextStyle
11 | import androidx.compose.ui.text.font.FontWeight
12 | import androidx.compose.ui.text.style.LineHeightStyle
13 | import androidx.compose.ui.text.style.LineHeightStyle.Alignment
14 | import androidx.compose.ui.text.style.LineHeightStyle.Trim
15 | import androidx.compose.ui.unit.sp
16 |
17 | /**
18 | * Now in Android typography.
19 | */
20 | internal val Typography = Typography(
21 | displayLarge = TextStyle(
22 | fontWeight = FontWeight.Normal,
23 | fontSize = 57.sp,
24 | lineHeight = 64.sp,
25 | letterSpacing = (-0.25).sp,
26 | ),
27 | displayMedium = TextStyle(
28 | fontWeight = FontWeight.Normal,
29 | fontSize = 45.sp,
30 | lineHeight = 52.sp,
31 | letterSpacing = 0.sp,
32 | ),
33 | displaySmall = TextStyle(
34 | fontWeight = FontWeight.Normal,
35 | fontSize = 36.sp,
36 | lineHeight = 44.sp,
37 | letterSpacing = 0.sp,
38 | ),
39 | headlineLarge = TextStyle(
40 | fontWeight = FontWeight.Normal,
41 | fontSize = 32.sp,
42 | lineHeight = 40.sp,
43 | letterSpacing = 0.sp,
44 | ),
45 | headlineMedium = TextStyle(
46 | fontWeight = FontWeight.Normal,
47 | fontSize = 28.sp,
48 | lineHeight = 36.sp,
49 | letterSpacing = 0.sp,
50 | ),
51 | headlineSmall = TextStyle(
52 | fontWeight = FontWeight.Normal,
53 | fontSize = 24.sp,
54 | lineHeight = 32.sp,
55 | letterSpacing = 0.sp,
56 | lineHeightStyle = LineHeightStyle(
57 | alignment = Alignment.Bottom,
58 | trim = Trim.None,
59 | ),
60 | ),
61 | titleLarge = TextStyle(
62 | fontWeight = FontWeight.Bold,
63 | fontSize = 22.sp,
64 | lineHeight = 28.sp,
65 | letterSpacing = 0.sp,
66 | lineHeightStyle = LineHeightStyle(
67 | alignment = Alignment.Bottom,
68 | trim = Trim.LastLineBottom,
69 | ),
70 | ),
71 | titleMedium = TextStyle(
72 | fontWeight = FontWeight.Bold,
73 | fontSize = 18.sp,
74 | lineHeight = 24.sp,
75 | letterSpacing = 0.1.sp,
76 | ),
77 | titleSmall = TextStyle(
78 | fontWeight = FontWeight.Medium,
79 | fontSize = 14.sp,
80 | lineHeight = 20.sp,
81 | letterSpacing = 0.1.sp,
82 | ),
83 | // Default text style
84 | bodyLarge = TextStyle(
85 | fontWeight = FontWeight.Normal,
86 | fontSize = 16.sp,
87 | lineHeight = 24.sp,
88 | letterSpacing = 0.5.sp,
89 | lineHeightStyle = LineHeightStyle(
90 | alignment = Alignment.Center,
91 | trim = Trim.None,
92 | ),
93 | ),
94 | bodyMedium = TextStyle(
95 | fontWeight = FontWeight.Normal,
96 | fontSize = 14.sp,
97 | lineHeight = 20.sp,
98 | letterSpacing = 0.25.sp,
99 | ),
100 | bodySmall = TextStyle(
101 | fontWeight = FontWeight.Normal,
102 | fontSize = 12.sp,
103 | lineHeight = 16.sp,
104 | letterSpacing = 0.4.sp,
105 | ),
106 | // Used for Button
107 | labelLarge = TextStyle(
108 | fontWeight = FontWeight.Medium,
109 | fontSize = 14.sp,
110 | lineHeight = 20.sp,
111 | letterSpacing = 0.1.sp,
112 | lineHeightStyle = LineHeightStyle(
113 | alignment = Alignment.Center,
114 | trim = Trim.LastLineBottom,
115 | ),
116 | ),
117 | // Used for Navigation items
118 | labelMedium = TextStyle(
119 | fontWeight = FontWeight.Medium,
120 | fontSize = 12.sp,
121 | lineHeight = 16.sp,
122 | letterSpacing = 0.5.sp,
123 | lineHeightStyle = LineHeightStyle(
124 | alignment = Alignment.Center,
125 | trim = Trim.LastLineBottom,
126 | ),
127 | ),
128 | // Used for Tag
129 | labelSmall = TextStyle(
130 | fontWeight = FontWeight.Medium,
131 | fontSize = 10.sp,
132 | lineHeight = 14.sp,
133 | letterSpacing = 0.sp,
134 | lineHeightStyle = LineHeightStyle(
135 | alignment = Alignment.Center,
136 | trim = Trim.LastLineBottom,
137 | ),
138 | ),
139 | )
--------------------------------------------------------------------------------
/foundation/ui/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 8dp
4 | 48dp
5 |
--------------------------------------------------------------------------------
/foundation/ui/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | android.defaults.buildfeatures.buildconfig=true
23 | android.nonTransitiveRClass=false
24 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | #
3 | agp = "8.5.2"
4 | appcompat = "1.7.0"
5 | coreSplashscreen = "1.0.1"
6 | gradle = "8.8.0"
7 | kotlin = "1.9.0"
8 | coreKtx = "1.13.1"
9 | junit = "4.13.2"
10 | junitVersion = "1.2.1"
11 | espressoCore = "3.6.1"
12 | kotlinGradlePlugin = "1.9.23"
13 | kotlinTest = "2.0.20"
14 | kotlinTestJunit = "2.0.20"
15 | kotlinxCoroutinesCore = "1.8.1"
16 | kotlinxSerializationJson = "1.6.3"
17 | ktorSerializationKotlinxJson = "2.3.10"
18 | ktorServerContentNegotiation = "2.3.10"
19 | ktorServerCore = "2.3.12"
20 | ktorServerCors = "2.3.10"
21 | ktorClientContentNegotiation = "2.3.10"
22 | ktorServerNetty = "2.3.10"
23 | lifecycleRuntimeKtx = "2.8.5"
24 | activityCompose = "1.9.2"
25 | composeBom = "2024.09.00"
26 | mockitoCore = "3.12.4"
27 | mockitoInline = "3.11.2"
28 | navigationCompose = "2.8.0"
29 |
30 | [libraries]
31 | # Kotlin
32 | androidx-foundation = { module = "androidx.compose.foundation:foundation" }
33 | material3 = { module = "androidx.compose.material3:material3" }
34 | tools-gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
35 | tools-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinGradlePlugin" }
36 |
37 | # Android
38 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
39 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
40 | androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
41 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
42 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
43 |
44 | # Compose
45 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
46 | androidx-ui = { group = "androidx.compose.ui", name = "ui" }
47 | androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
48 | androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
49 | androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
50 | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
51 | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
52 | androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
53 |
54 | # Testing
55 | junit = { group = "junit", name = "junit", version.ref = "junit" }
56 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
57 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
58 |
59 | # Navigation
60 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlinTest" }
61 | kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlinTestJunit" }
62 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
63 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
64 | ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorClientContentNegotiation" }
65 | ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktorSerializationKotlinxJson" }
66 | ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktorServerCore" }
67 | ktor-server-cors = { module = "io.ktor:ktor-server-cors", version.ref = "ktorServerCors" }
68 | ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktorServerContentNegotiation" }
69 | ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktorServerNetty" }
70 | mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockitoCore" }
71 | mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoInline" }
72 | navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
73 |
74 | [plugins]
75 | android-application = { id = "com.android.application", version.ref = "agp" }
76 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nphausg/android.embeddedserver/41ba38d1ea2e9fb2401b2fdb4fd57b7343bb738f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Oct 21 23:49:45 ICT 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
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 |
--------------------------------------------------------------------------------
/scripts/cleanup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | find . -name .gradle -exec rm -rf {} \;
3 | find . -name build -exec rm -rf {} \;
--------------------------------------------------------------------------------
/scripts/rebase.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | git fetch origin develop
3 | git rebase origin develop
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | rootProject.name = "android.embeddedserver"
9 | include ':app'
10 | // Modules
11 | include ':foundation-ui'
12 | project(':foundation-ui').projectDir = new File(rootDir, 'foundation/ui')
13 |
--------------------------------------------------------------------------------