├── .fleet
└── settings.json
├── .gitignore
├── .idea
├── .name
├── migrations.xml
├── misc.xml
├── other.xml
└── vcs.xml
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── local.properties
├── settings.gradle.kts
├── site-android
├── .gitignore
├── .kobweb
│ ├── conf.yaml
│ ├── server
│ │ ├── logs
│ │ │ └── kobweb-server.log
│ │ ├── server.jar
│ │ ├── start.bat
│ │ └── start.sh
│ └── site
│ │ ├── admin
│ │ ├── all-posts.html
│ │ ├── create-post.html
│ │ └── my-posts.html
│ │ ├── android-figure.webp
│ │ ├── androidhub-logo-dark.webp
│ │ ├── androidhub-logo.webp
│ │ ├── androidhub.js
│ │ ├── androidhub.js.map
│ │ ├── author.html
│ │ ├── bold.png
│ │ ├── category.html
│ │ ├── code.png
│ │ ├── favicon.ico
│ │ ├── image.png
│ │ ├── index.html
│ │ ├── italic.png
│ │ ├── jetpack.webp
│ │ ├── link.png
│ │ ├── login.html
│ │ ├── posts.html
│ │ ├── quote.png
│ │ ├── search.html
│ │ └── title-subtitle.png
├── build.gradle.kts
└── src
│ └── jsMain
│ ├── kotlin
│ └── com
│ │ └── canerture
│ │ └── androidhub
│ │ ├── AppEntry.kt
│ │ ├── AppStyles.kt
│ │ ├── SiteTheme.kt
│ │ ├── common
│ │ ├── Constants.kt
│ │ └── Extensions.kt
│ │ ├── components
│ │ ├── layouts
│ │ │ ├── AdminPageLayout.kt
│ │ │ └── PageLayout.kt
│ │ ├── sections
│ │ │ ├── Footer.kt
│ │ │ ├── NavHeader.kt
│ │ │ ├── OverflowSidePanel.kt
│ │ │ └── SidePanel.kt
│ │ └── widgets
│ │ │ ├── CategoryChip.kt
│ │ │ ├── ErrorView.kt
│ │ │ ├── LoadingIndicator.kt
│ │ │ ├── Popup.kt
│ │ │ ├── PostPreview.kt
│ │ │ ├── PostsView.kt
│ │ │ ├── SearchArea.kt
│ │ │ └── SearchBar.kt
│ │ ├── data
│ │ ├── CategoryApis.kt
│ │ ├── PostApis.kt
│ │ ├── UserApis.kt
│ │ └── model
│ │ │ ├── AuthorDetail.kt
│ │ │ ├── BaseResponse.kt
│ │ │ ├── Category.kt
│ │ │ ├── GetPostsResponse.kt
│ │ │ ├── LoginRequest.kt
│ │ │ ├── Post.kt
│ │ │ ├── RegisterRequest.kt
│ │ │ └── User.kt
│ │ ├── models
│ │ ├── ControlStyle.kt
│ │ └── EditorControl.kt
│ │ ├── navigation
│ │ └── Screen.kt
│ │ ├── pages
│ │ ├── adminallposts
│ │ │ └── AdminAllPosts.kt
│ │ ├── admincreatepost
│ │ │ ├── AdminCreatePost.kt
│ │ │ └── Functions.kt
│ │ ├── adminmyposts
│ │ │ └── AdminMyPosts.kt
│ │ ├── author
│ │ │ └── Author.kt
│ │ ├── category
│ │ │ └── Category.kt
│ │ ├── index
│ │ │ ├── AndroidHeroContent.kt
│ │ │ ├── Index.kt
│ │ │ ├── LatestArticleItem.kt
│ │ │ └── PopularArticleItem.kt
│ │ ├── login
│ │ │ └── Login.kt
│ │ ├── post
│ │ │ └── PostDetail.kt
│ │ └── search
│ │ │ └── Search.kt
│ │ └── utils
│ │ └── ApiUtils.kt
│ └── resources
│ ├── markdown
│ └── About.md
│ └── public
│ ├── android-figure.webp
│ ├── androidhub-logo-dark.webp
│ ├── androidhub-logo.webp
│ ├── bold.png
│ ├── code.png
│ ├── favicon.ico
│ ├── image.png
│ ├── italic.png
│ ├── jetpack.webp
│ ├── link.png
│ ├── quote.png
│ └── title-subtitle.png
└── site-ios
├── .gitignore
├── .kobweb
├── conf.yaml
├── server
│ ├── logs
│ │ └── kobweb-server.log
│ ├── server.jar
│ ├── start.bat
│ └── start.sh
└── site
│ ├── admin
│ ├── all-posts.html
│ ├── create-post.html
│ └── my-posts.html
│ ├── androidhub.js
│ ├── androidhub.js.map
│ ├── author.html
│ ├── bold.png
│ ├── category.html
│ ├── code.png
│ ├── favicon.ico
│ ├── image.png
│ ├── index.html
│ ├── ios-figure.webp
│ ├── ioshub-logo-dark.webp
│ ├── ioshub-logo.webp
│ ├── italic.png
│ ├── jetpack.webp
│ ├── link.png
│ ├── login.html
│ ├── posts.html
│ ├── quote.png
│ ├── search.html
│ └── title-subtitle.png
├── build.gradle.kts
└── src
└── jsMain
├── kotlin
└── com
│ └── canerture
│ └── androidhub
│ ├── AppEntry.kt
│ ├── AppStyles.kt
│ ├── SiteTheme.kt
│ ├── common
│ ├── Constants.kt
│ └── Extensions.kt
│ ├── components
│ ├── layouts
│ │ ├── AdminPageLayout.kt
│ │ └── PageLayout.kt
│ ├── sections
│ │ ├── Footer.kt
│ │ ├── NavHeader.kt
│ │ ├── OverflowSidePanel.kt
│ │ └── SidePanel.kt
│ └── widgets
│ │ ├── CategoryChip.kt
│ │ ├── ErrorView.kt
│ │ ├── LoadingIndicator.kt
│ │ ├── Popup.kt
│ │ ├── PostPreview.kt
│ │ ├── PostsView.kt
│ │ ├── SearchArea.kt
│ │ └── SearchBar.kt
│ ├── data
│ ├── CategoryApis.kt
│ ├── PostApis.kt
│ ├── UserApis.kt
│ └── model
│ │ ├── AuthorDetail.kt
│ │ ├── BaseResponse.kt
│ │ ├── Category.kt
│ │ ├── GetPostsResponse.kt
│ │ ├── LoginRequest.kt
│ │ ├── Post.kt
│ │ ├── RegisterRequest.kt
│ │ └── User.kt
│ ├── models
│ ├── ControlStyle.kt
│ └── EditorControl.kt
│ ├── navigation
│ └── Screen.kt
│ ├── pages
│ ├── adminallposts
│ │ └── AdminAllPosts.kt
│ ├── admincreatepost
│ │ ├── AdminCreatePost.kt
│ │ └── Functions.kt
│ ├── adminmyposts
│ │ └── AdminMyPosts.kt
│ ├── author
│ │ └── Author.kt
│ ├── category
│ │ └── Category.kt
│ ├── index
│ │ ├── IOSHeroContent.kt
│ │ ├── Index.kt
│ │ ├── LatestArticleItem.kt
│ │ └── PopularArticleItem.kt
│ ├── login
│ │ └── Login.kt
│ ├── post
│ │ └── PostDetail.kt
│ └── search
│ │ └── Search.kt
│ └── utils
│ └── ApiUtils.kt
└── resources
├── markdown
└── About.md
└── public
├── bold.png
├── code.png
├── favicon.ico
├── image.png
├── ios-figure.webp
├── ioshub-logo-dark.webp
├── ioshub-logo.webp
├── italic.png
├── jetpack.webp
├── link.png
├── quote.png
└── title-subtitle.png
/.fleet/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "toolchains": []
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # General ignores
2 | .DS_Store
3 | build
4 | out
5 | kotlin-js-store
6 |
7 | # IntelliJ ignores
8 | *.iml
9 | /*.ipr
10 |
11 | /.idea/caches
12 | /.idea/libraries
13 | /.idea/modules.xml
14 | /.idea/workspace.xml
15 | /.idea/gradle.xml
16 | /.idea/navEditor.xml
17 | /.idea/assetWizardSettings.xml
18 | /.idea/artifacts
19 | /.idea/compiler.xml
20 | /.idea/jarRepositories.xml
21 | /.idea/*.iml
22 | /.idea/modules
23 | /.idea/libraries-with-intellij-classes.xml
24 |
25 | # Gradle ignores
26 | .gradle
27 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | androidhub
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Kobweb](https://github.com/varabyte/kobweb) project bootstrapped with the `app` template.
2 |
3 | ## Screenshots
4 |
5 | 
6 |
7 | Live -> [https://androidhub.dev/](https://androidhub.dev/)
8 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | subprojects {
2 | repositories {
3 | mavenCentral()
4 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
5 | google()
6 | maven("https://us-central1-maven.pkg.dev/varabyte-repos/public")
7 | maven(url = "https://jitpack.io")
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | org.gradle.jvmargs=-Xmx4096m
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | jetbrains-compose = "1.6.2"
3 | kobweb = "0.17.3"
4 | kotlin = "1.9.23"
5 | kotlinx-serialization = "1.4.0"
6 | colorMath = "3.5.0"
7 |
8 | [libraries]
9 | kobweb-api = { module = "com.varabyte.kobweb:kobweb-api", version.ref = "kobweb" }
10 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core ", version.ref = "kobweb" }
11 | kobweb-silk = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" }
12 | silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" }
13 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
14 | colorMath = { module = "com.github.ajalt.colormath:colormath", version.ref = "colorMath" }
15 |
16 | [plugins]
17 | jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "jetbrains-compose" }
18 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" }
19 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
20 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | ## This file must *NOT* be checked into Version Control Systems,
2 | # as it contains information specific to your local configuration.
3 | #
4 | # Location of the SDK. This is only used by Gradle.
5 | # For customization when using a Version Control System, please read the
6 | # header note.
7 | #Wed Apr 17 01:45:18 TRT 2024
8 | sdk.dir=/Users/canerture/Library/Android/sdk
9 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
5 | maven("https://us-central1-maven.pkg.dev/varabyte-repos/public")
6 | }
7 | }
8 |
9 | rootProject.name = "androidhub"
10 |
11 | include(":site-android")
12 | include(":site-ios")
13 |
--------------------------------------------------------------------------------
/site-android/.gitignore:
--------------------------------------------------------------------------------
1 | # Kobweb ignores
2 | !.kobweb/conf.yaml
3 |
--------------------------------------------------------------------------------
/site-android/.kobweb/conf.yaml:
--------------------------------------------------------------------------------
1 | site:
2 | title: "."
3 |
4 | server:
5 | files:
6 | dev:
7 | contentRoot: "build/processedResources/js/main/public"
8 | script: "build/dist/js/developmentExecutable/androidhub.js"
9 | api: "build/libs/androidhub.jar"
10 | prod:
11 | script: "build/dist/js/productionExecutable/androidhub.js"
12 | siteRoot: ".kobweb/site"
13 |
14 | port: 8080
15 |
--------------------------------------------------------------------------------
/site-android/.kobweb/server/logs/kobweb-server.log:
--------------------------------------------------------------------------------
1 | 2024-06-15 04:01:37.322 [main] DEBUG ktor.application - Java Home: /Users/canerture/Library/Java/JavaVirtualMachines/jbr-17.0.9/Contents
2 | 2024-06-15 04:01:37.326 [main] DEBUG ktor.application - Class Loader: jdk.internal.loader.ClassLoaders$AppClassLoader@7aec35a: [file:/Users/canerture/Documents/GitHub/AndroidHub/site-android/.kobweb/server/server.jar!/ch]
3 | 2024-06-15 04:01:37.334 [main] INFO ktor.application - API jar found and will be loaded: "build/libs/androidhub.jar"
4 | 2024-06-15 04:01:37.355 [main] INFO ktor.application - Loaded and initialized server API jar in 1ms.
5 | 2024-06-15 04:01:37.365 [main] INFO ktor.application - Initializing Kobweb streams.
6 | 2024-06-15 04:01:37.382 [main] INFO ktor.application - Application started in 0.089 seconds.
7 | 2024-06-15 04:01:37.439 [main] INFO ktor.application - Responding at http://0.0.0.0:8080
8 | 2024-06-15 04:02:06.316 [kotlinx.coroutines.DefaultExecutor] INFO ktor.application - Kobweb server shutting down...
9 | 2024-06-15 04:02:07.375 [kotlinx.coroutines.DefaultExecutor] INFO ktor.application - Server finished shutting down.
10 |
--------------------------------------------------------------------------------
/site-android/.kobweb/server/server.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/server/server.jar
--------------------------------------------------------------------------------
/site-android/.kobweb/server/start.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | :: Find the kobweb project folder, using this file's location to start form
4 | cd /d "%~dp0"
5 | cd ..\..
6 |
7 | if defined KOBWEB_JAVA_HOME (
8 | set "java_cmd=%KOBWEB_JAVA_HOME%\bin\java"
9 | ) else if defined JAVA_HOME (
10 | set "java_cmd=%JAVA_HOME%\bin\java"
11 | ) else (
12 | set "java_cmd=java"
13 | )
14 |
15 | :: Run the java command with the common parameters
16 | %java_cmd% -Dkobweb.server.environment=PROD -Dkobweb.site.layout=FULLSTACK -Dio.ktor.development=false -jar .kobweb/server/server.jar
--------------------------------------------------------------------------------
/site-android/.kobweb/server/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Find the kobweb project folder, using this file's location to start form
4 | cd "$(dirname "$0")"
5 | cd ../..
6 |
7 | args="-Dkobweb.server.environment=PROD -Dkobweb.site.layout=FULLSTACK -Dio.ktor.development=false -jar .kobweb/server/server.jar"
8 |
9 | if [ -n "$KOBWEB_JAVA_HOME" ]; then
10 | "$KOBWEB_JAVA_HOME/bin/java" $args
11 | elif [ -n "$JAVA_HOME" ]; then
12 | "$JAVA_HOME/bin/java" $args
13 | else
14 | java $args
15 | fi
--------------------------------------------------------------------------------
/site-android/.kobweb/site/android-figure.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/android-figure.webp
--------------------------------------------------------------------------------
/site-android/.kobweb/site/androidhub-logo-dark.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/androidhub-logo-dark.webp
--------------------------------------------------------------------------------
/site-android/.kobweb/site/androidhub-logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/androidhub-logo.webp
--------------------------------------------------------------------------------
/site-android/.kobweb/site/bold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/bold.png
--------------------------------------------------------------------------------
/site-android/.kobweb/site/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/code.png
--------------------------------------------------------------------------------
/site-android/.kobweb/site/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/favicon.ico
--------------------------------------------------------------------------------
/site-android/.kobweb/site/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/image.png
--------------------------------------------------------------------------------
/site-android/.kobweb/site/italic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/italic.png
--------------------------------------------------------------------------------
/site-android/.kobweb/site/jetpack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/jetpack.webp
--------------------------------------------------------------------------------
/site-android/.kobweb/site/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/link.png
--------------------------------------------------------------------------------
/site-android/.kobweb/site/quote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/quote.png
--------------------------------------------------------------------------------
/site-android/.kobweb/site/title-subtitle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/.kobweb/site/title-subtitle.png
--------------------------------------------------------------------------------
/site-android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.varabyte.kobweb.gradle.application.extensions.AppBlock.LegacyRouteRedirectStrategy
2 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication
3 |
4 | plugins {
5 | alias(libs.plugins.kotlin.multiplatform)
6 | alias(libs.plugins.jetbrains.compose)
7 | alias(libs.plugins.kobweb.application)
8 | alias(libs.plugins.kotlin.serialization)
9 | }
10 |
11 | group = "com.canerture.androidhub"
12 | version = "1.0-SNAPSHOT"
13 |
14 | kobweb {
15 | app {
16 | index {
17 | description.set("Powered by Kobweb")
18 | }
19 | legacyRouteRedirectStrategy.set(LegacyRouteRedirectStrategy.DISALLOW)
20 | }
21 | }
22 |
23 | kotlin {
24 | configAsKobwebApplication("androidhub", includeServer = true)
25 |
26 | sourceSets {
27 | commonMain.dependencies {
28 | implementation(compose.runtime)
29 | }
30 |
31 | jsMain.dependencies {
32 | implementation(compose.html.core)
33 | implementation(libs.kobweb.core)
34 | implementation(libs.kobweb.silk)
35 | implementation(libs.silk.icons.fa)
36 | implementation(libs.kotlinx.serialization.json)
37 | implementation(libs.colorMath)
38 | }
39 |
40 | jvmMain.dependencies {
41 | implementation(libs.kobweb.api)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/AppEntry.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.runtime.setValue
8 | import com.canerture.androidhub.common.Res
9 | import com.varabyte.kobweb.compose.css.Cursor
10 | import com.varabyte.kobweb.compose.css.ScrollBehavior
11 | import com.varabyte.kobweb.compose.foundation.layout.Box
12 | import com.varabyte.kobweb.compose.ui.Alignment
13 | import com.varabyte.kobweb.compose.ui.Modifier
14 | import com.varabyte.kobweb.compose.ui.graphics.Colors
15 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
16 | import com.varabyte.kobweb.compose.ui.modifiers.border
17 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
18 | import com.varabyte.kobweb.compose.ui.modifiers.bottom
19 | import com.varabyte.kobweb.compose.ui.modifiers.color
20 | import com.varabyte.kobweb.compose.ui.modifiers.cursor
21 | import com.varabyte.kobweb.compose.ui.modifiers.display
22 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
23 | import com.varabyte.kobweb.compose.ui.modifiers.minHeight
24 | import com.varabyte.kobweb.compose.ui.modifiers.minWidth
25 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
26 | import com.varabyte.kobweb.compose.ui.modifiers.outline
27 | import com.varabyte.kobweb.compose.ui.modifiers.padding
28 | import com.varabyte.kobweb.compose.ui.modifiers.position
29 | import com.varabyte.kobweb.compose.ui.modifiers.right
30 | import com.varabyte.kobweb.compose.ui.modifiers.scrollBehavior
31 | import com.varabyte.kobweb.compose.ui.modifiers.size
32 | import com.varabyte.kobweb.compose.ui.modifiers.zIndex
33 | import com.varabyte.kobweb.compose.ui.toAttrs
34 | import com.varabyte.kobweb.core.App
35 | import com.varabyte.kobweb.silk.SilkApp
36 | import com.varabyte.kobweb.silk.components.graphics.Image
37 | import com.varabyte.kobweb.silk.components.layout.Surface
38 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
39 | import com.varabyte.kobweb.silk.components.style.toModifier
40 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
41 | import kotlinx.browser.document
42 | import kotlinx.browser.window
43 | import org.jetbrains.compose.web.css.Color
44 | import org.jetbrains.compose.web.css.DisplayStyle
45 | import org.jetbrains.compose.web.css.Position
46 | import org.jetbrains.compose.web.css.percent
47 | import org.jetbrains.compose.web.css.px
48 | import org.jetbrains.compose.web.css.vh
49 | import org.jetbrains.compose.web.css.vw
50 | import org.jetbrains.compose.web.dom.Div
51 | import org.w3c.dom.SMOOTH
52 | import org.w3c.dom.ScrollToOptions
53 |
54 | @App
55 | @Composable
56 | fun AppEntry(content: @Composable () -> Unit) {
57 | SilkApp {
58 | var isButtonVisible by remember { mutableStateOf(false) }
59 | val breakpoint = rememberBreakpoint()
60 |
61 | window.onscroll = {
62 | if (document.body!!.scrollTop > 300 || document.documentElement!!.scrollTop > 300) {
63 | isButtonVisible = true
64 | } else {
65 | isButtonVisible = false
66 | }
67 | }
68 |
69 | Surface(
70 | Modifier
71 | .minHeight(100.vh)
72 | .minWidth(100.vw)
73 | .scrollBehavior(ScrollBehavior.Smooth)
74 | ) {
75 | Box(
76 | contentAlignment = Alignment.Center,
77 | modifier = Modifier.fillMaxSize()
78 | ) {
79 | content()
80 |
81 | Div(
82 | attrs = Modifier
83 | .position(Position.Fixed)
84 | .bottom(20.px)
85 | .right(if (breakpoint < Breakpoint.MD) 0.px else 30.px)
86 | .zIndex(99)
87 | .border(0.px)
88 | .outline(0.px)
89 | .backgroundColor(Color.transparent)
90 | .color(Colors.White)
91 | .cursor(Cursor.Pointer)
92 | .borderRadius(100.percent)
93 | .padding(
94 | bottom = if (breakpoint < Breakpoint.MD) 10.px else 15.px,
95 | right = if (breakpoint < Breakpoint.MD) 4.px else 22.px
96 | )
97 | .display(
98 | if (isButtonVisible) DisplayStyle.Block else DisplayStyle.None
99 | )
100 | .onClick {
101 | document.documentElement?.scrollTo(
102 | ScrollToOptions(
103 | behavior = org.w3c.dom.ScrollBehavior.SMOOTH,
104 | top = 0.0
105 | )
106 | )
107 | }.toAttrs()
108 | ) {
109 | val size = if (breakpoint < Breakpoint.MD) 40.px else 60.px
110 | Image(
111 | src = Res.Image.JETPACK,
112 | modifier = Modifier.size(size, size)
113 | )
114 | }
115 | }
116 | }
117 | }
118 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/SiteTheme.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub
2 |
3 | import com.varabyte.kobweb.compose.ui.graphics.Color
4 | import com.varabyte.kobweb.compose.ui.graphics.Colors
5 | import com.varabyte.kobweb.silk.init.InitSilk
6 | import com.varabyte.kobweb.silk.init.InitSilkContext
7 | import com.varabyte.kobweb.silk.theme.colors.palette.background
8 | import com.varabyte.kobweb.silk.theme.colors.palette.color
9 |
10 | /**
11 | * @property nearBackground A useful color to apply to a container that should differentiate itself from the background
12 | * but just a little.
13 | */
14 | class SitePalette(
15 | val green: Color,
16 | val white: Color,
17 | val blue: Color,
18 | val gray: Color,
19 | val lightGray: Color,
20 | val grayTransparent: Color,
21 | )
22 |
23 | object SitePalettes {
24 | val palette = SitePalette(
25 | green = Color.rgb(0x3DDB86),
26 | white = Colors.White,
27 | blue = Color.rgb(0x092F42),
28 | gray = Color.rgb(0xC8C8C8),
29 | lightGray = Color.rgb(0xF0F0F0),
30 | grayTransparent = Color.rgba(255f, 255f, 255f, 0.15f),
31 | )
32 | }
33 |
34 | fun getSitePalette(): SitePalette {
35 | return SitePalettes.palette
36 | }
37 |
38 | @InitSilk
39 | fun initTheme(ctx: InitSilkContext) {
40 | ctx.theme.palettes.light.background = Color.rgb(0xFAF8FF)
41 | ctx.theme.palettes.light.color = Colors.White
42 | }
43 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/common/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.common
2 |
3 | object Constants {
4 | const val BASE_URL = "https://api.canerture.com/androidhub/"
5 |
6 | const val QUERY_PARAM = "query"
7 | const val CATEGORY_PARAM = "category"
8 | const val POST_SHORT_PARAM = "short"
9 | const val POST_AUTHOR_ID_PARAM = "authorId"
10 |
11 | const val SIDE_PANEL_WIDTH = 250
12 | const val COLLAPSED_PANEL_HEIGHT = 100
13 | }
14 |
15 | object Res {
16 | object Image {
17 | const val LOGO = "/androidhub-logo.webp"
18 | const val LOGO_DARK = "/androidhub-logo-dark.webp"
19 | const val ANDROID_FIGURE = "/android-figure.webp"
20 | const val JETPACK = "/jetpack.webp"
21 | }
22 |
23 | object Icon {
24 | const val BOLD = "/bold.png"
25 | const val ITALIC = "/italic.png"
26 | const val LINK = "/link.png"
27 | const val TITLE = "/title-subtitle.png"
28 | const val SUBTITLE = "/title-subtitle.png"
29 | const val QUOTE = "/quote.png"
30 | const val CODE = "/code.png"
31 | const val IMAGE = "/image.png"
32 | }
33 |
34 | object PathIcon {
35 | const val CREATE =
36 | "M12 9.52148V12.5215M12 12.5215V15.5215M12 12.5215H15M12 12.5215H9M21 12.5215C21 13.7034 20.7672 14.8737 20.3149 15.9656C19.8626 17.0576 19.1997 18.0497 18.364 18.8854C17.5282 19.7212 16.5361 20.3841 15.4442 20.8364C14.3522 21.2887 13.1819 21.5215 12 21.5215C10.8181 21.5215 9.64778 21.2887 8.55585 20.8364C7.46392 20.3841 6.47177 19.7212 5.63604 18.8854C4.80031 18.0497 4.13738 17.0576 3.68508 15.9656C3.23279 14.8737 3 13.7034 3 12.5215C3 10.1345 3.94821 7.84535 5.63604 6.15752C7.32387 4.4697 9.61305 3.52148 12 3.52148C14.3869 3.52148 16.6761 4.4697 18.364 6.15752C20.0518 7.84535 21 10.1345 21 12.5215Z"
37 | const val POSTS =
38 | "M9 5H7C6.46957 5 5.96086 5.21071 5.58579 5.58579C5.21071 5.96086 5 6.46957 5 7V19C5 19.5304 5.21071 20.0391 5.58579 20.4142C5.96086 20.7893 6.46957 21 7 21H17C17.5304 21 18.0391 20.7893 18.4142 20.4142C18.7893 20.0391 19 19.5304 19 19V7C19 6.46957 18.7893 5.96086 18.4142 5.58579C18.0391 5.21071 17.5304 5 17 5H15M9 5C9 5.53043 9.21071 6.03914 9.58579 6.41421C9.96086 6.78929 10.4696 7 11 7H13C13.5304 7 14.0391 6.78929 14.4142 6.41421C14.7893 6.03914 15 5.53043 15 5M9 5C9 4.46957 9.21071 3.96086 9.58579 3.58579C9.96086 3.21071 10.4696 3 11 3H13C13.5304 3 14.0391 3.21071 14.4142 3.58579C14.7893 3.96086 15 4.46957 15 5M12 12H15M12 16H15M9 12H9.01M9 16H9.01"
39 | const val LOGOUT =
40 | "M11 16.5215L7 12.5215M7 12.5215L11 8.52148M7 12.5215H21M16 16.5215V17.5215C16 18.3171 15.6839 19.0802 15.1213 19.6428C14.5587 20.2054 13.7956 20.5215 13 20.5215H6C5.20435 20.5215 4.44129 20.2054 3.87868 19.6428C3.31607 19.0802 3 18.3171 3 17.5215V7.52148C3 6.72583 3.31607 5.96277 3.87868 5.40016C4.44129 4.83755 5.20435 4.52148 6 4.52148H13C13.7956 4.52148 14.5587 4.83755 15.1213 5.40016C15.6839 5.96277 16 6.72583 16 7.52148V8.52148"
41 | }
42 | }
43 |
44 | object Id {
45 | const val NAME_INPUT = "nameInput"
46 | const val EMAIL_INPUT = "emailInput"
47 | const val PASSWORD_INPUT = "passwordInput"
48 | const val SVG_PARENT = "svgParent"
49 | const val VECTOR_ICON = "vectorIcon"
50 | const val NAVIGATION_TEXT = "navigationText"
51 | const val EDITOR = "editor"
52 | const val EDITOR_PREVIEW = "editorPreview"
53 | const val TITLE_INPUT = "titleInput"
54 | const val THUMBNAIL_INPUT = "thumbnailInput"
55 | const val LINK_HREF_INPUT = "linkHrefInput"
56 | const val LINK_TITLE_INPUT = "linkTitleInput"
57 | const val ADMIN_SEARCH_BAR = "adminSearchBar"
58 | const val POST_CONTENT = "postContent"
59 | const val NAVBAR_SEARCH_INPUT = "navbarSearchInput"
60 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/common/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.common
2 |
3 | import com.varabyte.kobweb.compose.ui.Modifier
4 | import com.varabyte.kobweb.compose.ui.graphics.Colors
5 | import com.varabyte.kobweb.compose.ui.modifiers.border
6 | import com.varabyte.kobweb.compose.ui.modifiers.outline
7 | import org.jetbrains.compose.web.css.LineStyle
8 | import org.jetbrains.compose.web.css.px
9 | import kotlin.js.Date
10 |
11 | fun Modifier.noBorder(): Modifier {
12 | return this.border(
13 | width = 0.px,
14 | style = LineStyle.None,
15 | color = Colors.Transparent
16 | ).outline(
17 | width = 0.px,
18 | style = LineStyle.None,
19 | color = Colors.Transparent
20 | )
21 | }
22 |
23 | fun Long.parseDateString() = Date(this).toLocaleDateString()
24 |
25 | fun String.formatShort() = replace(" ", "-").lowercase()
26 | .replace("ğ", "g")
27 | .replace("ü", "u")
28 | .replace("ş", "s")
29 | .replace("ı", "i")
30 | .replace("ö", "o")
31 | .replace("ç", "c")
32 | .replace("'", "")
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/components/layouts/AdminPageLayout.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.layouts
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.runtime.setValue
8 | import com.canerture.androidhub.components.sections.AdminNavigationItems
9 | import com.canerture.androidhub.components.sections.OverflowSidePanel
10 | import com.canerture.androidhub.components.sections.SidePanel
11 | import com.varabyte.kobweb.compose.foundation.layout.Box
12 | import com.varabyte.kobweb.compose.foundation.layout.Column
13 | import com.varabyte.kobweb.compose.ui.Alignment
14 | import com.varabyte.kobweb.compose.ui.Modifier
15 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
16 |
17 | @Composable
18 | fun AdminPageLayout(content: @Composable () -> Unit) {
19 | var overflowOpened by remember { mutableStateOf(false) }
20 | Box(
21 | modifier = Modifier.fillMaxSize(),
22 | contentAlignment = Alignment.Center
23 | ) {
24 | Column(
25 | modifier = Modifier
26 | .fillMaxSize()
27 | ) {
28 | SidePanel(
29 | onMenuClick = { overflowOpened = true }
30 | )
31 | if (overflowOpened) {
32 | OverflowSidePanel(
33 | onMenuClose = {
34 | overflowOpened = false
35 | },
36 | content = {
37 | AdminNavigationItems()
38 | }
39 | )
40 | }
41 | content()
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/components/sections/Footer.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.sections
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.ShadowedGreenVariant
5 | import com.canerture.androidhub.common.noBorder
6 | import com.canerture.androidhub.data.isUserLoggedIn
7 | import com.canerture.androidhub.getSitePalette
8 | import com.canerture.androidhub.navigation.Screen
9 | import com.varabyte.kobweb.compose.css.Cursor
10 | import com.varabyte.kobweb.compose.css.FontWeight
11 | import com.varabyte.kobweb.compose.css.TextAlign
12 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement
13 | import com.varabyte.kobweb.compose.foundation.layout.Box
14 | import com.varabyte.kobweb.compose.foundation.layout.Column
15 | import com.varabyte.kobweb.compose.ui.Alignment
16 | import com.varabyte.kobweb.compose.ui.Modifier
17 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
18 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
19 | import com.varabyte.kobweb.compose.ui.modifiers.color
20 | import com.varabyte.kobweb.compose.ui.modifiers.cursor
21 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
22 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
23 | import com.varabyte.kobweb.compose.ui.modifiers.margin
24 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
25 | import com.varabyte.kobweb.compose.ui.modifiers.padding
26 | import com.varabyte.kobweb.compose.ui.modifiers.setVariable
27 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
28 | import com.varabyte.kobweb.compose.ui.modifiers.width
29 | import com.varabyte.kobweb.compose.ui.toAttrs
30 | import com.varabyte.kobweb.core.rememberPageContext
31 | import com.varabyte.kobweb.silk.components.forms.ButtonVars
32 | import com.varabyte.kobweb.silk.components.navigation.Link
33 | import com.varabyte.kobweb.silk.components.style.ComponentStyle
34 | import com.varabyte.kobweb.silk.components.style.base
35 | import com.varabyte.kobweb.silk.components.style.toModifier
36 | import com.varabyte.kobweb.silk.components.text.SpanText
37 | import org.jetbrains.compose.web.css.cssRem
38 | import org.jetbrains.compose.web.css.percent
39 | import org.jetbrains.compose.web.css.px
40 | import org.jetbrains.compose.web.dom.Button
41 | import org.jetbrains.compose.web.dom.Span
42 |
43 | val FooterStyle by ComponentStyle.base {
44 | Modifier.padding(topBottom = 5.cssRem, leftRight = 10.percent)
45 | }
46 |
47 | @Composable
48 | fun Footer(modifier: Modifier = Modifier) {
49 | val context = rememberPageContext()
50 | val isUserLoggedIn = isUserLoggedIn()
51 | Column(
52 | modifier = FooterStyle.toModifier().then(modifier),
53 | verticalArrangement = Arrangement.Center,
54 | horizontalAlignment = Alignment.CenterHorizontally
55 | ) {
56 | Button(
57 | attrs = ShadowedGreenVariant.toModifier()
58 | .backgroundColor(getSitePalette().green)
59 | .setVariable(ButtonVars.BackgroundDefaultColor, getSitePalette().green)
60 | .setVariable(ButtonVars.BackgroundHoverColor, getSitePalette().green)
61 | .setVariable(ButtonVars.BackgroundPressedColor, getSitePalette().green)
62 | .width(120.px)
63 | .borderRadius(3.cssRem)
64 | .padding(leftRight = 1.cssRem, topBottom = 0.5.cssRem)
65 | .margin(bottom = 1.cssRem)
66 | .noBorder()
67 | .cursor(Cursor.Pointer)
68 | .onClick {
69 | if (isUserLoggedIn) {
70 | context.router.navigateTo(Screen.AdminMyPosts.route)
71 | } else {
72 | context.router.navigateTo(Screen.Login.route)
73 | }
74 | }
75 | .toAttrs(),
76 | ) {
77 | SpanText(
78 | text = "Join us",
79 | modifier = Modifier
80 | .fontSize(16.px)
81 | .color(getSitePalette().white)
82 | )
83 | }
84 |
85 | Span(Modifier.textAlign(TextAlign.Center).toAttrs()) {
86 | Link("https://bento.me/canerture") {
87 | SpanText(
88 | text = "2024 - Caner Ture",
89 | modifier = Modifier
90 | .color(getSitePalette().green)
91 | .fontWeight(FontWeight.Bold)
92 | )
93 | }
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/CategoryChip.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.data.model.Category
5 | import com.canerture.androidhub.data.model.colorParse
6 | import com.canerture.androidhub.getSitePalette
7 | import com.varabyte.kobweb.compose.foundation.layout.Box
8 | import com.varabyte.kobweb.compose.ui.Alignment
9 | import com.varabyte.kobweb.compose.ui.Modifier
10 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
11 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
12 | import com.varabyte.kobweb.compose.ui.modifiers.color
13 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
14 | import com.varabyte.kobweb.compose.ui.modifiers.height
15 | import com.varabyte.kobweb.compose.ui.modifiers.padding
16 | import com.varabyte.kobweb.silk.components.text.SpanText
17 | import org.jetbrains.compose.web.css.px
18 |
19 | @Composable
20 | fun CategoryChip(category: Category) {
21 | Box(
22 | modifier = Modifier
23 | .height(32.px)
24 | .padding(leftRight = 14.px)
25 | .borderRadius(r = 100.px)
26 | .backgroundColor(colorParse(category.color)),
27 | contentAlignment = Alignment.Center
28 | ) {
29 | SpanText(
30 | modifier = Modifier
31 | .fontSize(12.px)
32 | .color(getSitePalette().white),
33 | text = category.name
34 | )
35 | }
36 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/ErrorView.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.getSitePalette
5 | import com.varabyte.kobweb.compose.css.FontWeight
6 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement
7 | import com.varabyte.kobweb.compose.foundation.layout.Column
8 | import com.varabyte.kobweb.compose.ui.Alignment
9 | import com.varabyte.kobweb.compose.ui.Modifier
10 | import com.varabyte.kobweb.compose.ui.modifiers.color
11 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
12 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
13 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
14 | import com.varabyte.kobweb.compose.ui.modifiers.padding
15 | import com.varabyte.kobweb.silk.components.icons.fa.FaX
16 | import com.varabyte.kobweb.silk.components.icons.fa.IconSize
17 | import com.varabyte.kobweb.silk.components.text.SpanText
18 | import org.jetbrains.compose.web.css.px
19 |
20 | @Composable
21 | fun ErrorView(
22 | message: String = ""
23 | ) {
24 | Column(
25 | modifier = Modifier
26 | .fillMaxSize(),
27 | horizontalAlignment = Alignment.CenterHorizontally,
28 | verticalArrangement = Arrangement.Center
29 | ) {
30 | FaX(
31 | size = IconSize.X6,
32 | modifier = Modifier
33 | .padding(bottom = 48.px)
34 | .color(getSitePalette().blue)
35 | )
36 |
37 | SpanText(
38 | text = message.ifEmpty { "An error occurred while loading the page." },
39 | modifier = Modifier
40 | .color(getSitePalette().blue)
41 | .fontSize(24.px)
42 | .fontWeight(FontWeight.Bold)
43 | )
44 | }
45 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/LoadingIndicator.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.LoaderStyle
5 | import com.varabyte.kobweb.compose.foundation.layout.Box
6 | import com.varabyte.kobweb.compose.ui.Alignment
7 | import com.varabyte.kobweb.compose.ui.Modifier
8 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
9 | import com.varabyte.kobweb.compose.ui.modifiers.height
10 | import com.varabyte.kobweb.compose.ui.modifiers.padding
11 | import com.varabyte.kobweb.silk.components.style.toAttrs
12 | import org.jetbrains.compose.web.css.px
13 | import org.jetbrains.compose.web.css.vh
14 | import org.jetbrains.compose.web.dom.Div
15 |
16 | @Composable
17 | fun LoadingIndicator() {
18 | Box(
19 | modifier = Modifier
20 | .fillMaxSize()
21 | .height(100.vh)
22 | .padding(topBottom = 50.px),
23 | contentAlignment = Alignment.Center
24 | ) {
25 | Div(
26 | attrs = LoaderStyle.toAttrs()
27 | )
28 | }
29 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/Popup.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.common.Id
5 | import com.canerture.androidhub.common.noBorder
6 | import com.canerture.androidhub.getSitePalette
7 | import com.canerture.androidhub.models.EditorControl
8 | import com.varabyte.kobweb.compose.foundation.layout.Box
9 | import com.varabyte.kobweb.compose.foundation.layout.Column
10 | import com.varabyte.kobweb.compose.ui.Alignment
11 | import com.varabyte.kobweb.compose.ui.Modifier
12 | import com.varabyte.kobweb.compose.ui.graphics.Colors
13 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
14 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
15 | import com.varabyte.kobweb.compose.ui.modifiers.color
16 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
17 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
18 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
19 | import com.varabyte.kobweb.compose.ui.modifiers.height
20 | import com.varabyte.kobweb.compose.ui.modifiers.id
21 | import com.varabyte.kobweb.compose.ui.modifiers.margin
22 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
23 | import com.varabyte.kobweb.compose.ui.modifiers.padding
24 | import com.varabyte.kobweb.compose.ui.modifiers.position
25 | import com.varabyte.kobweb.compose.ui.modifiers.width
26 | import com.varabyte.kobweb.compose.ui.modifiers.zIndex
27 | import com.varabyte.kobweb.compose.ui.toAttrs
28 | import com.varabyte.kobweb.silk.components.text.SpanText
29 | import kotlinx.browser.document
30 | import org.jetbrains.compose.web.attributes.InputType
31 | import org.jetbrains.compose.web.css.Position
32 | import org.jetbrains.compose.web.css.px
33 | import org.jetbrains.compose.web.css.vh
34 | import org.jetbrains.compose.web.css.vw
35 | import org.jetbrains.compose.web.dom.Button
36 | import org.jetbrains.compose.web.dom.Input
37 | import org.w3c.dom.HTMLInputElement
38 |
39 | @Composable
40 | fun ControlPopup(
41 | editorControl: EditorControl,
42 | onDialogDismiss: () -> Unit,
43 | onAddClick: (String, String) -> Unit
44 | ) {
45 | Box(
46 | modifier = Modifier
47 | .width(100.vw)
48 | .height(100.vh)
49 | .position(Position.Fixed)
50 | .zIndex(19),
51 | contentAlignment = Alignment.Center
52 | ) {
53 | Box(
54 | modifier = Modifier
55 | .fillMaxSize()
56 | .backgroundColor(getSitePalette().blue)
57 | .onClick { onDialogDismiss() }
58 | )
59 | Column(
60 | modifier = Modifier
61 | .width(500.px)
62 | .padding(all = 24.px)
63 | .backgroundColor(Colors.White)
64 | .borderRadius(r = 4.px)
65 | ) {
66 | Input(
67 | type = InputType.Text,
68 | attrs = Modifier
69 | .id(Id.LINK_HREF_INPUT)
70 | .fillMaxWidth()
71 | .height(54.px)
72 | .padding(left = 20.px)
73 | .margin(bottom = 12.px)
74 | .fontSize(14.px)
75 | .noBorder()
76 | .borderRadius(r = 4.px)
77 | .backgroundColor(getSitePalette().white)
78 | .toAttrs {
79 | attr(
80 | "placeholder",
81 | if (editorControl == EditorControl.Link) "Href" else "Image URL"
82 | )
83 | }
84 | )
85 | Input(
86 | type = InputType.Text,
87 | attrs = Modifier
88 | .id(Id.LINK_TITLE_INPUT)
89 | .fillMaxWidth()
90 | .height(54.px)
91 | .padding(left = 20.px)
92 | .margin(bottom = 20.px)
93 | .fontSize(14.px)
94 | .noBorder()
95 | .borderRadius(r = 4.px)
96 | .backgroundColor(getSitePalette().white)
97 | .toAttrs {
98 | attr(
99 | "placeholder",
100 | if (editorControl == EditorControl.Link) "Title" else "Description"
101 | )
102 | }
103 | )
104 | Button(
105 | attrs = Modifier
106 | .onClick {
107 | val href =
108 | (document.getElementById(Id.LINK_HREF_INPUT) as HTMLInputElement).value
109 | val title =
110 | (document.getElementById(Id.LINK_TITLE_INPUT) as HTMLInputElement).value
111 | onAddClick(href, title)
112 | onDialogDismiss()
113 | }
114 | .fillMaxWidth()
115 | .height(54.px)
116 | .borderRadius(r = 4.px)
117 | .backgroundColor(getSitePalette().blue)
118 | .color(Colors.White)
119 | .noBorder()
120 | .fontSize(14.px)
121 | .toAttrs()
122 | ) {
123 | SpanText(text = "Add")
124 | }
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/PostPreview.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.ShadowedGrayVariant
5 | import com.canerture.androidhub.common.parseDateString
6 | import com.canerture.androidhub.data.model.Post
7 | import com.canerture.androidhub.getSitePalette
8 | import com.varabyte.kobweb.compose.css.CSSTransition
9 | import com.varabyte.kobweb.compose.css.Cursor
10 | import com.varabyte.kobweb.compose.css.FontWeight
11 | import com.varabyte.kobweb.compose.css.Overflow
12 | import com.varabyte.kobweb.compose.css.TextOverflow
13 | import com.varabyte.kobweb.compose.css.TransitionProperty
14 | import com.varabyte.kobweb.compose.foundation.layout.Column
15 | import com.varabyte.kobweb.compose.ui.Modifier
16 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
17 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
18 | import com.varabyte.kobweb.compose.ui.modifiers.color
19 | import com.varabyte.kobweb.compose.ui.modifiers.cursor
20 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
21 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
22 | import com.varabyte.kobweb.compose.ui.modifiers.margin
23 | import com.varabyte.kobweb.compose.ui.modifiers.marginBlock
24 | import com.varabyte.kobweb.compose.ui.modifiers.marginInline
25 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
26 | import com.varabyte.kobweb.compose.ui.modifiers.overflow
27 | import com.varabyte.kobweb.compose.ui.modifiers.padding
28 | import com.varabyte.kobweb.compose.ui.modifiers.textOverflow
29 | import com.varabyte.kobweb.compose.ui.modifiers.transition
30 | import com.varabyte.kobweb.silk.components.style.toModifier
31 | import com.varabyte.kobweb.silk.components.text.SpanText
32 | import org.jetbrains.compose.web.css.cssRem
33 | import org.jetbrains.compose.web.css.ms
34 | import org.jetbrains.compose.web.css.px
35 |
36 | @Composable
37 | fun PostPreview(
38 | post: Post,
39 | onClick: (String) -> Unit
40 | ) {
41 | Column(
42 | modifier = ShadowedGrayVariant.toModifier()
43 | .marginInline(end = 1.5.cssRem)
44 | .marginBlock(end = 1.5.cssRem)
45 | .backgroundColor(getSitePalette().white)
46 | .borderRadius(r = 10.px)
47 | .padding(all = 18.px)
48 | .transition(CSSTransition(property = TransitionProperty.All, duration = 400.ms))
49 | .cursor(Cursor.Pointer)
50 | .onClick { onClick(post.short) }
51 | ) {
52 | SpanText(
53 | modifier = Modifier
54 | .fontSize(12.px)
55 | .color(getSitePalette().blue),
56 | text = post.date.toLong().parseDateString()
57 | )
58 | SpanText(
59 | modifier = Modifier
60 | .weight(1f)
61 | .margin(topBottom = 12.px)
62 | .fontSize(20.px)
63 | .fontWeight(FontWeight.Bold)
64 | .color(getSitePalette().blue)
65 | .textOverflow(TextOverflow.Ellipsis)
66 | .overflow(Overflow.Hidden),
67 | text = post.title
68 | )
69 | CategoryChip(category = post.category)
70 | }
71 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/PostsView.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.data.model.Post
5 | import com.varabyte.kobweb.compose.ui.Modifier
6 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
7 | import com.varabyte.kobweb.compose.ui.modifiers.margin
8 | import com.varabyte.kobweb.silk.components.layout.SimpleGrid
9 | import com.varabyte.kobweb.silk.components.layout.numColumns
10 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
11 | import com.varabyte.kobweb.silk.components.style.breakpoint.ResponsiveValues
12 | import org.jetbrains.compose.web.css.percent
13 | import org.jetbrains.compose.web.css.px
14 |
15 | @Composable
16 | fun PostsView(
17 | breakpoint: Breakpoint,
18 | posts: List,
19 | numColumns: ResponsiveValues = numColumns(base = 1, sm = 2, md = 3, lg = 3),
20 | onClick: (String) -> Unit
21 | ) {
22 | SimpleGrid(
23 | modifier = Modifier
24 | .fillMaxWidth(
25 | if (breakpoint > Breakpoint.MD) 80.percent
26 | else 90.percent
27 | )
28 | .margin(top = 40.px),
29 | numColumns = numColumns
30 | ) {
31 | posts.forEach {
32 | PostPreview(
33 | post = it,
34 | onClick = onClick
35 | )
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/SearchBar.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.common.Id
10 | import com.canerture.androidhub.common.noBorder
11 | import com.canerture.androidhub.getSitePalette
12 | import com.varabyte.kobweb.compose.css.CSSTransition
13 | import com.varabyte.kobweb.compose.css.Cursor
14 | import com.varabyte.kobweb.compose.foundation.layout.Row
15 | import com.varabyte.kobweb.compose.ui.Alignment
16 | import com.varabyte.kobweb.compose.ui.Modifier
17 | import com.varabyte.kobweb.compose.ui.graphics.Colors
18 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
19 | import com.varabyte.kobweb.compose.ui.modifiers.border
20 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
21 | import com.varabyte.kobweb.compose.ui.modifiers.color
22 | import com.varabyte.kobweb.compose.ui.modifiers.cursor
23 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
24 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
25 | import com.varabyte.kobweb.compose.ui.modifiers.height
26 | import com.varabyte.kobweb.compose.ui.modifiers.id
27 | import com.varabyte.kobweb.compose.ui.modifiers.margin
28 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
29 | import com.varabyte.kobweb.compose.ui.modifiers.onFocusIn
30 | import com.varabyte.kobweb.compose.ui.modifiers.onFocusOut
31 | import com.varabyte.kobweb.compose.ui.modifiers.onKeyDown
32 | import com.varabyte.kobweb.compose.ui.modifiers.padding
33 | import com.varabyte.kobweb.compose.ui.modifiers.transition
34 | import com.varabyte.kobweb.compose.ui.toAttrs
35 | import com.varabyte.kobweb.silk.components.icons.fa.FaMagnifyingGlass
36 | import com.varabyte.kobweb.silk.components.icons.fa.IconSize
37 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
38 | import org.jetbrains.compose.web.attributes.InputType
39 | import org.jetbrains.compose.web.css.LineStyle
40 | import org.jetbrains.compose.web.css.ms
41 | import org.jetbrains.compose.web.css.px
42 | import org.jetbrains.compose.web.dom.Input
43 |
44 | @Composable
45 | fun SearchBar(
46 | breakpoint: Breakpoint,
47 | modifier: Modifier = Modifier,
48 | onEnterClick: () -> Unit,
49 | onSearchIconClick: (Boolean) -> Unit
50 | ) {
51 | var focused by remember { mutableStateOf(false) }
52 |
53 | LaunchedEffect(breakpoint) {
54 | if (breakpoint >= Breakpoint.SM) onSearchIconClick(false)
55 | }
56 |
57 | if (breakpoint >= Breakpoint.SM) {
58 | Row(
59 | modifier = modifier
60 | .fillMaxWidth()
61 | .padding(left = 20.px)
62 | .height(54.px)
63 | .backgroundColor(getSitePalette().white)
64 | .borderRadius(r = 100.px)
65 | .border(
66 | width = 2.px,
67 | style = LineStyle.Solid,
68 | color = if (focused) getSitePalette().green else getSitePalette().blue
69 | )
70 | .transition(CSSTransition(property = "border", duration = 200.ms)),
71 | verticalAlignment = Alignment.CenterVertically
72 | ) {
73 | FaMagnifyingGlass(
74 | modifier = Modifier
75 | .margin(right = 14.px)
76 | .color(if (focused) getSitePalette().green else getSitePalette().blue)
77 | .transition(CSSTransition(property = "color", duration = 200.ms)),
78 | size = IconSize.SM
79 | )
80 | Input(
81 | type = InputType.Text,
82 | attrs = Modifier
83 | .id(Id.ADMIN_SEARCH_BAR)
84 | .fillMaxSize()
85 | .color(getSitePalette().blue)
86 | .backgroundColor(Colors.Transparent)
87 | .noBorder()
88 | .onFocusIn { focused = true }
89 | .onFocusOut { focused = false }
90 | .onKeyDown {
91 | if (it.key == "Enter") {
92 | onEnterClick()
93 | }
94 | }
95 | .toAttrs {
96 | attr("placeholder", "Search...")
97 | }
98 | )
99 | }
100 | } else {
101 | FaMagnifyingGlass(
102 | modifier = Modifier
103 | .margin(right = 14.px)
104 | .color(getSitePalette().green)
105 | .cursor(Cursor.Pointer)
106 | .onClick { onSearchIconClick(true) },
107 | size = IconSize.SM
108 | )
109 | }
110 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/CategoryApis.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data
2 |
3 | import com.canerture.androidhub.data.model.Category
4 | import com.canerture.androidhub.utils.ApiUtils.get
5 | import com.canerture.androidhub.utils.ApiUtils.safeApiCall
6 |
7 | suspend fun getCategories(
8 | onSuccess: (List) -> Unit = {},
9 | onError: (String) -> Unit = {}
10 | ) = safeApiCall>(
11 | call = { get("categories") },
12 | onSuccess = onSuccess,
13 | onError = onError
14 | )
15 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/PostApis.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data
2 |
3 | import com.canerture.androidhub.common.formatShort
4 | import com.canerture.androidhub.data.model.AddPostRequest
5 | import com.canerture.androidhub.data.model.AuthorDetail
6 | import com.canerture.androidhub.data.model.GetPostsResponse
7 | import com.canerture.androidhub.data.model.Post
8 | import com.canerture.androidhub.data.model.UpdatePostRequest
9 | import com.canerture.androidhub.utils.ApiUtils.delete
10 | import com.canerture.androidhub.utils.ApiUtils.get
11 | import com.canerture.androidhub.utils.ApiUtils.post
12 | import com.canerture.androidhub.utils.ApiUtils.safeApiCall
13 | import kotlinx.browser.localStorage
14 | import kotlinx.serialization.encodeToString
15 | import kotlinx.serialization.json.Json
16 | import org.w3c.dom.get
17 | import kotlin.js.Date
18 |
19 | suspend fun getAdminAllPosts(
20 | onSuccess: (List) -> Unit = {},
21 | onError: (String) -> Unit = {}
22 | ) = safeApiCall>(
23 | call = { get("admin_all_posts") },
24 | onSuccess = onSuccess,
25 | onError = onError
26 | )
27 |
28 | suspend fun getPosts(
29 | page: Int = 0,
30 | onSuccess: (GetPostsResponse) -> Unit = {},
31 | onError: (String) -> Unit = {}
32 | ) = safeApiCall(
33 | call = { get("posts", "page" to page) },
34 | onSuccess = onSuccess,
35 | onError = onError
36 | )
37 |
38 | suspend fun getPopularPosts(
39 | onSuccess: (List) -> Unit = {},
40 | onError: (String) -> Unit = {}
41 | ) = safeApiCall>(
42 | call = { get("posts", "popular" to "") },
43 | onSuccess = onSuccess,
44 | onError = onError
45 | )
46 |
47 | suspend fun getPostDetail(
48 | short: String,
49 | onSuccess: (Post) -> Unit = {},
50 | onError: (String) -> Unit = {}
51 | ) = safeApiCall(
52 | call = { get("posts", "short" to short) },
53 | onSuccess = onSuccess,
54 | onError = onError
55 | )
56 |
57 | suspend fun getPendingPosts(
58 | onSuccess: (List) -> Unit = {},
59 | onError: (String) -> Unit = {}
60 | ) = safeApiCall>(
61 | call = { get("pending_posts") },
62 | onSuccess = onSuccess,
63 | onError = onError
64 | )
65 |
66 | suspend fun getDraftPosts(
67 | onSuccess: (List) -> Unit = {},
68 | onError: (String) -> Unit = {}
69 | ) = safeApiCall>(
70 | call = { get("draft_posts") },
71 | onSuccess = onSuccess,
72 | onError = onError
73 | )
74 |
75 | suspend fun addPost(
76 | content: String,
77 | title: String,
78 | category: String,
79 | thumbnail: String,
80 | status: String,
81 | onSuccess: (String) -> Unit = {},
82 | onError: (String) -> Unit = {}
83 | ) = safeApiCall(
84 | call = {
85 | val id = localStorage["userId"] ?: ""
86 | val name = localStorage["name"] ?: ""
87 | val short = title.formatShort()
88 | post(
89 | path = "posts",
90 | body = Json.encodeToString(
91 | AddPostRequest(id, name, Date.now(), content, title, short, category, thumbnail, status)
92 | ).encodeToByteArray()
93 | )
94 | },
95 | onSuccess = onSuccess,
96 | onError = onError
97 | )
98 |
99 | suspend fun updatePost(
100 | id: Int,
101 | content: String,
102 | title: String,
103 | category: String,
104 | thumbnail: String,
105 | status: String,
106 | onSuccess: (String) -> Unit = {},
107 | onError: (String) -> Unit = {}
108 | ) = safeApiCall(
109 | call = {
110 | val short = title.formatShort()
111 | post(
112 | path = "posts",
113 | body = Json.encodeToString(
114 | UpdatePostRequest(id, Date.now(), content, title, short, category, thumbnail, status)
115 | ).encodeToByteArray()
116 | )
117 | },
118 | onSuccess = onSuccess,
119 | onError = onError
120 | )
121 |
122 | suspend fun searchPostsByTitle(
123 | title: String,
124 | onSuccess: (List) -> Unit = {},
125 | onError: (String) -> Unit = {}
126 | ) = safeApiCall>(
127 | call = { get("posts", "title" to title) },
128 | onSuccess = onSuccess,
129 | onError = onError
130 | )
131 |
132 | suspend fun getMyPosts(
133 | onSuccess: (AuthorDetail) -> Unit = {},
134 | onError: (String) -> Unit = {}
135 | ) = safeApiCall(
136 | call = { get("posts", "authorId" to localStorage["userId"].orEmpty()) },
137 | onSuccess = onSuccess,
138 | onError = onError
139 | )
140 |
141 | suspend fun deletePost(
142 | id: Int,
143 | onSuccess: (String) -> Unit = {},
144 | onError: (String) -> Unit = {}
145 | ) = safeApiCall(
146 | call = { delete("posts", "id" to id) },
147 | onSuccess = onSuccess,
148 | onError = onError
149 | )
150 |
151 | suspend fun getPostsByCategory(
152 | category: String,
153 | onSuccess: (List) -> Unit = {},
154 | onError: (String) -> Unit = {}
155 | ) = safeApiCall>(
156 | call = { get("posts", "category" to category) },
157 | onSuccess = onSuccess,
158 | onError = onError
159 | )
160 |
161 | suspend fun getPostsByAuthorId(
162 | authorId: String,
163 | onSuccess: (AuthorDetail) -> Unit = {},
164 | onError: (String) -> Unit = {}
165 | ) = safeApiCall(
166 | call = { get("posts", "authorId" to authorId) },
167 | onSuccess = onSuccess,
168 | onError = onError
169 | )
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/UserApis.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.data.model.LoginRequest
10 | import com.canerture.androidhub.data.model.RegisterRequest
11 | import com.canerture.androidhub.data.model.User
12 | import com.canerture.androidhub.utils.ApiUtils.get
13 | import com.canerture.androidhub.utils.ApiUtils.post
14 | import com.canerture.androidhub.utils.ApiUtils.safeApiCall
15 | import kotlinx.browser.localStorage
16 | import kotlinx.serialization.encodeToString
17 | import kotlinx.serialization.json.Json
18 | import org.w3c.dom.get
19 | import org.w3c.dom.set
20 |
21 | suspend fun login(
22 | email: String,
23 | password: String,
24 | onSuccess: (User) -> Unit = {},
25 | onError: (String) -> Unit = {}
26 | ) = safeApiCall(
27 | call = {
28 | post(
29 | path = "login",
30 | body = Json.encodeToString(LoginRequest(email, password)).encodeToByteArray()
31 | )
32 | },
33 | onSuccess = onSuccess,
34 | onError = onError
35 | )
36 |
37 | suspend fun register(
38 | name: String,
39 | email: String,
40 | password: String,
41 | onSuccess: (User) -> Unit = {},
42 | onError: (String) -> Unit = {}
43 | ) = safeApiCall(
44 | call = {
45 | post(
46 | path = "register",
47 | body = Json.encodeToString(RegisterRequest(name, email, password)).encodeToByteArray()
48 | )
49 | },
50 | onSuccess = onSuccess,
51 | onError = onError
52 | )
53 |
54 | suspend fun checkUserId(
55 | userId: String,
56 | onSuccess: (Boolean) -> Unit = {},
57 | onError: (String) -> Unit = {}
58 | ) = safeApiCall(
59 | call = { get("check_user_id", "userId" to userId) },
60 | onSuccess = onSuccess,
61 | onError = onError
62 | )
63 |
64 | @Composable
65 | fun isUserLoggedIn(): Boolean {
66 | val remembered = remember { localStorage["remember"].toBoolean() }
67 | val userId = remember { localStorage["userId"] }
68 | var userIdExists by remember { mutableStateOf(null) }
69 | LaunchedEffect(key1 = Unit) {
70 | if (!userId.isNullOrEmpty()) {
71 | checkUserId(
72 | userId = userId,
73 | onSuccess = {
74 | userIdExists = it
75 | },
76 | onError = {
77 | userIdExists = null
78 | }
79 | )
80 | }
81 | }
82 |
83 | return remembered && userIdExists ?: false
84 | }
85 |
86 | fun logout() {
87 | localStorage["remember"] = "false"
88 | localStorage["userId"] = ""
89 | localStorage["username"] = ""
90 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/model/AuthorDetail.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class AuthorDetail(
7 | val authorName: String,
8 | val posts: List,
9 | )
10 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/model/BaseResponse.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class BaseResponse(
7 | val status: Int? = null,
8 | val message: String? = null,
9 | val data: T? = null
10 | ) {
11 | fun isSuccess() = status == 200
12 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/model/Category.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import com.github.ajalt.colormath.model.RGBInt
4 | import com.github.ajalt.colormath.parse
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class Category(
9 | val id: Int = 0,
10 | val name: String = "",
11 | val short: String = "",
12 | val color: String = "",
13 | val parentCategory: String = "",
14 | )
15 |
16 | data class ParentCategory(
17 | val id: Int = 0,
18 | val name: String = "",
19 | val short: String = "",
20 | val color: String = "",
21 | val subCategories: MutableList = mutableListOf(),
22 | )
23 |
24 | data class SubCategory(
25 | val id: Int = 0,
26 | val name: String = "",
27 | val short: String = "",
28 | val color: String = "",
29 | val parentCategory: String = "",
30 | )
31 |
32 | fun colorParse(color: String): com.varabyte.kobweb.compose.ui.graphics.Color {
33 | return com.github.ajalt.colormath.Color.parse("#$color").toComposeColor()
34 | }
35 |
36 | fun com.github.ajalt.colormath.Color.toComposeColor(): com.varabyte.kobweb.compose.ui.graphics.Color {
37 | return if (this is RGBInt) {
38 | com.varabyte.kobweb.compose.ui.graphics.Color.rgb(argb.toInt())
39 | } else {
40 | val (r, g, b, a) = toSRGB()
41 | com.varabyte.kobweb.compose.ui.graphics.Color.rgba(r, g, b, a)
42 | }
43 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/model/GetPostsResponse.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class GetPostsResponse(
7 | val totalPage: Int,
8 | val posts: List
9 | )
10 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/model/LoginRequest.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class LoginRequest(
7 | val email: String,
8 | val password: String,
9 | )
10 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/model/Post.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class AddPostRequest(
7 | val authorId: String = "",
8 | val authorName: String = "",
9 | val date: Double = 0.0,
10 | val content: String = "",
11 | val title: String = "",
12 | val short: String = "",
13 | val category: String = "",
14 | val thumbnail: String = "",
15 | val status: String = "",
16 | )
17 |
18 | @Serializable
19 | data class UpdatePostRequest(
20 | val id: Int = 0,
21 | val date: Double = 0.0,
22 | val content: String = "",
23 | val title: String = "",
24 | val short: String = "",
25 | val category: String = "",
26 | val thumbnail: String = "",
27 | val status: String = "",
28 | )
29 |
30 | @Serializable
31 | data class Post(
32 | val id: Int = 0,
33 | val authorId: String = "",
34 | val authorName: String = "",
35 | val date: Double = 0.0,
36 | val content: String = "",
37 | val title: String = "",
38 | val short: String = "",
39 | val category: Category = Category(),
40 | val thumbnail: String = "",
41 | val commentCount: Int = 0,
42 | val status: String = "",
43 | )
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/model/RegisterRequest.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class RegisterRequest(
7 | val name: String,
8 | val email: String,
9 | val password: String,
10 | )
11 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/data/model/User.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class User(
7 | val userId: String,
8 | val name: String,
9 | val email: String,
10 | val isAdmin: Boolean
11 | )
12 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/models/ControlStyle.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.models
2 |
3 | import com.canerture.androidhub.getSitePalette
4 |
5 | sealed class ControlStyle(val style: String) {
6 | data class Bold(val selectedText: String?) : ControlStyle(
7 | style = "$selectedText"
8 | )
9 |
10 | data class Italic(val selectedText: String?) : ControlStyle(
11 | style = "$selectedText"
12 | )
13 |
14 | data class Link(
15 | val selectedText: String?,
16 | val href: String,
17 | val title: String
18 | ) : ControlStyle(
19 | style = "$selectedText"
20 | )
21 |
22 | data class Title(val selectedText: String?) : ControlStyle(
23 | style = "$selectedText
"
24 | )
25 |
26 | data class Subtitle(val selectedText: String?) : ControlStyle(
27 | style = "$selectedText
"
28 | )
29 |
30 | data class Quote(val selectedText: String?) : ControlStyle(
31 | style = "❞ $selectedText
"
32 | )
33 |
34 | data class Code(val selectedText: String?) : ControlStyle(
35 | style = ""
36 | )
37 |
38 | data class Image(
39 | val selectedText: String?,
40 | val imageUrl: String,
41 | val alt: String
42 | ) : ControlStyle(
43 | style = "
$selectedText"
44 | )
45 |
46 | data class Break(val selectedText: String?) : ControlStyle(
47 | style = "$selectedText
"
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/models/EditorControl.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.models
2 |
3 | import com.canerture.androidhub.common.Res
4 |
5 | enum class EditorControl(
6 | val icon: String,
7 | ) {
8 | Bold(icon = Res.Icon.BOLD),
9 | Italic(icon = Res.Icon.ITALIC),
10 | Link(icon = Res.Icon.LINK),
11 | Title(icon = Res.Icon.TITLE),
12 | Subtitle(icon = Res.Icon.SUBTITLE),
13 | Quote(icon = Res.Icon.QUOTE),
14 | Code(icon = Res.Icon.CODE),
15 | Image(icon = Res.Icon.IMAGE)
16 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/navigation/Screen.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.navigation
2 |
3 | import com.canerture.androidhub.common.Constants.CATEGORY_PARAM
4 | import com.canerture.androidhub.common.Constants.POST_AUTHOR_ID_PARAM
5 | import com.canerture.androidhub.common.Constants.POST_SHORT_PARAM
6 | import com.canerture.androidhub.common.Constants.QUERY_PARAM
7 |
8 | sealed class Screen(val route: String) {
9 | data object HomePage : Screen(route = "/")
10 |
11 | data object PostPage : Screen(route = "/posts") {
12 | fun getPost(short: String) = "/posts?${POST_SHORT_PARAM}=$short"
13 | }
14 |
15 | data object AuthorPage : Screen(route = "/author") {
16 | fun getAuthor(authorId: String) = "/author?${POST_AUTHOR_ID_PARAM}=$authorId"
17 | }
18 |
19 | data object CategoryPage : Screen(route = "/category") {
20 | fun getCategoryPosts(category: String) = "/category?${CATEGORY_PARAM}=${category}"
21 | }
22 |
23 | data object Login : Screen(route = "/login")
24 |
25 | data object AdminCreate : Screen(route = "/admin/create-post") {
26 | fun passPostId(short: String) = "/admin/create-post?${POST_SHORT_PARAM}=$short"
27 | }
28 |
29 | data object AdminMyPosts : Screen(route = "/admin/my-posts")
30 |
31 | data object AdminAllPosts : Screen(route = "/admin/all-posts")
32 |
33 | data object SearchPage : Screen(route = "/search") {
34 | fun searchByTitle(query: String) = "/search?${QUERY_PARAM}=$query"
35 | }
36 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/pages/adminallposts/AdminAllPosts.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.adminallposts
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.common.Constants.SIDE_PANEL_WIDTH
10 | import com.canerture.androidhub.components.layouts.AdminPageLayout
11 | import com.canerture.androidhub.components.widgets.PostsView
12 | import com.canerture.androidhub.data.getAdminAllPosts
13 | import com.canerture.androidhub.data.getDraftPosts
14 | import com.canerture.androidhub.data.getPendingPosts
15 | import com.canerture.androidhub.data.model.Post
16 | import com.canerture.androidhub.getSitePalette
17 | import com.canerture.androidhub.navigation.Screen
18 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement
19 | import com.varabyte.kobweb.compose.foundation.layout.Column
20 | import com.varabyte.kobweb.compose.foundation.layout.Row
21 | import com.varabyte.kobweb.compose.ui.Alignment
22 | import com.varabyte.kobweb.compose.ui.Modifier
23 | import com.varabyte.kobweb.compose.ui.modifiers.color
24 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
25 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
26 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
27 | import com.varabyte.kobweb.compose.ui.modifiers.margin
28 | import com.varabyte.kobweb.compose.ui.modifiers.padding
29 | import com.varabyte.kobweb.core.Page
30 | import com.varabyte.kobweb.core.rememberPageContext
31 | import com.varabyte.kobweb.silk.components.forms.Switch
32 | import com.varabyte.kobweb.silk.components.forms.SwitchSize
33 | import com.varabyte.kobweb.silk.components.layout.numColumns
34 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
35 | import com.varabyte.kobweb.silk.components.text.SpanText
36 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
37 | import org.jetbrains.compose.web.css.percent
38 | import org.jetbrains.compose.web.css.px
39 |
40 | data class AllPostsUIState(
41 | var isLoading: Boolean = true,
42 | var posts: List = emptyList(),
43 | var pendingPostEnabled: Boolean = false,
44 | var draftPostEnabled: Boolean = false,
45 | )
46 |
47 | @Page("/admin/all-posts")
48 | @Composable
49 | fun AdminAllPosts() {
50 | val context = rememberPageContext()
51 | val breakpoint = rememberBreakpoint()
52 |
53 | var state by remember { mutableStateOf(AllPostsUIState()) }
54 |
55 | LaunchedEffect(state.pendingPostEnabled || state.draftPostEnabled) {
56 | when {
57 | state.pendingPostEnabled -> getPendingPosts(
58 | onSuccess = {
59 | state = state.copy(isLoading = false, posts = it)
60 | }
61 | )
62 |
63 | state.draftPostEnabled -> getDraftPosts(
64 | onSuccess = {
65 | state = state.copy(isLoading = false, posts = it)
66 | }
67 | )
68 |
69 | else -> getAdminAllPosts(
70 | onSuccess = {
71 | state = state.copy(isLoading = false, posts = it)
72 | }
73 | )
74 | }
75 | }
76 |
77 | AdminPageLayout {
78 | Column(
79 | modifier = Modifier
80 | .margin(topBottom = 60.px)
81 | .fillMaxSize()
82 | .padding(left = if (breakpoint > Breakpoint.MD) SIDE_PANEL_WIDTH.px else 0.px),
83 | verticalArrangement = Arrangement.Top,
84 | horizontalAlignment = Alignment.CenterHorizontally
85 | ) {
86 | Row(
87 | modifier = Modifier
88 | .fillMaxWidth(
89 | if (breakpoint > Breakpoint.MD) 80.percent
90 | else 90.percent
91 | )
92 | .margin(top = 80.px),
93 | horizontalArrangement = Arrangement.Start,
94 | verticalAlignment = Alignment.CenterVertically
95 | ) {
96 | Switch(
97 | modifier = Modifier.margin(right = 8.px),
98 | checked = state.pendingPostEnabled,
99 | onCheckedChange = {
100 | state = state.copy(pendingPostEnabled = it)
101 | if (it) {
102 | state = state.copy(draftPostEnabled = false)
103 | }
104 | },
105 | size = SwitchSize.MD
106 | )
107 | SpanText(
108 | modifier = Modifier
109 | .fontSize(14.px)
110 | .color(getSitePalette().blue),
111 | text = "Show Pending Posts"
112 | )
113 |
114 | Switch(
115 | modifier = Modifier.margin(leftRight = 8.px),
116 | checked = state.draftPostEnabled,
117 | onCheckedChange = {
118 | state = state.copy(draftPostEnabled = it)
119 | if (it) {
120 | state = state.copy(pendingPostEnabled = false)
121 | }
122 | },
123 | size = SwitchSize.MD
124 | )
125 | SpanText(
126 | modifier = Modifier
127 | .fontSize(14.px)
128 | .color(getSitePalette().blue),
129 | text = "Show Draft Posts"
130 | )
131 | }
132 | PostsView(
133 | breakpoint = breakpoint,
134 | posts = state.posts,
135 | numColumns = numColumns(base = 1, sm = 1, md = 2, lg = 2),
136 | onClick = { context.router.navigateTo(Screen.AdminCreate.passPostId(short = it)) }
137 | )
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/pages/admincreatepost/Functions.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.admincreatepost
2 |
3 | import com.canerture.androidhub.common.Id
4 | import com.canerture.androidhub.models.ControlStyle
5 | import com.canerture.androidhub.models.EditorControl
6 | import kotlinx.browser.document
7 | import org.w3c.dom.HTMLTextAreaElement
8 |
9 | fun getEditor() = document.getElementById(Id.EDITOR) as HTMLTextAreaElement
10 |
11 | fun getSelectedIntRange(): IntRange? {
12 | val editor = getEditor()
13 | val start = editor.selectionStart
14 | val end = editor.selectionEnd
15 | return if (start != null && end != null) {
16 | IntRange(start, (end - 1))
17 | } else null
18 | }
19 |
20 | fun getSelectedText(): String? {
21 | val range = getSelectedIntRange()
22 | return if (range != null) {
23 | getEditor().value.substring(range)
24 | } else null
25 | }
26 |
27 | fun applyStyle(controlStyle: ControlStyle) {
28 | val selectedText = getSelectedText()
29 | val selectedIntRange = getSelectedIntRange()
30 | if (selectedIntRange != null && selectedText != null) {
31 | getEditor().value = getEditor().value.replaceRange(
32 | range = selectedIntRange,
33 | replacement = controlStyle.style
34 | )
35 | document.getElementById(Id.EDITOR_PREVIEW)?.innerHTML = getEditor().value
36 | }
37 | }
38 |
39 | fun applyControlStyle(
40 | editorControl: EditorControl,
41 | onLinkClick: () -> Unit,
42 | onImageClick: () -> Unit
43 | ) {
44 | when (editorControl) {
45 | EditorControl.Bold -> {
46 | applyStyle(
47 | ControlStyle.Bold(
48 | selectedText = getSelectedText()
49 | )
50 | )
51 | }
52 |
53 | EditorControl.Italic -> {
54 | applyStyle(
55 | ControlStyle.Italic(
56 | selectedText = getSelectedText()
57 | )
58 | )
59 | }
60 |
61 | EditorControl.Link -> {
62 | onLinkClick()
63 | }
64 |
65 | EditorControl.Title -> {
66 | applyStyle(
67 | ControlStyle.Title(
68 | selectedText = getSelectedText()
69 | )
70 | )
71 | }
72 |
73 | EditorControl.Subtitle -> {
74 | applyStyle(
75 | ControlStyle.Subtitle(
76 | selectedText = getSelectedText(),
77 | )
78 | )
79 | }
80 |
81 | EditorControl.Quote -> {
82 | applyStyle(
83 | ControlStyle.Quote(
84 | selectedText = getSelectedText()
85 | )
86 | )
87 | }
88 |
89 | EditorControl.Code -> {
90 | applyStyle(
91 | ControlStyle.Code(
92 | selectedText = getSelectedText()
93 | )
94 | )
95 | }
96 |
97 | EditorControl.Image -> {
98 | onImageClick()
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/pages/author/Author.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.author
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.common.Constants.POST_AUTHOR_ID_PARAM
10 | import com.canerture.androidhub.components.layouts.PageLayout
11 | import com.canerture.androidhub.components.widgets.ErrorView
12 | import com.canerture.androidhub.components.widgets.LoadingIndicator
13 | import com.canerture.androidhub.data.getPostsByAuthorId
14 | import com.canerture.androidhub.data.model.Post
15 | import com.canerture.androidhub.getSitePalette
16 | import com.canerture.androidhub.pages.index.LatestArticleItem
17 | import com.varabyte.kobweb.compose.css.FontWeight
18 | import com.varabyte.kobweb.compose.css.TextAlign
19 | import com.varabyte.kobweb.compose.foundation.layout.Row
20 | import com.varabyte.kobweb.compose.ui.Alignment
21 | import com.varabyte.kobweb.compose.ui.Modifier
22 | import com.varabyte.kobweb.compose.ui.modifiers.alignContent
23 | import com.varabyte.kobweb.compose.ui.modifiers.color
24 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
25 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
26 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
27 | import com.varabyte.kobweb.compose.ui.modifiers.height
28 | import com.varabyte.kobweb.compose.ui.modifiers.margin
29 | import com.varabyte.kobweb.compose.ui.modifiers.padding
30 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
31 | import com.varabyte.kobweb.core.Page
32 | import com.varabyte.kobweb.core.rememberPageContext
33 | import com.varabyte.kobweb.silk.components.icons.fa.FaBoltLightning
34 | import com.varabyte.kobweb.silk.components.layout.SimpleGrid
35 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
36 | import com.varabyte.kobweb.silk.components.style.breakpoint.ResponsiveValues
37 | import com.varabyte.kobweb.silk.components.text.SpanText
38 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
39 | import kotlinx.browser.document
40 | import org.jetbrains.compose.web.css.AlignContent
41 | import org.jetbrains.compose.web.css.cssRem
42 | import org.jetbrains.compose.web.css.px
43 |
44 | data class AuthorUIState(
45 | val isLoading: Boolean = true,
46 | val posts: List = emptyList(),
47 | val authorId: String = "",
48 | val authorName: String = "",
49 | val isError: Boolean = false
50 | )
51 |
52 | @Page("/author")
53 | @Composable
54 | fun Author() {
55 | val context = rememberPageContext()
56 |
57 | var state by remember { mutableStateOf(AuthorUIState()) }
58 |
59 | state = state.copy(authorId = context.route.queryParams[POST_AUTHOR_ID_PARAM].orEmpty())
60 |
61 | LaunchedEffect(state.authorId) {
62 | state = state.copy(isLoading = true)
63 | document.title = state.authorName
64 | getPostsByAuthorId(
65 | authorId = state.authorId,
66 | onSuccess = {
67 | state = state.copy(isLoading = false, authorName = it.authorName, posts = it.posts)
68 | },
69 | onError = {
70 | state = state.copy(isLoading = false, isError = true)
71 | }
72 | )
73 | }
74 |
75 | if (state.isLoading) {
76 | LoadingIndicator()
77 | } else {
78 | PageLayout("Home") {
79 | if (state.isError) {
80 | ErrorView()
81 | return@PageLayout
82 | }
83 |
84 | if (state.posts.isNotEmpty()) {
85 | AuthorPosts(state.authorName, state.posts)
86 | }
87 | }
88 | }
89 | }
90 |
91 | @Composable
92 | private fun AuthorPosts(authorName: String, list: List) {
93 | val breakpoint = rememberBreakpoint()
94 | Row(
95 | modifier = Modifier
96 | .fillMaxWidth()
97 | .padding(bottom = 2.cssRem, top = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem, leftRight = 6.cssRem),
98 | verticalAlignment = Alignment.CenterVertically
99 | ) {
100 | FaBoltLightning(
101 | modifier = Modifier
102 | .height(20.px)
103 | .color(getSitePalette().green)
104 | .margin(right = 0.5.cssRem)
105 | .alignContent(AlignContent.Start),
106 | )
107 | SpanText(
108 | text = authorName,
109 | modifier = Modifier
110 | .textAlign(TextAlign.Center)
111 | .fontWeight(FontWeight.Bold)
112 | .fontSize(1.25.cssRem)
113 | .color(getSitePalette().blue.toRgb())
114 | )
115 | }
116 |
117 | SimpleGrid(
118 | modifier = Modifier.fillMaxWidth().padding(leftRight = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem),
119 | numColumns = ResponsiveValues(1, 1, 3, 3, 3),
120 | content = {
121 | list.forEach { article ->
122 | LatestArticleItem(
123 | article = article
124 | )
125 | }
126 | }
127 | )
128 | }
129 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/pages/category/Category.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.category
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.components.layouts.PageLayout
10 | import com.canerture.androidhub.components.widgets.ErrorView
11 | import com.canerture.androidhub.components.widgets.LoadingIndicator
12 | import com.canerture.androidhub.data.getPostsByCategory
13 | import com.canerture.androidhub.data.model.Post
14 | import com.canerture.androidhub.getSitePalette
15 | import com.canerture.androidhub.pages.index.LatestArticleItem
16 | import com.varabyte.kobweb.compose.css.FontWeight
17 | import com.varabyte.kobweb.compose.css.TextAlign
18 | import com.varabyte.kobweb.compose.foundation.layout.Row
19 | import com.varabyte.kobweb.compose.ui.Alignment
20 | import com.varabyte.kobweb.compose.ui.Modifier
21 | import com.varabyte.kobweb.compose.ui.modifiers.alignContent
22 | import com.varabyte.kobweb.compose.ui.modifiers.color
23 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
24 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
25 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
26 | import com.varabyte.kobweb.compose.ui.modifiers.height
27 | import com.varabyte.kobweb.compose.ui.modifiers.margin
28 | import com.varabyte.kobweb.compose.ui.modifiers.padding
29 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
30 | import com.varabyte.kobweb.core.Page
31 | import com.varabyte.kobweb.core.rememberPageContext
32 | import com.varabyte.kobweb.silk.components.icons.fa.FaBoltLightning
33 | import com.varabyte.kobweb.silk.components.layout.SimpleGrid
34 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
35 | import com.varabyte.kobweb.silk.components.style.breakpoint.ResponsiveValues
36 | import com.varabyte.kobweb.silk.components.text.SpanText
37 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
38 | import kotlinx.browser.document
39 | import org.jetbrains.compose.web.css.AlignContent
40 | import org.jetbrains.compose.web.css.cssRem
41 | import org.jetbrains.compose.web.css.px
42 |
43 | data class CategoryUIState(
44 | val isLoading: Boolean = true,
45 | val posts: List = emptyList(),
46 | val category: String = "",
47 | val isError: Boolean = false
48 | )
49 |
50 | @Page("/category")
51 | @Composable
52 | fun Category() {
53 | val context = rememberPageContext()
54 |
55 | var state by remember { mutableStateOf(CategoryUIState()) }
56 |
57 | state = state.copy(category = context.route.queryParams["category"].orEmpty())
58 |
59 | LaunchedEffect(state.category) {
60 | state = state.copy(isLoading = true)
61 | document.title = state.category
62 | getPostsByCategory(
63 | category = state.category,
64 | onSuccess = { posts ->
65 | state = CategoryUIState(
66 | isLoading = false,
67 | posts = posts
68 | )
69 | },
70 | onError = {
71 | state = state.copy(isLoading = false, isError = true)
72 | }
73 | )
74 | }
75 |
76 | if (state.isLoading) {
77 | LoadingIndicator()
78 | } else {
79 | PageLayout("Home") {
80 | if (state.isError) {
81 | ErrorView()
82 | return@PageLayout
83 | }
84 |
85 | if (state.posts.isNotEmpty()) {
86 | CategoryPosts(state.category, state.posts)
87 | }
88 | }
89 | }
90 | }
91 |
92 | @Composable
93 | private fun CategoryPosts(category: String, list: List) {
94 | val breakpoint = rememberBreakpoint()
95 | Row(
96 | modifier = Modifier
97 | .fillMaxWidth()
98 | .padding(bottom = 2.cssRem, top = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem, leftRight = 6.cssRem),
99 | verticalAlignment = Alignment.CenterVertically
100 | ) {
101 | FaBoltLightning(
102 | modifier = Modifier
103 | .height(20.px)
104 | .color(getSitePalette().green)
105 | .margin(right = 0.5.cssRem)
106 | .alignContent(AlignContent.Start),
107 | )
108 | SpanText(
109 | text = category,
110 | modifier = Modifier
111 | .textAlign(TextAlign.Center)
112 | .fontWeight(FontWeight.Bold)
113 | .fontSize(1.25.cssRem)
114 | .color(getSitePalette().blue.toRgb())
115 | )
116 | }
117 |
118 | SimpleGrid(
119 | modifier = Modifier.fillMaxWidth().padding(leftRight = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem),
120 | numColumns = ResponsiveValues(1, 2, 3, 3, 3),
121 | content = {
122 | list.forEach { article ->
123 | LatestArticleItem(
124 | article = article
125 | )
126 | }
127 | }
128 | )
129 | }
130 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/pages/index/AndroidHeroContent.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.index
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.common.Res
5 | import com.canerture.androidhub.getSitePalette
6 | import com.varabyte.kobweb.compose.css.AlignContent
7 | import com.varabyte.kobweb.compose.css.FontWeight
8 | import com.varabyte.kobweb.compose.css.TextAlign
9 | import com.varabyte.kobweb.compose.foundation.layout.Column
10 | import com.varabyte.kobweb.compose.foundation.layout.Row
11 | import com.varabyte.kobweb.compose.foundation.layout.Spacer
12 | import com.varabyte.kobweb.compose.ui.Alignment
13 | import com.varabyte.kobweb.compose.ui.Modifier
14 | import com.varabyte.kobweb.compose.ui.modifiers.alignContent
15 | import com.varabyte.kobweb.compose.ui.modifiers.color
16 | import com.varabyte.kobweb.compose.ui.modifiers.display
17 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
18 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
19 | import com.varabyte.kobweb.compose.ui.modifiers.lineHeight
20 | import com.varabyte.kobweb.compose.ui.modifiers.padding
21 | import com.varabyte.kobweb.compose.ui.modifiers.size
22 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
23 | import com.varabyte.kobweb.silk.components.graphics.Image
24 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
25 | import com.varabyte.kobweb.silk.components.text.SpanText
26 | import org.jetbrains.compose.web.css.Color
27 | import org.jetbrains.compose.web.css.DisplayStyle
28 | import org.jetbrains.compose.web.css.cssRem
29 | import org.jetbrains.compose.web.css.px
30 |
31 | @Composable
32 | fun AndroidHeroContent(
33 | breakpoint: Breakpoint
34 | ) {
35 | if (breakpoint < Breakpoint.SM) {
36 | Column(
37 | horizontalAlignment = Alignment.CenterHorizontally
38 | ) {
39 | Image(
40 | src = Res.Image.ANDROID_FIGURE,
41 | description = "Android Figure",
42 | modifier = Modifier
43 | .size(240.px)
44 | .display(DisplayStyle.Block)
45 | )
46 | TextArea(
47 | breakpoint = breakpoint,
48 | modifier = Modifier.padding(leftRight = 2.cssRem).alignContent(AlignContent.Center)
49 | )
50 | }
51 | } else {
52 | Row(
53 | modifier = Modifier.padding(leftRight = 10.cssRem),
54 | verticalAlignment = Alignment.CenterVertically
55 | ) {
56 | TextArea(breakpoint)
57 | Image(
58 | src = Res.Image.ANDROID_FIGURE,
59 | description = "Android Figure",
60 | modifier = Modifier
61 | .size(400.px)
62 | .display(DisplayStyle.Block)
63 | )
64 | }
65 | }
66 | }
67 |
68 | @Composable
69 | private fun TextArea(breakpoint: Breakpoint, modifier: Modifier = Modifier) {
70 | Column(
71 | modifier = modifier
72 | ) {
73 | SpanText(
74 | text = "Hi \uD83D\uDC4B",
75 | modifier = Modifier
76 | .fontWeight(FontWeight.Bold)
77 | .textAlign(TextAlign.Start)
78 | .color(getSitePalette().green)
79 | .fontSize(if (breakpoint < Breakpoint.MD) 18.px else 28.px)
80 | )
81 | Spacer()
82 | SpanText(
83 | text = "Let’s explore everything about the exciting world of Android together. On this platform, you can delve into the vast opportunities offered by the Android operating system and examine the latest developments in the field. Come on, let’s discover the limitless world of Android together!",
84 | modifier = Modifier
85 | .textAlign(TextAlign.Start)
86 | .color(Color.gray)
87 | .fontSize(if (breakpoint < Breakpoint.MD) 14.px else 20.px)
88 | .lineHeight(2)
89 | .padding(right = 2.cssRem)
90 | )
91 | }
92 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/pages/search/Search.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.search
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.components.layouts.PageLayout
10 | import com.canerture.androidhub.components.widgets.ErrorView
11 | import com.canerture.androidhub.components.widgets.LoadingIndicator
12 | import com.canerture.androidhub.data.model.Post
13 | import com.canerture.androidhub.data.searchPostsByTitle
14 | import com.canerture.androidhub.getSitePalette
15 | import com.canerture.androidhub.pages.index.LatestArticleItem
16 | import com.varabyte.kobweb.compose.css.FontWeight
17 | import com.varabyte.kobweb.compose.css.TextAlign
18 | import com.varabyte.kobweb.compose.foundation.layout.Row
19 | import com.varabyte.kobweb.compose.ui.Alignment
20 | import com.varabyte.kobweb.compose.ui.Modifier
21 | import com.varabyte.kobweb.compose.ui.modifiers.alignContent
22 | import com.varabyte.kobweb.compose.ui.modifiers.color
23 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
24 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
25 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
26 | import com.varabyte.kobweb.compose.ui.modifiers.height
27 | import com.varabyte.kobweb.compose.ui.modifiers.margin
28 | import com.varabyte.kobweb.compose.ui.modifiers.padding
29 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
30 | import com.varabyte.kobweb.core.Page
31 | import com.varabyte.kobweb.core.rememberPageContext
32 | import com.varabyte.kobweb.silk.components.icons.fa.FaBoltLightning
33 | import com.varabyte.kobweb.silk.components.layout.SimpleGrid
34 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
35 | import com.varabyte.kobweb.silk.components.style.breakpoint.ResponsiveValues
36 | import com.varabyte.kobweb.silk.components.text.SpanText
37 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
38 | import kotlinx.browser.document
39 | import org.jetbrains.compose.web.css.AlignContent
40 | import org.jetbrains.compose.web.css.cssRem
41 | import org.jetbrains.compose.web.css.px
42 |
43 | data class SearchUIState(
44 | val isLoading: Boolean = true,
45 | val posts: List = emptyList(),
46 | val query: String = "",
47 | val isError: Boolean = false,
48 | )
49 |
50 | @Page("/search")
51 | @Composable
52 | fun Search() {
53 | val context = rememberPageContext()
54 |
55 | var state by remember { mutableStateOf(SearchUIState()) }
56 |
57 | state = state.copy(query = context.route.queryParams["query"].orEmpty())
58 |
59 | LaunchedEffect(state.query) {
60 | state = state.copy(isLoading = true)
61 | document.title = state.query
62 | searchPostsByTitle(
63 | title = state.query,
64 | onSuccess = { posts ->
65 | state = SearchUIState(
66 | isLoading = false,
67 | posts = posts
68 | )
69 | },
70 | onError = {
71 | state = state.copy(
72 | isLoading = false,
73 | isError = true
74 | )
75 | }
76 | )
77 | }
78 |
79 | if (state.isLoading) {
80 | LoadingIndicator()
81 | } else {
82 | PageLayout("Search") {
83 | if (state.isError) {
84 | ErrorView()
85 | return@PageLayout
86 | }
87 |
88 | if (state.posts.isNotEmpty()) {
89 | SearchPosts(state.query, state.posts)
90 | }
91 | }
92 | }
93 | }
94 |
95 | @Composable
96 | private fun SearchPosts(query: String, list: List) {
97 | val breakpoint = rememberBreakpoint()
98 | Row(
99 | modifier = Modifier
100 | .fillMaxWidth()
101 | .padding(bottom = 2.cssRem, top = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem, leftRight = 6.cssRem),
102 | verticalAlignment = Alignment.CenterVertically
103 | ) {
104 | FaBoltLightning(
105 | modifier = Modifier
106 | .height(if (breakpoint < Breakpoint.MD) 10.px else 20.px)
107 | .color(getSitePalette().green)
108 | .margin(right = 0.5.cssRem)
109 | .alignContent(AlignContent.Center),
110 | )
111 | SpanText(
112 | text = "${list.size} posts found for \"$query\"",
113 | modifier = Modifier
114 | .textAlign(TextAlign.Center)
115 | .fontWeight(FontWeight.Bold)
116 | .fontSize(if (breakpoint < Breakpoint.MD) 14.px else 18.px)
117 | .color(getSitePalette().blue.toRgb())
118 | )
119 | }
120 |
121 | SimpleGrid(
122 | modifier = Modifier.fillMaxWidth().padding(leftRight = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem),
123 | numColumns = ResponsiveValues(1, 1, 3, 3, 3),
124 | content = {
125 | list.forEach { article ->
126 | LatestArticleItem(
127 | article = article
128 | )
129 | }
130 | }
131 | )
132 | }
133 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/kotlin/com/canerture/androidhub/utils/ApiUtils.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.utils
2 |
3 | import com.canerture.androidhub.common.Constants
4 | import com.canerture.androidhub.data.model.BaseResponse
5 | import com.varabyte.kobweb.browser.http.http
6 | import kotlinx.browser.window
7 | import kotlinx.serialization.decodeFromString
8 | import kotlinx.serialization.json.Json
9 |
10 | object ApiUtils {
11 | inline fun safeApiCall(
12 | call: () -> ByteArray,
13 | onSuccess: (T) -> Unit,
14 | onError: (String) -> Unit,
15 | ) {
16 | try {
17 | val response = call().parseData>()
18 | if (response.status == 200) {
19 | onSuccess(response.data!!)
20 | } else {
21 | onError(response.message.orEmpty())
22 | }
23 | } catch (e: Exception) {
24 | onError(e.message.orEmpty())
25 | }
26 | }
27 |
28 | suspend fun get(path: String, param: Pair? = null): ByteArray {
29 | val url = if (param == null) {
30 | Constants.BASE_URL.plus(path)
31 | } else {
32 | Constants.BASE_URL.plus(path).plus("?${param.first}=${param.second}")
33 | }
34 | return window.http.get(url)
35 | }
36 |
37 | suspend fun post(path: String, body: ByteArray): ByteArray {
38 | return window.http.post(
39 | resource = Constants.BASE_URL.plus(path),
40 | body = body
41 | )
42 | }
43 |
44 | suspend fun put(path: String, body: ByteArray): ByteArray {
45 | return window.http.put(
46 | resource = Constants.BASE_URL.plus(path),
47 | body = body
48 | )
49 | }
50 |
51 | suspend fun delete(path: String, param: Pair? = null): ByteArray {
52 | val url = if (param == null) {
53 | Constants.BASE_URL.plus(path)
54 | } else {
55 | Constants.BASE_URL.plus(path).plus("?${param.first}=${param.second}")
56 | }
57 | return window.http.delete(url)
58 | }
59 |
60 | inline fun ByteArray?.parseData(): T {
61 | val json = this?.decodeToString().orEmpty()
62 | return Json.decodeFromString(json)
63 | }
64 | }
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/markdown/About.md:
--------------------------------------------------------------------------------
1 | ---
2 | root: .components.layouts.MarkdownLayout("About")
3 | ---
4 |
5 | # About this template
6 |
7 | This template is intended to both demonstrate some fundamentals of the Kobweb framework and act a
8 | starting point you can
9 | build your own site from.
10 |
11 | ---
12 |
13 | ## Learn
14 |
15 | If this is your first time using Kobweb, please open this site's project in an IDE and take a few
16 | minutes to look around
17 | the code.
18 |
19 | ### Files
20 |
21 | #### AppEntry.kt
22 |
23 | This file declares a method that is an entry point for all pages on your site. You can rename the
24 | file and the method if
25 | you like. Kobweb searches for a single method at compile time annotated with `@App`.
26 |
27 | #### AppStyles.kt
28 |
29 | An example of declaring generally useful styles that can be used anywhere across the whole site.
30 | Otherwise, you normally
31 | declare styles close to the widget that uses them.
32 |
33 | #### SiteTheme.kt
34 |
35 | An example of how to define some site-specific colors, effectively extending the palette provided by
36 | Silk.
37 |
38 | #### components/
39 |
40 | By convention, Kobweb codebases organize reusable site components under this folder. Within it, you
41 | have:
42 |
43 | * `layout/`
44 | Represents top-level organization for pages
45 | * `sections/`
46 | Areas of content that appear across multiple pages (such as nav bars and footers)
47 | * `widgets/`
48 | Home for low-level UI pieces that you can use around your site
49 |
50 | #### pages/
51 |
52 | Any `@Composable` under this folder additionally tagged with `@Page` will have a route generated for
53 | it automatically.
54 | Defining a page outside of this folder will be flagged as an error by the Kobweb Gradle plugin at
55 | compile time. Note
56 | that additional pages (like this one!) might live under the `resources/markdown` folder.
57 |
58 | #### resources/
59 |
60 | * `public`
61 | If you want to host any media on your site (such as an icon, an image, text configuration files,
62 | movies, fonts, etc.),
63 | you should put it under this folder.
64 | * `markdown`
65 | Any markdown discovered in here by Kobweb at compile time will be converted into pages on your
66 | site.
67 |
68 | ### Classes
69 |
70 | The Kobweb and Silk APIs introduce a lot of powerful concepts. Here are some of the most important
71 | ones to know about
72 | which you can find used throughout this template.
73 |
74 | #### Modifier
75 |
76 | Kobweb introduces the `Modifier` keyword that Android developers will recognize from the Jetpack
77 | Compose API. In a
78 | webdev context, this is used for setting CSS styles and html attributes on elements in the page.
79 |
80 | #### ComponentStyle
81 |
82 | Traditional HTML pages use CSS to style their UI. In Kobweb, these styles can be declared using
83 | the `ComponentStyle`
84 | class in a Kotlin-idiomatic way. You can find examples of component styles used throughout the
85 | template.
86 |
87 | #### ComponentVariant
88 |
89 | You can generate variants from component styles, which are ways to take base component styles and
90 | tweak them further.
91 |
92 | #### Keyframes
93 |
94 | You can create animations by declaring keyframes for them.
95 |
96 | ## Starting Point
97 |
98 | This template aims to create some generally useful pieces that most sites will want to use. Making
99 | your own site could
100 | be as easy as deleting this *About* page and working from there. However, you are welcome to modify
101 | or delete anything
102 | you find in the template that you don't plan to use in your final site.
103 |
104 | If instead you'd like to start from scratch, you can run
105 |
106 | ```
107 | $ kobweb create app/empty
108 | ```
109 |
110 | which will create a new project with nothing inside of it except for a minimal, skeletal structure.
111 |
112 | ## Export and Deploy
113 |
114 | When you are ready to share your site with the world, you'll want to export it first. This will
115 | create a production
116 | snapshot of your site.
117 |
118 | There are two flavors of Kobweb sites: *static layout* and *full stack*. You
119 | can [read more about these choices here](https://github.com/varabyte/kobweb#static-layout-vs-full-stack-sites).
120 |
121 | For most sites, a static layout site is what you want, so to do that, return to the command line and
122 | run:
123 |
124 | ```
125 | $ kobweb export --layout static
126 | ```
127 |
128 | After that runs for a little while, your production site should be generated! You can find the files
129 | under the
130 | `.kobweb/site` folder.
131 |
132 | Test it locally by running:
133 |
134 | ```
135 | $ kobweb run --layout static --env prod
136 | ```
137 |
138 | If you're satisfied, you can upload your site files to the static website host provider of your
139 | choice. Each provider
140 | has its own instructions for how it discovers your files, so please refer to their documentation.
141 |
142 | You can [read this blog post](https://bitspittle.dev/blog/2022/staticdeploy) for some concrete
143 | examples of exporting a
144 | Kobweb site to two popular static website hosting providers.
145 |
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/android-figure.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/android-figure.webp
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/androidhub-logo-dark.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/androidhub-logo-dark.webp
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/androidhub-logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/androidhub-logo.webp
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/bold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/bold.png
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/code.png
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/favicon.ico
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/image.png
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/italic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/italic.png
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/jetpack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/jetpack.webp
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/link.png
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/quote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/quote.png
--------------------------------------------------------------------------------
/site-android/src/jsMain/resources/public/title-subtitle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-android/src/jsMain/resources/public/title-subtitle.png
--------------------------------------------------------------------------------
/site-ios/.gitignore:
--------------------------------------------------------------------------------
1 | # Kobweb ignores
2 | !.kobweb/conf.yaml
3 |
--------------------------------------------------------------------------------
/site-ios/.kobweb/conf.yaml:
--------------------------------------------------------------------------------
1 | site:
2 | title: "."
3 |
4 | server:
5 | files:
6 | dev:
7 | contentRoot: "build/processedResources/js/main/public"
8 | script: "build/dist/js/developmentExecutable/androidhub.js"
9 | api: "build/libs/androidhub.jar"
10 | prod:
11 | script: "build/dist/js/productionExecutable/androidhub.js"
12 | siteRoot: ".kobweb/site"
13 |
14 | port: 8080
15 |
--------------------------------------------------------------------------------
/site-ios/.kobweb/server/logs/kobweb-server.log:
--------------------------------------------------------------------------------
1 | 2024-06-15 04:08:56.446 [main] DEBUG ktor.application - Java Home: /Users/canerture/Library/Java/JavaVirtualMachines/jbr-17.0.9/Contents
2 | 2024-06-15 04:08:56.447 [main] DEBUG ktor.application - Class Loader: jdk.internal.loader.ClassLoaders$AppClassLoader@7aec35a: [file:/Users/canerture/Documents/GitHub/AndroidHub/site-ios/.kobweb/server/server.jar!/ch]
3 | 2024-06-15 04:08:56.456 [main] INFO ktor.application - API jar found and will be loaded: "build/libs/androidhub.jar"
4 | 2024-06-15 04:08:56.475 [main] INFO ktor.application - Loaded and initialized server API jar in 1ms.
5 | 2024-06-15 04:08:56.486 [main] INFO ktor.application - Initializing Kobweb streams.
6 | 2024-06-15 04:08:56.504 [main] INFO ktor.application - Application started in 0.086 seconds.
7 | 2024-06-15 04:08:56.561 [main] INFO ktor.application - Responding at http://0.0.0.0:8080
8 | 2024-06-15 04:09:24.194 [kotlinx.coroutines.DefaultExecutor] INFO ktor.application - Kobweb server shutting down...
9 | 2024-06-15 04:09:25.244 [kotlinx.coroutines.DefaultExecutor] INFO ktor.application - Server finished shutting down.
10 |
--------------------------------------------------------------------------------
/site-ios/.kobweb/server/server.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/server/server.jar
--------------------------------------------------------------------------------
/site-ios/.kobweb/server/start.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | :: Find the kobweb project folder, using this file's location to start form
4 | cd /d "%~dp0"
5 | cd ..\..
6 |
7 | if defined KOBWEB_JAVA_HOME (
8 | set "java_cmd=%KOBWEB_JAVA_HOME%\bin\java"
9 | ) else if defined JAVA_HOME (
10 | set "java_cmd=%JAVA_HOME%\bin\java"
11 | ) else (
12 | set "java_cmd=java"
13 | )
14 |
15 | :: Run the java command with the common parameters
16 | %java_cmd% -Dkobweb.server.environment=PROD -Dkobweb.site.layout=FULLSTACK -Dio.ktor.development=false -jar .kobweb/server/server.jar
--------------------------------------------------------------------------------
/site-ios/.kobweb/server/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Find the kobweb project folder, using this file's location to start form
4 | cd "$(dirname "$0")"
5 | cd ../..
6 |
7 | args="-Dkobweb.server.environment=PROD -Dkobweb.site.layout=FULLSTACK -Dio.ktor.development=false -jar .kobweb/server/server.jar"
8 |
9 | if [ -n "$KOBWEB_JAVA_HOME" ]; then
10 | "$KOBWEB_JAVA_HOME/bin/java" $args
11 | elif [ -n "$JAVA_HOME" ]; then
12 | "$JAVA_HOME/bin/java" $args
13 | else
14 | java $args
15 | fi
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/bold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/bold.png
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/code.png
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/favicon.ico
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/image.png
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/ios-figure.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/ios-figure.webp
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/ioshub-logo-dark.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/ioshub-logo-dark.webp
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/ioshub-logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/ioshub-logo.webp
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/italic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/italic.png
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/jetpack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/jetpack.webp
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/link.png
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/quote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/quote.png
--------------------------------------------------------------------------------
/site-ios/.kobweb/site/title-subtitle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/.kobweb/site/title-subtitle.png
--------------------------------------------------------------------------------
/site-ios/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.varabyte.kobweb.gradle.application.extensions.AppBlock.LegacyRouteRedirectStrategy
2 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication
3 |
4 | plugins {
5 | alias(libs.plugins.kotlin.multiplatform)
6 | alias(libs.plugins.jetbrains.compose)
7 | alias(libs.plugins.kobweb.application)
8 | alias(libs.plugins.kotlin.serialization)
9 | }
10 |
11 | group = "com.canerture.androidhub"
12 | version = "1.0-SNAPSHOT"
13 |
14 | kobweb {
15 | app {
16 | index {
17 | description.set("Powered by Kobweb")
18 | }
19 | legacyRouteRedirectStrategy.set(LegacyRouteRedirectStrategy.DISALLOW)
20 | }
21 | }
22 |
23 | kotlin {
24 | configAsKobwebApplication("androidhub", includeServer = true)
25 |
26 | sourceSets {
27 | commonMain.dependencies {
28 | implementation(compose.runtime)
29 | }
30 |
31 | jsMain.dependencies {
32 | implementation(compose.html.core)
33 | implementation(libs.kobweb.core)
34 | implementation(libs.kobweb.silk)
35 | implementation(libs.silk.icons.fa)
36 | implementation(libs.kotlinx.serialization.json)
37 | implementation(libs.colorMath)
38 | }
39 |
40 | jvmMain.dependencies {
41 | implementation(libs.kobweb.api)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/AppEntry.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.runtime.setValue
8 | import com.canerture.androidhub.common.Res
9 | import com.varabyte.kobweb.compose.css.Cursor
10 | import com.varabyte.kobweb.compose.css.ScrollBehavior
11 | import com.varabyte.kobweb.compose.foundation.layout.Box
12 | import com.varabyte.kobweb.compose.ui.Alignment
13 | import com.varabyte.kobweb.compose.ui.Modifier
14 | import com.varabyte.kobweb.compose.ui.graphics.Colors
15 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
16 | import com.varabyte.kobweb.compose.ui.modifiers.border
17 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
18 | import com.varabyte.kobweb.compose.ui.modifiers.bottom
19 | import com.varabyte.kobweb.compose.ui.modifiers.color
20 | import com.varabyte.kobweb.compose.ui.modifiers.cursor
21 | import com.varabyte.kobweb.compose.ui.modifiers.display
22 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
23 | import com.varabyte.kobweb.compose.ui.modifiers.minHeight
24 | import com.varabyte.kobweb.compose.ui.modifiers.minWidth
25 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
26 | import com.varabyte.kobweb.compose.ui.modifiers.outline
27 | import com.varabyte.kobweb.compose.ui.modifiers.padding
28 | import com.varabyte.kobweb.compose.ui.modifiers.position
29 | import com.varabyte.kobweb.compose.ui.modifiers.right
30 | import com.varabyte.kobweb.compose.ui.modifiers.scrollBehavior
31 | import com.varabyte.kobweb.compose.ui.modifiers.size
32 | import com.varabyte.kobweb.compose.ui.modifiers.zIndex
33 | import com.varabyte.kobweb.compose.ui.toAttrs
34 | import com.varabyte.kobweb.core.App
35 | import com.varabyte.kobweb.silk.SilkApp
36 | import com.varabyte.kobweb.silk.components.graphics.Image
37 | import com.varabyte.kobweb.silk.components.layout.Surface
38 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
39 | import com.varabyte.kobweb.silk.components.style.toModifier
40 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
41 | import kotlinx.browser.document
42 | import kotlinx.browser.window
43 | import org.jetbrains.compose.web.css.Color
44 | import org.jetbrains.compose.web.css.DisplayStyle
45 | import org.jetbrains.compose.web.css.Position
46 | import org.jetbrains.compose.web.css.percent
47 | import org.jetbrains.compose.web.css.px
48 | import org.jetbrains.compose.web.css.vh
49 | import org.jetbrains.compose.web.css.vw
50 | import org.jetbrains.compose.web.dom.Div
51 | import org.w3c.dom.SMOOTH
52 | import org.w3c.dom.ScrollToOptions
53 |
54 | @App
55 | @Composable
56 | fun AppEntry(content: @Composable () -> Unit) {
57 | SilkApp {
58 | var isButtonVisible by remember { mutableStateOf(false) }
59 | val breakpoint = rememberBreakpoint()
60 |
61 | window.onscroll = {
62 | if (document.body!!.scrollTop > 300 || document.documentElement!!.scrollTop > 300) {
63 | isButtonVisible = true
64 | } else {
65 | isButtonVisible = false
66 | }
67 | }
68 |
69 | Surface(
70 | Modifier
71 | .minHeight(100.vh)
72 | .minWidth(100.vw)
73 | .scrollBehavior(ScrollBehavior.Smooth)
74 | ) {
75 | Box(
76 | contentAlignment = Alignment.Center,
77 | modifier = Modifier.fillMaxSize()
78 | ) {
79 | content()
80 |
81 | Div(
82 | attrs = Modifier
83 | .position(Position.Fixed)
84 | .bottom(20.px)
85 | .right(if (breakpoint < Breakpoint.MD) 0.px else 30.px)
86 | .zIndex(99)
87 | .border(0.px)
88 | .outline(0.px)
89 | .backgroundColor(Color.transparent)
90 | .color(Colors.White)
91 | .cursor(Cursor.Pointer)
92 | .borderRadius(100.percent)
93 | .padding(
94 | bottom = if (breakpoint < Breakpoint.MD) 10.px else 15.px,
95 | right = if (breakpoint < Breakpoint.MD) 4.px else 22.px
96 | )
97 | .display(
98 | if (isButtonVisible) DisplayStyle.Block else DisplayStyle.None
99 | )
100 | .onClick {
101 | document.documentElement?.scrollTo(
102 | ScrollToOptions(
103 | behavior = org.w3c.dom.ScrollBehavior.SMOOTH,
104 | top = 0.0
105 | )
106 | )
107 | }.toAttrs()
108 | ) {
109 | val size = if (breakpoint < Breakpoint.MD) 40.px else 60.px
110 | Image(
111 | src = Res.Image.JETPACK,
112 | modifier = Modifier.size(size, size)
113 | )
114 | }
115 | }
116 | }
117 | }
118 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/SiteTheme.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub
2 |
3 | import com.varabyte.kobweb.compose.ui.graphics.Color
4 | import com.varabyte.kobweb.compose.ui.graphics.Colors
5 | import com.varabyte.kobweb.silk.init.InitSilk
6 | import com.varabyte.kobweb.silk.init.InitSilkContext
7 | import com.varabyte.kobweb.silk.theme.colors.palette.background
8 | import com.varabyte.kobweb.silk.theme.colors.palette.color
9 |
10 | /**
11 | * @property nearBackground A useful color to apply to a container that should differentiate itself from the background
12 | * but just a little.
13 | */
14 | class SitePalette(
15 | val primary: Color,
16 | val white: Color,
17 | val secondary: Color,
18 | val gray: Color,
19 | val lightGray: Color,
20 | val grayTransparent: Color,
21 | )
22 |
23 | object SitePalettes {
24 | val palette = SitePalette(
25 | primary = Color.rgb(0x2b2b2b),
26 | white = Colors.White,
27 | secondary = Color.rgb(0x2b2b2b),
28 | gray = Color.rgb(0xC8C8C8),
29 | lightGray = Color.rgb(0xF0F0F0),
30 | grayTransparent = Color.rgba(255f, 255f, 255f, 0.15f),
31 | )
32 | }
33 |
34 | fun getSitePalette(): SitePalette {
35 | return SitePalettes.palette
36 | }
37 |
38 | @InitSilk
39 | fun initTheme(ctx: InitSilkContext) {
40 | ctx.theme.palettes.light.background = Color.rgb(0xFAF8FF)
41 | ctx.theme.palettes.light.color = Colors.White
42 | }
43 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/common/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.common
2 |
3 | object Constants {
4 | const val BASE_URL = "https://api.canerture.com/ioshub/"
5 |
6 | const val QUERY_PARAM = "query"
7 | const val CATEGORY_PARAM = "category"
8 | const val POST_SHORT_PARAM = "short"
9 | const val POST_AUTHOR_ID_PARAM = "authorId"
10 |
11 | const val SIDE_PANEL_WIDTH = 250
12 | const val COLLAPSED_PANEL_HEIGHT = 100
13 | }
14 |
15 | object Res {
16 | object Image {
17 | const val LOGO = "/ioshub-logo.webp"
18 | const val LOGO_DARK = "/ioshub-logo-dark.webp"
19 | const val IOS_FIGURE = "/ios-figure.webp"
20 | const val JETPACK = "/jetpack.webp"
21 | }
22 |
23 | object Icon {
24 | const val BOLD = "/bold.png"
25 | const val ITALIC = "/italic.png"
26 | const val LINK = "/link.png"
27 | const val TITLE = "/title-subtitle.png"
28 | const val SUBTITLE = "/title-subtitle.png"
29 | const val QUOTE = "/quote.png"
30 | const val CODE = "/code.png"
31 | const val IMAGE = "/image.png"
32 | }
33 |
34 | object PathIcon {
35 | const val CREATE =
36 | "M12 9.52148V12.5215M12 12.5215V15.5215M12 12.5215H15M12 12.5215H9M21 12.5215C21 13.7034 20.7672 14.8737 20.3149 15.9656C19.8626 17.0576 19.1997 18.0497 18.364 18.8854C17.5282 19.7212 16.5361 20.3841 15.4442 20.8364C14.3522 21.2887 13.1819 21.5215 12 21.5215C10.8181 21.5215 9.64778 21.2887 8.55585 20.8364C7.46392 20.3841 6.47177 19.7212 5.63604 18.8854C4.80031 18.0497 4.13738 17.0576 3.68508 15.9656C3.23279 14.8737 3 13.7034 3 12.5215C3 10.1345 3.94821 7.84535 5.63604 6.15752C7.32387 4.4697 9.61305 3.52148 12 3.52148C14.3869 3.52148 16.6761 4.4697 18.364 6.15752C20.0518 7.84535 21 10.1345 21 12.5215Z"
37 | const val POSTS =
38 | "M9 5H7C6.46957 5 5.96086 5.21071 5.58579 5.58579C5.21071 5.96086 5 6.46957 5 7V19C5 19.5304 5.21071 20.0391 5.58579 20.4142C5.96086 20.7893 6.46957 21 7 21H17C17.5304 21 18.0391 20.7893 18.4142 20.4142C18.7893 20.0391 19 19.5304 19 19V7C19 6.46957 18.7893 5.96086 18.4142 5.58579C18.0391 5.21071 17.5304 5 17 5H15M9 5C9 5.53043 9.21071 6.03914 9.58579 6.41421C9.96086 6.78929 10.4696 7 11 7H13C13.5304 7 14.0391 6.78929 14.4142 6.41421C14.7893 6.03914 15 5.53043 15 5M9 5C9 4.46957 9.21071 3.96086 9.58579 3.58579C9.96086 3.21071 10.4696 3 11 3H13C13.5304 3 14.0391 3.21071 14.4142 3.58579C14.7893 3.96086 15 4.46957 15 5M12 12H15M12 16H15M9 12H9.01M9 16H9.01"
39 | const val LOGOUT =
40 | "M11 16.5215L7 12.5215M7 12.5215L11 8.52148M7 12.5215H21M16 16.5215V17.5215C16 18.3171 15.6839 19.0802 15.1213 19.6428C14.5587 20.2054 13.7956 20.5215 13 20.5215H6C5.20435 20.5215 4.44129 20.2054 3.87868 19.6428C3.31607 19.0802 3 18.3171 3 17.5215V7.52148C3 6.72583 3.31607 5.96277 3.87868 5.40016C4.44129 4.83755 5.20435 4.52148 6 4.52148H13C13.7956 4.52148 14.5587 4.83755 15.1213 5.40016C15.6839 5.96277 16 6.72583 16 7.52148V8.52148"
41 | }
42 | }
43 |
44 | object Id {
45 | const val NAME_INPUT = "nameInput"
46 | const val EMAIL_INPUT = "emailInput"
47 | const val PASSWORD_INPUT = "passwordInput"
48 | const val SVG_PARENT = "svgParent"
49 | const val VECTOR_ICON = "vectorIcon"
50 | const val NAVIGATION_TEXT = "navigationText"
51 | const val EDITOR = "editor"
52 | const val EDITOR_PREVIEW = "editorPreview"
53 | const val TITLE_INPUT = "titleInput"
54 | const val THUMBNAIL_INPUT = "thumbnailInput"
55 | const val LINK_HREF_INPUT = "linkHrefInput"
56 | const val LINK_TITLE_INPUT = "linkTitleInput"
57 | const val ADMIN_SEARCH_BAR = "adminSearchBar"
58 | const val POST_CONTENT = "postContent"
59 | const val NAVBAR_SEARCH_INPUT = "navbarSearchInput"
60 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/common/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.common
2 |
3 | import com.varabyte.kobweb.compose.ui.Modifier
4 | import com.varabyte.kobweb.compose.ui.graphics.Colors
5 | import com.varabyte.kobweb.compose.ui.modifiers.border
6 | import com.varabyte.kobweb.compose.ui.modifiers.outline
7 | import org.jetbrains.compose.web.css.LineStyle
8 | import org.jetbrains.compose.web.css.px
9 | import kotlin.js.Date
10 |
11 | fun Modifier.noBorder(): Modifier {
12 | return this.border(
13 | width = 0.px,
14 | style = LineStyle.None,
15 | color = Colors.Transparent
16 | ).outline(
17 | width = 0.px,
18 | style = LineStyle.None,
19 | color = Colors.Transparent
20 | )
21 | }
22 |
23 | fun Long.parseDateString() = Date(this).toLocaleDateString()
24 |
25 | fun String.formatShort() = replace(" ", "-").lowercase()
26 | .replace("ğ", "g")
27 | .replace("ü", "u")
28 | .replace("ş", "s")
29 | .replace("ı", "i")
30 | .replace("ö", "o")
31 | .replace("ç", "c")
32 | .replace("'", "")
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/components/layouts/AdminPageLayout.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.layouts
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.runtime.setValue
8 | import com.canerture.androidhub.components.sections.AdminNavigationItems
9 | import com.canerture.androidhub.components.sections.OverflowSidePanel
10 | import com.canerture.androidhub.components.sections.SidePanel
11 | import com.varabyte.kobweb.compose.foundation.layout.Box
12 | import com.varabyte.kobweb.compose.foundation.layout.Column
13 | import com.varabyte.kobweb.compose.ui.Alignment
14 | import com.varabyte.kobweb.compose.ui.Modifier
15 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
16 |
17 | @Composable
18 | fun AdminPageLayout(content: @Composable () -> Unit) {
19 | var overflowOpened by remember { mutableStateOf(false) }
20 | Box(
21 | modifier = Modifier.fillMaxSize(),
22 | contentAlignment = Alignment.Center
23 | ) {
24 | Column(
25 | modifier = Modifier
26 | .fillMaxSize()
27 | ) {
28 | SidePanel(
29 | onMenuClick = { overflowOpened = true }
30 | )
31 | if (overflowOpened) {
32 | OverflowSidePanel(
33 | onMenuClose = {
34 | overflowOpened = false
35 | },
36 | content = {
37 | AdminNavigationItems()
38 | }
39 | )
40 | }
41 | content()
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/components/sections/Footer.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.sections
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.ShadowedGreenVariant
5 | import com.canerture.androidhub.common.noBorder
6 | import com.canerture.androidhub.data.isUserLoggedIn
7 | import com.canerture.androidhub.getSitePalette
8 | import com.canerture.androidhub.navigation.Screen
9 | import com.varabyte.kobweb.compose.css.Cursor
10 | import com.varabyte.kobweb.compose.css.FontWeight
11 | import com.varabyte.kobweb.compose.css.TextAlign
12 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement
13 | import com.varabyte.kobweb.compose.foundation.layout.Column
14 | import com.varabyte.kobweb.compose.ui.Alignment
15 | import com.varabyte.kobweb.compose.ui.Modifier
16 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
17 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
18 | import com.varabyte.kobweb.compose.ui.modifiers.color
19 | import com.varabyte.kobweb.compose.ui.modifiers.cursor
20 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
21 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
22 | import com.varabyte.kobweb.compose.ui.modifiers.margin
23 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
24 | import com.varabyte.kobweb.compose.ui.modifiers.padding
25 | import com.varabyte.kobweb.compose.ui.modifiers.setVariable
26 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
27 | import com.varabyte.kobweb.compose.ui.modifiers.width
28 | import com.varabyte.kobweb.compose.ui.toAttrs
29 | import com.varabyte.kobweb.core.rememberPageContext
30 | import com.varabyte.kobweb.silk.components.forms.ButtonVars
31 | import com.varabyte.kobweb.silk.components.navigation.Link
32 | import com.varabyte.kobweb.silk.components.style.ComponentStyle
33 | import com.varabyte.kobweb.silk.components.style.base
34 | import com.varabyte.kobweb.silk.components.style.toModifier
35 | import com.varabyte.kobweb.silk.components.text.SpanText
36 | import org.jetbrains.compose.web.css.cssRem
37 | import org.jetbrains.compose.web.css.percent
38 | import org.jetbrains.compose.web.css.px
39 | import org.jetbrains.compose.web.dom.Button
40 | import org.jetbrains.compose.web.dom.Span
41 |
42 | val FooterStyle by ComponentStyle.base {
43 | Modifier.padding(topBottom = 5.cssRem, leftRight = 10.percent)
44 | }
45 |
46 | @Composable
47 | fun Footer(modifier: Modifier = Modifier) {
48 | val context = rememberPageContext()
49 | val isUserLoggedIn = isUserLoggedIn()
50 | Column(
51 | modifier = FooterStyle.toModifier().then(modifier),
52 | verticalArrangement = Arrangement.Center,
53 | horizontalAlignment = Alignment.CenterHorizontally
54 | ) {
55 | Button(
56 | attrs = ShadowedGreenVariant.toModifier()
57 | .backgroundColor(getSitePalette().primary)
58 | .setVariable(ButtonVars.BackgroundDefaultColor, getSitePalette().primary)
59 | .setVariable(ButtonVars.BackgroundHoverColor, getSitePalette().primary)
60 | .setVariable(ButtonVars.BackgroundPressedColor, getSitePalette().primary)
61 | .width(120.px)
62 | .borderRadius(3.cssRem)
63 | .padding(leftRight = 1.cssRem, topBottom = 0.5.cssRem)
64 | .margin(bottom = 1.cssRem)
65 | .noBorder()
66 | .cursor(Cursor.Pointer)
67 | .onClick {
68 | if (isUserLoggedIn) {
69 | context.router.navigateTo(Screen.AdminMyPosts.route)
70 | } else {
71 | context.router.navigateTo(Screen.Login.route)
72 | }
73 | }
74 | .toAttrs(),
75 | ) {
76 | SpanText(
77 | text = "Join us",
78 | modifier = Modifier
79 | .fontSize(16.px)
80 | .color(getSitePalette().white)
81 | )
82 | }
83 |
84 | Span(Modifier.textAlign(TextAlign.Center).toAttrs()) {
85 | Link("https://bento.me/canerture") {
86 | SpanText(
87 | text = "2024 - Caner Ture",
88 | modifier = Modifier
89 | .color(getSitePalette().primary)
90 | .fontWeight(FontWeight.Bold)
91 | )
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/CategoryChip.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.data.model.Category
5 | import com.canerture.androidhub.data.model.colorParse
6 | import com.canerture.androidhub.getSitePalette
7 | import com.varabyte.kobweb.compose.foundation.layout.Box
8 | import com.varabyte.kobweb.compose.ui.Alignment
9 | import com.varabyte.kobweb.compose.ui.Modifier
10 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
11 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
12 | import com.varabyte.kobweb.compose.ui.modifiers.color
13 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
14 | import com.varabyte.kobweb.compose.ui.modifiers.height
15 | import com.varabyte.kobweb.compose.ui.modifiers.padding
16 | import com.varabyte.kobweb.silk.components.text.SpanText
17 | import org.jetbrains.compose.web.css.px
18 |
19 | @Composable
20 | fun CategoryChip(category: Category) {
21 | Box(
22 | modifier = Modifier
23 | .height(32.px)
24 | .padding(leftRight = 14.px)
25 | .borderRadius(r = 100.px)
26 | .backgroundColor(colorParse(category.color)),
27 | contentAlignment = Alignment.Center
28 | ) {
29 | SpanText(
30 | modifier = Modifier
31 | .fontSize(12.px)
32 | .color(getSitePalette().white),
33 | text = category.name
34 | )
35 | }
36 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/ErrorView.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.getSitePalette
5 | import com.varabyte.kobweb.compose.css.FontWeight
6 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement
7 | import com.varabyte.kobweb.compose.foundation.layout.Column
8 | import com.varabyte.kobweb.compose.ui.Alignment
9 | import com.varabyte.kobweb.compose.ui.Modifier
10 | import com.varabyte.kobweb.compose.ui.modifiers.color
11 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
12 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
13 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
14 | import com.varabyte.kobweb.compose.ui.modifiers.padding
15 | import com.varabyte.kobweb.silk.components.icons.fa.FaX
16 | import com.varabyte.kobweb.silk.components.icons.fa.IconSize
17 | import com.varabyte.kobweb.silk.components.text.SpanText
18 | import org.jetbrains.compose.web.css.px
19 |
20 | @Composable
21 | fun ErrorView(
22 | message: String = ""
23 | ) {
24 | Column(
25 | modifier = Modifier
26 | .fillMaxSize(),
27 | horizontalAlignment = Alignment.CenterHorizontally,
28 | verticalArrangement = Arrangement.Center
29 | ) {
30 | FaX(
31 | size = IconSize.X6,
32 | modifier = Modifier
33 | .padding(bottom = 48.px)
34 | .color(getSitePalette().secondary)
35 | )
36 |
37 | SpanText(
38 | text = message.ifEmpty { "An error occurred while loading the page." },
39 | modifier = Modifier
40 | .color(getSitePalette().secondary)
41 | .fontSize(24.px)
42 | .fontWeight(FontWeight.Bold)
43 | )
44 | }
45 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/LoadingIndicator.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.LoaderStyle
5 | import com.varabyte.kobweb.compose.foundation.layout.Box
6 | import com.varabyte.kobweb.compose.ui.Alignment
7 | import com.varabyte.kobweb.compose.ui.Modifier
8 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
9 | import com.varabyte.kobweb.compose.ui.modifiers.height
10 | import com.varabyte.kobweb.compose.ui.modifiers.padding
11 | import com.varabyte.kobweb.silk.components.style.toAttrs
12 | import org.jetbrains.compose.web.css.px
13 | import org.jetbrains.compose.web.css.vh
14 | import org.jetbrains.compose.web.dom.Div
15 |
16 | @Composable
17 | fun LoadingIndicator() {
18 | Box(
19 | modifier = Modifier
20 | .fillMaxSize()
21 | .height(100.vh)
22 | .padding(topBottom = 50.px),
23 | contentAlignment = Alignment.Center
24 | ) {
25 | Div(
26 | attrs = LoaderStyle.toAttrs()
27 | )
28 | }
29 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/Popup.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.common.Id
5 | import com.canerture.androidhub.common.noBorder
6 | import com.canerture.androidhub.getSitePalette
7 | import com.canerture.androidhub.models.EditorControl
8 | import com.varabyte.kobweb.compose.foundation.layout.Box
9 | import com.varabyte.kobweb.compose.foundation.layout.Column
10 | import com.varabyte.kobweb.compose.ui.Alignment
11 | import com.varabyte.kobweb.compose.ui.Modifier
12 | import com.varabyte.kobweb.compose.ui.graphics.Colors
13 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
14 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
15 | import com.varabyte.kobweb.compose.ui.modifiers.color
16 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
17 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
18 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
19 | import com.varabyte.kobweb.compose.ui.modifiers.height
20 | import com.varabyte.kobweb.compose.ui.modifiers.id
21 | import com.varabyte.kobweb.compose.ui.modifiers.margin
22 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
23 | import com.varabyte.kobweb.compose.ui.modifiers.padding
24 | import com.varabyte.kobweb.compose.ui.modifiers.position
25 | import com.varabyte.kobweb.compose.ui.modifiers.width
26 | import com.varabyte.kobweb.compose.ui.modifiers.zIndex
27 | import com.varabyte.kobweb.compose.ui.toAttrs
28 | import com.varabyte.kobweb.silk.components.text.SpanText
29 | import kotlinx.browser.document
30 | import org.jetbrains.compose.web.attributes.InputType
31 | import org.jetbrains.compose.web.css.Position
32 | import org.jetbrains.compose.web.css.px
33 | import org.jetbrains.compose.web.css.vh
34 | import org.jetbrains.compose.web.css.vw
35 | import org.jetbrains.compose.web.dom.Button
36 | import org.jetbrains.compose.web.dom.Input
37 | import org.w3c.dom.HTMLInputElement
38 |
39 | @Composable
40 | fun ControlPopup(
41 | editorControl: EditorControl,
42 | onDialogDismiss: () -> Unit,
43 | onAddClick: (String, String) -> Unit
44 | ) {
45 | Box(
46 | modifier = Modifier
47 | .width(100.vw)
48 | .height(100.vh)
49 | .position(Position.Fixed)
50 | .zIndex(19),
51 | contentAlignment = Alignment.Center
52 | ) {
53 | Box(
54 | modifier = Modifier
55 | .fillMaxSize()
56 | .backgroundColor(getSitePalette().secondary)
57 | .onClick { onDialogDismiss() }
58 | )
59 | Column(
60 | modifier = Modifier
61 | .width(500.px)
62 | .padding(all = 24.px)
63 | .backgroundColor(Colors.White)
64 | .borderRadius(r = 4.px)
65 | ) {
66 | Input(
67 | type = InputType.Text,
68 | attrs = Modifier
69 | .id(Id.LINK_HREF_INPUT)
70 | .fillMaxWidth()
71 | .height(54.px)
72 | .padding(left = 20.px)
73 | .margin(bottom = 12.px)
74 | .fontSize(14.px)
75 | .noBorder()
76 | .borderRadius(r = 4.px)
77 | .backgroundColor(getSitePalette().white)
78 | .toAttrs {
79 | attr(
80 | "placeholder",
81 | if (editorControl == EditorControl.Link) "Href" else "Image URL"
82 | )
83 | }
84 | )
85 | Input(
86 | type = InputType.Text,
87 | attrs = Modifier
88 | .id(Id.LINK_TITLE_INPUT)
89 | .fillMaxWidth()
90 | .height(54.px)
91 | .padding(left = 20.px)
92 | .margin(bottom = 20.px)
93 | .fontSize(14.px)
94 | .noBorder()
95 | .borderRadius(r = 4.px)
96 | .backgroundColor(getSitePalette().white)
97 | .toAttrs {
98 | attr(
99 | "placeholder",
100 | if (editorControl == EditorControl.Link) "Title" else "Description"
101 | )
102 | }
103 | )
104 | Button(
105 | attrs = Modifier
106 | .onClick {
107 | val href =
108 | (document.getElementById(Id.LINK_HREF_INPUT) as HTMLInputElement).value
109 | val title =
110 | (document.getElementById(Id.LINK_TITLE_INPUT) as HTMLInputElement).value
111 | onAddClick(href, title)
112 | onDialogDismiss()
113 | }
114 | .fillMaxWidth()
115 | .height(54.px)
116 | .borderRadius(r = 4.px)
117 | .backgroundColor(getSitePalette().secondary)
118 | .color(Colors.White)
119 | .noBorder()
120 | .fontSize(14.px)
121 | .toAttrs()
122 | ) {
123 | SpanText(text = "Add")
124 | }
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/PostPreview.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.ShadowedGrayVariant
5 | import com.canerture.androidhub.common.parseDateString
6 | import com.canerture.androidhub.data.model.Post
7 | import com.canerture.androidhub.getSitePalette
8 | import com.varabyte.kobweb.compose.css.CSSTransition
9 | import com.varabyte.kobweb.compose.css.Cursor
10 | import com.varabyte.kobweb.compose.css.FontWeight
11 | import com.varabyte.kobweb.compose.css.Overflow
12 | import com.varabyte.kobweb.compose.css.TextOverflow
13 | import com.varabyte.kobweb.compose.css.TransitionProperty
14 | import com.varabyte.kobweb.compose.foundation.layout.Column
15 | import com.varabyte.kobweb.compose.ui.Modifier
16 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
17 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
18 | import com.varabyte.kobweb.compose.ui.modifiers.color
19 | import com.varabyte.kobweb.compose.ui.modifiers.cursor
20 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
21 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
22 | import com.varabyte.kobweb.compose.ui.modifiers.margin
23 | import com.varabyte.kobweb.compose.ui.modifiers.marginBlock
24 | import com.varabyte.kobweb.compose.ui.modifiers.marginInline
25 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
26 | import com.varabyte.kobweb.compose.ui.modifiers.overflow
27 | import com.varabyte.kobweb.compose.ui.modifiers.padding
28 | import com.varabyte.kobweb.compose.ui.modifiers.textOverflow
29 | import com.varabyte.kobweb.compose.ui.modifiers.transition
30 | import com.varabyte.kobweb.silk.components.style.toModifier
31 | import com.varabyte.kobweb.silk.components.text.SpanText
32 | import org.jetbrains.compose.web.css.cssRem
33 | import org.jetbrains.compose.web.css.ms
34 | import org.jetbrains.compose.web.css.px
35 |
36 | @Composable
37 | fun PostPreview(
38 | post: Post,
39 | onClick: (String) -> Unit
40 | ) {
41 | Column(
42 | modifier = ShadowedGrayVariant.toModifier()
43 | .marginInline(end = 1.5.cssRem)
44 | .marginBlock(end = 1.5.cssRem)
45 | .backgroundColor(getSitePalette().white)
46 | .borderRadius(r = 10.px)
47 | .padding(all = 18.px)
48 | .transition(CSSTransition(property = TransitionProperty.All, duration = 400.ms))
49 | .cursor(Cursor.Pointer)
50 | .onClick { onClick(post.short) }
51 | ) {
52 | SpanText(
53 | modifier = Modifier
54 | .fontSize(12.px)
55 | .color(getSitePalette().secondary),
56 | text = post.date.toLong().parseDateString()
57 | )
58 | SpanText(
59 | modifier = Modifier
60 | .weight(1f)
61 | .margin(topBottom = 12.px)
62 | .fontSize(20.px)
63 | .fontWeight(FontWeight.Bold)
64 | .color(getSitePalette().secondary)
65 | .textOverflow(TextOverflow.Ellipsis)
66 | .overflow(Overflow.Hidden),
67 | text = post.title
68 | )
69 | CategoryChip(category = post.category)
70 | }
71 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/PostsView.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.data.model.Post
5 | import com.varabyte.kobweb.compose.ui.Modifier
6 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
7 | import com.varabyte.kobweb.compose.ui.modifiers.margin
8 | import com.varabyte.kobweb.silk.components.layout.SimpleGrid
9 | import com.varabyte.kobweb.silk.components.layout.numColumns
10 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
11 | import com.varabyte.kobweb.silk.components.style.breakpoint.ResponsiveValues
12 | import org.jetbrains.compose.web.css.percent
13 | import org.jetbrains.compose.web.css.px
14 |
15 | @Composable
16 | fun PostsView(
17 | breakpoint: Breakpoint,
18 | posts: List,
19 | numColumns: ResponsiveValues = numColumns(base = 1, sm = 2, md = 3, lg = 3),
20 | onClick: (String) -> Unit
21 | ) {
22 | SimpleGrid(
23 | modifier = Modifier
24 | .fillMaxWidth(
25 | if (breakpoint > Breakpoint.MD) 80.percent
26 | else 90.percent
27 | )
28 | .margin(top = 40.px),
29 | numColumns = numColumns
30 | ) {
31 | posts.forEach {
32 | PostPreview(
33 | post = it,
34 | onClick = onClick
35 | )
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/components/widgets/SearchBar.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.components.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.common.Id
10 | import com.canerture.androidhub.common.noBorder
11 | import com.canerture.androidhub.getSitePalette
12 | import com.varabyte.kobweb.compose.css.CSSTransition
13 | import com.varabyte.kobweb.compose.css.Cursor
14 | import com.varabyte.kobweb.compose.foundation.layout.Row
15 | import com.varabyte.kobweb.compose.ui.Alignment
16 | import com.varabyte.kobweb.compose.ui.Modifier
17 | import com.varabyte.kobweb.compose.ui.graphics.Colors
18 | import com.varabyte.kobweb.compose.ui.modifiers.backgroundColor
19 | import com.varabyte.kobweb.compose.ui.modifiers.border
20 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
21 | import com.varabyte.kobweb.compose.ui.modifiers.color
22 | import com.varabyte.kobweb.compose.ui.modifiers.cursor
23 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
24 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
25 | import com.varabyte.kobweb.compose.ui.modifiers.height
26 | import com.varabyte.kobweb.compose.ui.modifiers.id
27 | import com.varabyte.kobweb.compose.ui.modifiers.margin
28 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
29 | import com.varabyte.kobweb.compose.ui.modifiers.onFocusIn
30 | import com.varabyte.kobweb.compose.ui.modifiers.onFocusOut
31 | import com.varabyte.kobweb.compose.ui.modifiers.onKeyDown
32 | import com.varabyte.kobweb.compose.ui.modifiers.padding
33 | import com.varabyte.kobweb.compose.ui.modifiers.transition
34 | import com.varabyte.kobweb.compose.ui.toAttrs
35 | import com.varabyte.kobweb.silk.components.icons.fa.FaMagnifyingGlass
36 | import com.varabyte.kobweb.silk.components.icons.fa.IconSize
37 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
38 | import org.jetbrains.compose.web.attributes.InputType
39 | import org.jetbrains.compose.web.css.LineStyle
40 | import org.jetbrains.compose.web.css.ms
41 | import org.jetbrains.compose.web.css.px
42 | import org.jetbrains.compose.web.dom.Input
43 |
44 | @Composable
45 | fun SearchBar(
46 | breakpoint: Breakpoint,
47 | modifier: Modifier = Modifier,
48 | onEnterClick: () -> Unit,
49 | onSearchIconClick: (Boolean) -> Unit
50 | ) {
51 | var focused by remember { mutableStateOf(false) }
52 |
53 | LaunchedEffect(breakpoint) {
54 | if (breakpoint >= Breakpoint.SM) onSearchIconClick(false)
55 | }
56 |
57 | if (breakpoint >= Breakpoint.SM) {
58 | Row(
59 | modifier = modifier
60 | .fillMaxWidth()
61 | .padding(left = 20.px)
62 | .height(54.px)
63 | .backgroundColor(getSitePalette().white)
64 | .borderRadius(r = 100.px)
65 | .border(
66 | width = 2.px,
67 | style = LineStyle.Solid,
68 | color = if (focused) getSitePalette().primary else getSitePalette().secondary
69 | )
70 | .transition(CSSTransition(property = "border", duration = 200.ms)),
71 | verticalAlignment = Alignment.CenterVertically
72 | ) {
73 | FaMagnifyingGlass(
74 | modifier = Modifier
75 | .margin(right = 14.px)
76 | .color(if (focused) getSitePalette().primary else getSitePalette().secondary)
77 | .transition(CSSTransition(property = "color", duration = 200.ms)),
78 | size = IconSize.SM
79 | )
80 | Input(
81 | type = InputType.Text,
82 | attrs = Modifier
83 | .id(Id.ADMIN_SEARCH_BAR)
84 | .fillMaxSize()
85 | .color(getSitePalette().secondary)
86 | .backgroundColor(Colors.Transparent)
87 | .noBorder()
88 | .onFocusIn { focused = true }
89 | .onFocusOut { focused = false }
90 | .onKeyDown {
91 | if (it.key == "Enter") {
92 | onEnterClick()
93 | }
94 | }
95 | .toAttrs {
96 | attr("placeholder", "Search...")
97 | }
98 | )
99 | }
100 | } else {
101 | FaMagnifyingGlass(
102 | modifier = Modifier
103 | .margin(right = 14.px)
104 | .color(getSitePalette().primary)
105 | .cursor(Cursor.Pointer)
106 | .onClick { onSearchIconClick(true) },
107 | size = IconSize.SM
108 | )
109 | }
110 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/CategoryApis.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data
2 |
3 | import com.canerture.androidhub.data.model.Category
4 | import com.canerture.androidhub.utils.ApiUtils.get
5 | import com.canerture.androidhub.utils.ApiUtils.safeApiCall
6 |
7 | suspend fun getCategories(
8 | onSuccess: (List) -> Unit = {},
9 | onError: (String) -> Unit = {}
10 | ) = safeApiCall>(
11 | call = { get("categories") },
12 | onSuccess = onSuccess,
13 | onError = onError
14 | )
15 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/PostApis.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data
2 |
3 | import com.canerture.androidhub.common.formatShort
4 | import com.canerture.androidhub.data.model.AddPostRequest
5 | import com.canerture.androidhub.data.model.AuthorDetail
6 | import com.canerture.androidhub.data.model.GetPostsResponse
7 | import com.canerture.androidhub.data.model.Post
8 | import com.canerture.androidhub.data.model.UpdatePostRequest
9 | import com.canerture.androidhub.utils.ApiUtils.delete
10 | import com.canerture.androidhub.utils.ApiUtils.get
11 | import com.canerture.androidhub.utils.ApiUtils.post
12 | import com.canerture.androidhub.utils.ApiUtils.put
13 | import com.canerture.androidhub.utils.ApiUtils.safeApiCall
14 | import kotlinx.browser.localStorage
15 | import kotlinx.serialization.encodeToString
16 | import kotlinx.serialization.json.Json
17 | import org.w3c.dom.get
18 | import kotlin.js.Date
19 |
20 | suspend fun getPosts(
21 | page: Int,
22 | onSuccess: (GetPostsResponse) -> Unit = {},
23 | onError: (String) -> Unit = {}
24 | ) = safeApiCall(
25 | call = { get("posts", "page" to page) },
26 | onSuccess = onSuccess,
27 | onError = onError
28 | )
29 |
30 | suspend fun getPopularPosts(
31 | onSuccess: (List) -> Unit = {},
32 | onError: (String) -> Unit = {}
33 | ) = safeApiCall>(
34 | call = { get("posts", "popular" to "") },
35 | onSuccess = onSuccess,
36 | onError = onError
37 | )
38 |
39 | suspend fun getPostDetail(
40 | short: String,
41 | onSuccess: (Post) -> Unit = {},
42 | onError: (String) -> Unit = {}
43 | ) = safeApiCall(
44 | call = { get("posts", "short" to short) },
45 | onSuccess = onSuccess,
46 | onError = onError
47 | )
48 |
49 | suspend fun getPendingPosts(
50 | onSuccess: (List) -> Unit = {},
51 | onError: (String) -> Unit = {}
52 | ) = safeApiCall>(
53 | call = { get("pending_posts") },
54 | onSuccess = onSuccess,
55 | onError = onError
56 | )
57 |
58 | suspend fun addPost(
59 | content: String,
60 | title: String,
61 | category: String,
62 | thumbnail: String,
63 | status: String,
64 | onSuccess: (String) -> Unit = {},
65 | onError: (String) -> Unit = {}
66 | ) = safeApiCall(
67 | call = {
68 | val id = localStorage["userId"] ?: ""
69 | val name = localStorage["name"] ?: ""
70 | val short = title.formatShort()
71 | post(
72 | path = "posts",
73 | body = Json.encodeToString(
74 | AddPostRequest(id, name, Date.now(), content, title, short, category, thumbnail, status)
75 | ).encodeToByteArray()
76 | )
77 | },
78 | onSuccess = onSuccess,
79 | onError = onError
80 | )
81 |
82 | suspend fun updatePost(
83 | id: Int,
84 | content: String,
85 | title: String,
86 | category: String,
87 | thumbnail: String,
88 | status: String,
89 | onSuccess: (String) -> Unit = {},
90 | onError: (String) -> Unit = {}
91 | ) = safeApiCall(
92 | call = {
93 | val short = title.formatShort()
94 | post(
95 | path = "posts",
96 | body = Json.encodeToString(
97 | UpdatePostRequest(id, Date.now(), content, title, short, category, thumbnail, status)
98 | ).encodeToByteArray()
99 | )
100 | },
101 | onSuccess = onSuccess,
102 | onError = onError
103 | )
104 |
105 | suspend fun searchPostsByTitle(
106 | title: String,
107 | onSuccess: (List) -> Unit = {},
108 | onError: (String) -> Unit = {}
109 | ) = safeApiCall>(
110 | call = { get("posts", "title" to title) },
111 | onSuccess = onSuccess,
112 | onError = onError
113 | )
114 |
115 | suspend fun getMyPosts(
116 | onSuccess: (AuthorDetail) -> Unit = {},
117 | onError: (String) -> Unit = {}
118 | ) = safeApiCall(
119 | call = { get("posts", "authorId" to localStorage["userId"].orEmpty()) },
120 | onSuccess = onSuccess,
121 | onError = onError
122 | )
123 |
124 | suspend fun deletePost(
125 | id: Int,
126 | onSuccess: (String) -> Unit = {},
127 | onError: (String) -> Unit = {}
128 | ) = safeApiCall(
129 | call = { delete("posts", "id" to id) },
130 | onSuccess = onSuccess,
131 | onError = onError
132 | )
133 |
134 | suspend fun getPostsByCategory(
135 | category: String,
136 | onSuccess: (List) -> Unit = {},
137 | onError: (String) -> Unit = {}
138 | ) = safeApiCall>(
139 | call = { get("posts", "category" to category) },
140 | onSuccess = onSuccess,
141 | onError = onError
142 | )
143 |
144 | suspend fun getPostsByAuthorId(
145 | authorId: String,
146 | onSuccess: (AuthorDetail) -> Unit = {},
147 | onError: (String) -> Unit = {}
148 | ) = safeApiCall(
149 | call = { get("posts", "authorId" to authorId) },
150 | onSuccess = onSuccess,
151 | onError = onError
152 | )
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/UserApis.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.data.model.LoginRequest
10 | import com.canerture.androidhub.data.model.RegisterRequest
11 | import com.canerture.androidhub.data.model.User
12 | import com.canerture.androidhub.utils.ApiUtils.get
13 | import com.canerture.androidhub.utils.ApiUtils.post
14 | import com.canerture.androidhub.utils.ApiUtils.safeApiCall
15 | import kotlinx.browser.localStorage
16 | import kotlinx.serialization.encodeToString
17 | import kotlinx.serialization.json.Json
18 | import org.w3c.dom.get
19 | import org.w3c.dom.set
20 |
21 | suspend fun login(
22 | email: String,
23 | password: String,
24 | onSuccess: (User) -> Unit = {},
25 | onError: (String) -> Unit = {}
26 | ) = safeApiCall(
27 | call = {
28 | post(
29 | path = "login",
30 | body = Json.encodeToString(LoginRequest(email, password)).encodeToByteArray()
31 | )
32 | },
33 | onSuccess = onSuccess,
34 | onError = onError
35 | )
36 |
37 | suspend fun register(
38 | name: String,
39 | email: String,
40 | password: String,
41 | onSuccess: (User) -> Unit = {},
42 | onError: (String) -> Unit = {}
43 | ) = safeApiCall(
44 | call = {
45 | post(
46 | path = "register",
47 | body = Json.encodeToString(RegisterRequest(name, email, password)).encodeToByteArray()
48 | )
49 | },
50 | onSuccess = onSuccess,
51 | onError = onError
52 | )
53 |
54 | suspend fun checkUserId(
55 | userId: String,
56 | onSuccess: (Boolean) -> Unit = {},
57 | onError: (String) -> Unit = {}
58 | ) = safeApiCall(
59 | call = { get("check_user_id", "userId" to userId) },
60 | onSuccess = onSuccess,
61 | onError = onError
62 | )
63 |
64 | @Composable
65 | fun isUserLoggedIn(): Boolean {
66 | val remembered = remember { localStorage["remember"].toBoolean() }
67 | val userId = remember { localStorage["userId"] }
68 | var userIdExists by remember { mutableStateOf(null) }
69 | LaunchedEffect(key1 = Unit) {
70 | if (!userId.isNullOrEmpty()) {
71 | checkUserId(
72 | userId = userId,
73 | onSuccess = {
74 | userIdExists = it
75 | },
76 | onError = {
77 | userIdExists = null
78 | }
79 | )
80 | }
81 | }
82 |
83 | return remembered && userIdExists ?: false
84 | }
85 |
86 | fun logout() {
87 | localStorage["remember"] = "false"
88 | localStorage["userId"] = ""
89 | localStorage["username"] = ""
90 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/model/AuthorDetail.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class AuthorDetail(
7 | val authorName: String,
8 | val posts: List,
9 | )
10 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/model/BaseResponse.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class BaseResponse(
7 | val status: Int? = null,
8 | val message: String? = null,
9 | val data: T? = null
10 | ) {
11 | fun isSuccess() = status == 200
12 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/model/Category.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import com.github.ajalt.colormath.model.RGBInt
4 | import com.github.ajalt.colormath.parse
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class Category(
9 | val id: Int = 0,
10 | val name: String = "",
11 | val short: String = "",
12 | val color: String = "",
13 | val parentCategory: String = "",
14 | )
15 |
16 | data class ParentCategory(
17 | val id: Int = 0,
18 | val name: String = "",
19 | val short: String = "",
20 | val color: String = "",
21 | val subCategories: MutableList = mutableListOf(),
22 | )
23 |
24 | data class SubCategory(
25 | val id: Int = 0,
26 | val name: String = "",
27 | val short: String = "",
28 | val color: String = "",
29 | val parentCategory: String = "",
30 | )
31 |
32 | fun colorParse(color: String): com.varabyte.kobweb.compose.ui.graphics.Color {
33 | return com.github.ajalt.colormath.Color.parse("#$color").toComposeColor()
34 | }
35 |
36 | fun com.github.ajalt.colormath.Color.toComposeColor(): com.varabyte.kobweb.compose.ui.graphics.Color {
37 | return if (this is RGBInt) {
38 | com.varabyte.kobweb.compose.ui.graphics.Color.rgb(argb.toInt())
39 | } else {
40 | val (r, g, b, a) = toSRGB()
41 | com.varabyte.kobweb.compose.ui.graphics.Color.rgba(r, g, b, a)
42 | }
43 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/model/GetPostsResponse.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class GetPostsResponse(
7 | val totalPage: Int,
8 | val posts: List
9 | )
10 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/model/LoginRequest.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class LoginRequest(
7 | val email: String,
8 | val password: String,
9 | )
10 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/model/Post.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class AddPostRequest(
7 | val authorId: String = "",
8 | val authorName: String = "",
9 | val date: Double = 0.0,
10 | val content: String = "",
11 | val title: String = "",
12 | val short: String = "",
13 | val category: String = "",
14 | val thumbnail: String = "",
15 | val status: String = "",
16 | )
17 |
18 | @Serializable
19 | data class UpdatePostRequest(
20 | val id: Int = 0,
21 | val date: Double = 0.0,
22 | val content: String = "",
23 | val title: String = "",
24 | val short: String = "",
25 | val category: String = "",
26 | val thumbnail: String = "",
27 | val status: String = "",
28 | )
29 |
30 | @Serializable
31 | data class Post(
32 | val id: Int = 0,
33 | val authorId: String = "",
34 | val authorName: String = "",
35 | val date: Double = 0.0,
36 | val content: String = "",
37 | val title: String = "",
38 | val short: String = "",
39 | val category: Category = Category(),
40 | val thumbnail: String = "",
41 | val commentCount: Int = 0,
42 | val status: String = "",
43 | )
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/model/RegisterRequest.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class RegisterRequest(
7 | val name: String,
8 | val email: String,
9 | val password: String,
10 | )
11 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/data/model/User.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class User(
7 | val userId: String,
8 | val name: String,
9 | val email: String,
10 | val isAdmin: Boolean
11 | )
12 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/models/ControlStyle.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.models
2 |
3 | import com.canerture.androidhub.getSitePalette
4 |
5 | sealed class ControlStyle(val style: String) {
6 | data class Bold(val selectedText: String?) : ControlStyle(
7 | style = "$selectedText"
8 | )
9 |
10 | data class Italic(val selectedText: String?) : ControlStyle(
11 | style = "$selectedText"
12 | )
13 |
14 | data class Link(
15 | val selectedText: String?,
16 | val href: String,
17 | val title: String
18 | ) : ControlStyle(
19 | style = "$selectedText"
20 | )
21 |
22 | data class Title(val selectedText: String?) : ControlStyle(
23 | style = "$selectedText
"
24 | )
25 |
26 | data class Subtitle(val selectedText: String?) : ControlStyle(
27 | style = "$selectedText
"
28 | )
29 |
30 | data class Quote(val selectedText: String?) : ControlStyle(
31 | style = "❞ $selectedText
"
32 | )
33 |
34 | data class Code(val selectedText: String?) : ControlStyle(
35 | style = ""
36 | )
37 |
38 | data class Image(
39 | val selectedText: String?,
40 | val imageUrl: String,
41 | val alt: String
42 | ) : ControlStyle(
43 | style = "
$selectedText"
44 | )
45 |
46 | data class Break(val selectedText: String?) : ControlStyle(
47 | style = "$selectedText
"
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/models/EditorControl.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.models
2 |
3 | import com.canerture.androidhub.common.Res
4 |
5 | enum class EditorControl(
6 | val icon: String,
7 | ) {
8 | Bold(icon = Res.Icon.BOLD),
9 | Italic(icon = Res.Icon.ITALIC),
10 | Link(icon = Res.Icon.LINK),
11 | Title(icon = Res.Icon.TITLE),
12 | Subtitle(icon = Res.Icon.SUBTITLE),
13 | Quote(icon = Res.Icon.QUOTE),
14 | Code(icon = Res.Icon.CODE),
15 | Image(icon = Res.Icon.IMAGE)
16 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/navigation/Screen.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.navigation
2 |
3 | import com.canerture.androidhub.common.Constants.CATEGORY_PARAM
4 | import com.canerture.androidhub.common.Constants.POST_AUTHOR_ID_PARAM
5 | import com.canerture.androidhub.common.Constants.POST_SHORT_PARAM
6 | import com.canerture.androidhub.common.Constants.QUERY_PARAM
7 |
8 | sealed class Screen(val route: String) {
9 | data object HomePage : Screen(route = "/")
10 |
11 | data object PostPage : Screen(route = "/posts") {
12 | fun getPost(short: String) = "/posts?${POST_SHORT_PARAM}=$short"
13 | }
14 |
15 | data object AuthorPage : Screen(route = "/author") {
16 | fun getAuthor(authorId: String) = "/author?${POST_AUTHOR_ID_PARAM}=$authorId"
17 | }
18 |
19 | data object CategoryPage : Screen(route = "/category") {
20 | fun getCategoryPosts(category: String) = "/category?${CATEGORY_PARAM}=${category}"
21 | }
22 |
23 | data object Login : Screen(route = "/login")
24 |
25 | data object AdminCreate : Screen(route = "/admin/create-post") {
26 | fun passPostId(short: String) = "/admin/create-post?${POST_SHORT_PARAM}=$short"
27 | }
28 |
29 | data object AdminMyPosts : Screen(route = "/admin/my-posts")
30 |
31 | data object AdminAllPosts : Screen(route = "/admin/all-posts")
32 |
33 | data object SearchPage : Screen(route = "/search") {
34 | fun searchByTitle(query: String) = "/search?${QUERY_PARAM}=$query"
35 | }
36 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/pages/adminallposts/AdminAllPosts.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.adminallposts
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.common.Constants.SIDE_PANEL_WIDTH
10 | import com.canerture.androidhub.components.layouts.AdminPageLayout
11 | import com.canerture.androidhub.components.widgets.PostsView
12 | import com.canerture.androidhub.data.getPendingPosts
13 | import com.canerture.androidhub.data.getPosts
14 | import com.canerture.androidhub.data.model.Post
15 | import com.canerture.androidhub.getSitePalette
16 | import com.canerture.androidhub.navigation.Screen
17 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement
18 | import com.varabyte.kobweb.compose.foundation.layout.Column
19 | import com.varabyte.kobweb.compose.foundation.layout.Row
20 | import com.varabyte.kobweb.compose.ui.Alignment
21 | import com.varabyte.kobweb.compose.ui.Modifier
22 | import com.varabyte.kobweb.compose.ui.modifiers.color
23 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
24 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
25 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
26 | import com.varabyte.kobweb.compose.ui.modifiers.margin
27 | import com.varabyte.kobweb.compose.ui.modifiers.padding
28 | import com.varabyte.kobweb.core.Page
29 | import com.varabyte.kobweb.core.rememberPageContext
30 | import com.varabyte.kobweb.silk.components.forms.Switch
31 | import com.varabyte.kobweb.silk.components.forms.SwitchSize
32 | import com.varabyte.kobweb.silk.components.layout.numColumns
33 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
34 | import com.varabyte.kobweb.silk.components.text.SpanText
35 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
36 | import org.jetbrains.compose.web.css.percent
37 | import org.jetbrains.compose.web.css.px
38 |
39 | data class AllPostsUIState(
40 | var isLoading: Boolean = true,
41 | var posts: List = emptyList(),
42 | var pendingPostEnabled: Boolean = false,
43 | )
44 |
45 | @Page("/admin/all-posts")
46 | @Composable
47 | fun AdminAllPosts() {
48 | val context = rememberPageContext()
49 | val breakpoint = rememberBreakpoint()
50 |
51 | var state by remember { mutableStateOf(AllPostsUIState()) }
52 |
53 | LaunchedEffect(state.pendingPostEnabled) {
54 | if (state.pendingPostEnabled) {
55 | getPendingPosts(
56 | onSuccess = {
57 | state = state.copy(isLoading = false, posts = it)
58 | }
59 | )
60 | } else {
61 | getPosts(
62 | page = 1,
63 | onSuccess = {
64 | state = state.copy(isLoading = false, posts = it.posts)
65 | }
66 | )
67 | }
68 | }
69 |
70 | AdminPageLayout {
71 | Column(
72 | modifier = Modifier
73 | .margin(topBottom = 60.px)
74 | .fillMaxSize()
75 | .padding(left = if (breakpoint > Breakpoint.MD) SIDE_PANEL_WIDTH.px else 0.px),
76 | verticalArrangement = Arrangement.Top,
77 | horizontalAlignment = Alignment.CenterHorizontally
78 | ) {
79 | Row(
80 | modifier = Modifier
81 | .fillMaxWidth(
82 | if (breakpoint > Breakpoint.MD) 80.percent
83 | else 90.percent
84 | )
85 | .margin(top = 80.px),
86 | horizontalArrangement = Arrangement.Start,
87 | verticalAlignment = Alignment.CenterVertically
88 | ) {
89 | Switch(
90 | modifier = Modifier.margin(right = 8.px),
91 | checked = state.pendingPostEnabled,
92 | onCheckedChange = { state = state.copy(pendingPostEnabled = it) },
93 | size = SwitchSize.MD
94 | )
95 | SpanText(
96 | modifier = Modifier
97 | .fontSize(14.px)
98 | .color(getSitePalette().secondary),
99 | text = "Show Pending Posts"
100 | )
101 | }
102 | PostsView(
103 | breakpoint = breakpoint,
104 | posts = state.posts,
105 | numColumns = numColumns(base = 1, sm = 1, md = 2, lg = 2),
106 | onClick = { context.router.navigateTo(Screen.AdminCreate.passPostId(short = it)) }
107 | )
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/pages/admincreatepost/Functions.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.admincreatepost
2 |
3 | import com.canerture.androidhub.common.Id
4 | import com.canerture.androidhub.models.ControlStyle
5 | import com.canerture.androidhub.models.EditorControl
6 | import kotlinx.browser.document
7 | import org.w3c.dom.HTMLTextAreaElement
8 |
9 | fun getEditor() = document.getElementById(Id.EDITOR) as HTMLTextAreaElement
10 |
11 | fun getSelectedIntRange(): IntRange? {
12 | val editor = getEditor()
13 | val start = editor.selectionStart
14 | val end = editor.selectionEnd
15 | return if (start != null && end != null) {
16 | IntRange(start, (end - 1))
17 | } else null
18 | }
19 |
20 | fun getSelectedText(): String? {
21 | val range = getSelectedIntRange()
22 | return if (range != null) {
23 | getEditor().value.substring(range)
24 | } else null
25 | }
26 |
27 | fun applyStyle(controlStyle: ControlStyle) {
28 | val selectedText = getSelectedText()
29 | val selectedIntRange = getSelectedIntRange()
30 | if (selectedIntRange != null && selectedText != null) {
31 | getEditor().value = getEditor().value.replaceRange(
32 | range = selectedIntRange,
33 | replacement = controlStyle.style
34 | )
35 | document.getElementById(Id.EDITOR_PREVIEW)?.innerHTML = getEditor().value
36 | }
37 | }
38 |
39 | fun applyControlStyle(
40 | editorControl: EditorControl,
41 | onLinkClick: () -> Unit,
42 | onImageClick: () -> Unit
43 | ) {
44 | when (editorControl) {
45 | EditorControl.Bold -> {
46 | applyStyle(
47 | ControlStyle.Bold(
48 | selectedText = getSelectedText()
49 | )
50 | )
51 | }
52 |
53 | EditorControl.Italic -> {
54 | applyStyle(
55 | ControlStyle.Italic(
56 | selectedText = getSelectedText()
57 | )
58 | )
59 | }
60 |
61 | EditorControl.Link -> {
62 | onLinkClick()
63 | }
64 |
65 | EditorControl.Title -> {
66 | applyStyle(
67 | ControlStyle.Title(
68 | selectedText = getSelectedText()
69 | )
70 | )
71 | }
72 |
73 | EditorControl.Subtitle -> {
74 | applyStyle(
75 | ControlStyle.Subtitle(
76 | selectedText = getSelectedText(),
77 | )
78 | )
79 | }
80 |
81 | EditorControl.Quote -> {
82 | applyStyle(
83 | ControlStyle.Quote(
84 | selectedText = getSelectedText()
85 | )
86 | )
87 | }
88 |
89 | EditorControl.Code -> {
90 | applyStyle(
91 | ControlStyle.Code(
92 | selectedText = getSelectedText()
93 | )
94 | )
95 | }
96 |
97 | EditorControl.Image -> {
98 | onImageClick()
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/pages/adminmyposts/AdminMyPosts.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.adminmyposts
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.rememberCoroutineScope
9 | import androidx.compose.runtime.setValue
10 | import com.canerture.androidhub.common.Constants.SIDE_PANEL_WIDTH
11 | import com.canerture.androidhub.common.Id
12 | import com.canerture.androidhub.components.layouts.AdminPageLayout
13 | import com.canerture.androidhub.components.widgets.ErrorView
14 | import com.canerture.androidhub.components.widgets.LoadingIndicator
15 | import com.canerture.androidhub.components.widgets.PostsView
16 | import com.canerture.androidhub.components.widgets.SearchBar
17 | import com.canerture.androidhub.data.getMyPosts
18 | import com.canerture.androidhub.data.model.Post
19 | import com.canerture.androidhub.data.searchPostsByTitle
20 | import com.canerture.androidhub.navigation.Screen
21 | import com.varabyte.kobweb.compose.css.CSSTransition
22 | import com.varabyte.kobweb.compose.css.TransitionProperty
23 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement
24 | import com.varabyte.kobweb.compose.foundation.layout.Box
25 | import com.varabyte.kobweb.compose.foundation.layout.Column
26 | import com.varabyte.kobweb.compose.ui.Alignment
27 | import com.varabyte.kobweb.compose.ui.Modifier
28 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize
29 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
30 | import com.varabyte.kobweb.compose.ui.modifiers.margin
31 | import com.varabyte.kobweb.compose.ui.modifiers.padding
32 | import com.varabyte.kobweb.compose.ui.modifiers.transition
33 | import com.varabyte.kobweb.core.Page
34 | import com.varabyte.kobweb.core.rememberPageContext
35 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
36 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
37 | import kotlinx.browser.document
38 | import kotlinx.coroutines.launch
39 | import org.jetbrains.compose.web.css.ms
40 | import org.jetbrains.compose.web.css.percent
41 | import org.jetbrains.compose.web.css.px
42 | import org.w3c.dom.HTMLInputElement
43 |
44 | data class AdminMyPostsUIState(
45 | var isLoading: Boolean = true,
46 | var posts: List = emptyList(),
47 | var isError: Boolean = false,
48 | )
49 |
50 | @Page("/admin/my-posts")
51 | @Composable
52 | fun MyPostsScreen() {
53 | val context = rememberPageContext()
54 | val breakpoint = rememberBreakpoint()
55 | val scope = rememberCoroutineScope()
56 |
57 | var state by remember { mutableStateOf(AdminMyPostsUIState()) }
58 |
59 | LaunchedEffect(context.route) {
60 | getMyPosts(
61 | onSuccess = {
62 | state = state.copy(isLoading = false, posts = it.posts)
63 | },
64 | onError = {
65 | state = state.copy(isLoading = false, isError = true)
66 | }
67 | )
68 | }
69 |
70 | AdminPageLayout {
71 | Column(
72 | modifier = Modifier
73 | .margin(topBottom = 50.px)
74 | .fillMaxSize()
75 | .padding(left = if (breakpoint > Breakpoint.MD) SIDE_PANEL_WIDTH.px else 0.px),
76 | verticalArrangement = Arrangement.Top,
77 | horizontalAlignment = Alignment.CenterHorizontally
78 | ) {
79 | Box(
80 | modifier = Modifier
81 | .fillMaxWidth(
82 | if (breakpoint > Breakpoint.MD) 30.percent
83 | else 50.percent
84 | )
85 | .margin(bottom = 24.px),
86 | contentAlignment = Alignment.Center
87 | ) {
88 | SearchBar(
89 | breakpoint = breakpoint,
90 | modifier = Modifier
91 | .transition(CSSTransition(property = TransitionProperty.All, duration = 200.ms)),
92 | onEnterClick = {
93 | val searchInput =
94 | (document.getElementById(Id.ADMIN_SEARCH_BAR) as HTMLInputElement).value
95 | scope.launch {
96 | state = state.copy(isLoading = true)
97 | if (searchInput.isNotEmpty()) {
98 | searchPostsByTitle(
99 | title = searchInput,
100 | onSuccess = {
101 | state = state.copy(isLoading = false, posts = it)
102 | },
103 | onError = {
104 | state = state.copy(isLoading = false, isError = true)
105 | }
106 | )
107 | } else {
108 | getMyPosts(
109 | onSuccess = {
110 | state = state.copy(isLoading = false, posts = it.posts)
111 | },
112 | )
113 | }
114 | }
115 | },
116 | onSearchIconClick = {}
117 | )
118 | }
119 |
120 | if (state.isLoading) {
121 | LoadingIndicator()
122 | } else {
123 | if (state.isError) {
124 | ErrorView("You don't have a post yet")
125 | return@Column
126 | }
127 | PostsView(
128 | breakpoint = breakpoint,
129 | posts = state.posts,
130 | onClick = { context.router.navigateTo(Screen.AdminCreate.passPostId(short = it)) }
131 | )
132 | }
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/pages/author/Author.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.author
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.common.Constants.POST_AUTHOR_ID_PARAM
10 | import com.canerture.androidhub.components.layouts.PageLayout
11 | import com.canerture.androidhub.components.widgets.ErrorView
12 | import com.canerture.androidhub.components.widgets.LoadingIndicator
13 | import com.canerture.androidhub.data.getPostsByAuthorId
14 | import com.canerture.androidhub.data.model.Post
15 | import com.canerture.androidhub.getSitePalette
16 | import com.canerture.androidhub.pages.index.LatestArticleItem
17 | import com.varabyte.kobweb.compose.css.FontWeight
18 | import com.varabyte.kobweb.compose.css.TextAlign
19 | import com.varabyte.kobweb.compose.foundation.layout.Row
20 | import com.varabyte.kobweb.compose.ui.Alignment
21 | import com.varabyte.kobweb.compose.ui.Modifier
22 | import com.varabyte.kobweb.compose.ui.modifiers.alignContent
23 | import com.varabyte.kobweb.compose.ui.modifiers.color
24 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
25 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
26 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
27 | import com.varabyte.kobweb.compose.ui.modifiers.height
28 | import com.varabyte.kobweb.compose.ui.modifiers.margin
29 | import com.varabyte.kobweb.compose.ui.modifiers.padding
30 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
31 | import com.varabyte.kobweb.core.Page
32 | import com.varabyte.kobweb.core.rememberPageContext
33 | import com.varabyte.kobweb.silk.components.icons.fa.FaBoltLightning
34 | import com.varabyte.kobweb.silk.components.layout.SimpleGrid
35 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
36 | import com.varabyte.kobweb.silk.components.style.breakpoint.ResponsiveValues
37 | import com.varabyte.kobweb.silk.components.text.SpanText
38 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
39 | import kotlinx.browser.document
40 | import org.jetbrains.compose.web.css.AlignContent
41 | import org.jetbrains.compose.web.css.cssRem
42 | import org.jetbrains.compose.web.css.px
43 |
44 | data class AuthorUIState(
45 | val isLoading: Boolean = true,
46 | val posts: List = emptyList(),
47 | val authorId: String = "",
48 | val authorName: String = "",
49 | val isError: Boolean = false
50 | )
51 |
52 | @Page("/author")
53 | @Composable
54 | fun Author() {
55 | val context = rememberPageContext()
56 |
57 | var state by remember { mutableStateOf(AuthorUIState()) }
58 |
59 | state = state.copy(authorId = context.route.queryParams[POST_AUTHOR_ID_PARAM].orEmpty())
60 |
61 | LaunchedEffect(state.authorId) {
62 | state = state.copy(isLoading = true)
63 | document.title = state.authorName
64 | getPostsByAuthorId(
65 | authorId = state.authorId,
66 | onSuccess = {
67 | state = state.copy(isLoading = false, authorName = it.authorName, posts = it.posts)
68 | },
69 | onError = {
70 | state = state.copy(isLoading = false, isError = true)
71 | }
72 | )
73 | }
74 |
75 | if (state.isLoading) {
76 | LoadingIndicator()
77 | } else {
78 | PageLayout("Home") {
79 | if (state.isError) {
80 | ErrorView()
81 | return@PageLayout
82 | }
83 |
84 | if (state.posts.isNotEmpty()) {
85 | AuthorPosts(state.authorName, state.posts)
86 | }
87 | }
88 | }
89 | }
90 |
91 | @Composable
92 | private fun AuthorPosts(authorName: String, list: List) {
93 | val breakpoint = rememberBreakpoint()
94 | Row(
95 | modifier = Modifier
96 | .fillMaxWidth()
97 | .padding(bottom = 2.cssRem, top = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem, leftRight = 6.cssRem),
98 | verticalAlignment = Alignment.CenterVertically
99 | ) {
100 | FaBoltLightning(
101 | modifier = Modifier
102 | .height(20.px)
103 | .color(getSitePalette().primary)
104 | .margin(right = 0.5.cssRem)
105 | .alignContent(AlignContent.Start),
106 | )
107 | SpanText(
108 | text = authorName,
109 | modifier = Modifier
110 | .textAlign(TextAlign.Center)
111 | .fontWeight(FontWeight.Bold)
112 | .fontSize(1.25.cssRem)
113 | .color(getSitePalette().secondary.toRgb())
114 | )
115 | }
116 |
117 | SimpleGrid(
118 | modifier = Modifier.fillMaxWidth().padding(leftRight = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem),
119 | numColumns = ResponsiveValues(1, 1, 3, 3, 3),
120 | content = {
121 | list.forEach { article ->
122 | LatestArticleItem(
123 | article = article
124 | )
125 | }
126 | }
127 | )
128 | }
129 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/pages/category/Category.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.category
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.components.layouts.PageLayout
10 | import com.canerture.androidhub.components.widgets.ErrorView
11 | import com.canerture.androidhub.components.widgets.LoadingIndicator
12 | import com.canerture.androidhub.data.getPostsByCategory
13 | import com.canerture.androidhub.data.model.Post
14 | import com.canerture.androidhub.getSitePalette
15 | import com.canerture.androidhub.pages.index.LatestArticleItem
16 | import com.varabyte.kobweb.compose.css.FontWeight
17 | import com.varabyte.kobweb.compose.css.TextAlign
18 | import com.varabyte.kobweb.compose.foundation.layout.Row
19 | import com.varabyte.kobweb.compose.ui.Alignment
20 | import com.varabyte.kobweb.compose.ui.Modifier
21 | import com.varabyte.kobweb.compose.ui.modifiers.alignContent
22 | import com.varabyte.kobweb.compose.ui.modifiers.color
23 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
24 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
25 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
26 | import com.varabyte.kobweb.compose.ui.modifiers.height
27 | import com.varabyte.kobweb.compose.ui.modifiers.margin
28 | import com.varabyte.kobweb.compose.ui.modifiers.padding
29 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
30 | import com.varabyte.kobweb.core.Page
31 | import com.varabyte.kobweb.core.rememberPageContext
32 | import com.varabyte.kobweb.silk.components.icons.fa.FaBoltLightning
33 | import com.varabyte.kobweb.silk.components.layout.SimpleGrid
34 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
35 | import com.varabyte.kobweb.silk.components.style.breakpoint.ResponsiveValues
36 | import com.varabyte.kobweb.silk.components.text.SpanText
37 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
38 | import kotlinx.browser.document
39 | import org.jetbrains.compose.web.css.AlignContent
40 | import org.jetbrains.compose.web.css.cssRem
41 | import org.jetbrains.compose.web.css.px
42 |
43 | data class CategoryUIState(
44 | val isLoading: Boolean = true,
45 | val posts: List = emptyList(),
46 | val category: String = "",
47 | val isError: Boolean = false
48 | )
49 |
50 | @Page("/category")
51 | @Composable
52 | fun Category() {
53 | val context = rememberPageContext()
54 |
55 | var state by remember { mutableStateOf(CategoryUIState()) }
56 |
57 | state = state.copy(category = context.route.queryParams["category"].orEmpty())
58 |
59 | LaunchedEffect(state.category) {
60 | state = state.copy(isLoading = true)
61 | document.title = state.category
62 | getPostsByCategory(
63 | category = state.category,
64 | onSuccess = { posts ->
65 | state = CategoryUIState(
66 | isLoading = false,
67 | posts = posts
68 | )
69 | },
70 | onError = {
71 | state = state.copy(isLoading = false, isError = true)
72 | }
73 | )
74 | }
75 |
76 | if (state.isLoading) {
77 | LoadingIndicator()
78 | } else {
79 | PageLayout("Home") {
80 | if (state.isError) {
81 | ErrorView()
82 | return@PageLayout
83 | }
84 |
85 | if (state.posts.isNotEmpty()) {
86 | CategoryPosts(state.category, state.posts)
87 | }
88 | }
89 | }
90 | }
91 |
92 | @Composable
93 | private fun CategoryPosts(category: String, list: List) {
94 | val breakpoint = rememberBreakpoint()
95 | Row(
96 | modifier = Modifier
97 | .fillMaxWidth()
98 | .padding(bottom = 2.cssRem, top = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem, leftRight = 6.cssRem),
99 | verticalAlignment = Alignment.CenterVertically
100 | ) {
101 | FaBoltLightning(
102 | modifier = Modifier
103 | .height(20.px)
104 | .color(getSitePalette().primary)
105 | .margin(right = 0.5.cssRem)
106 | .alignContent(AlignContent.Start),
107 | )
108 | SpanText(
109 | text = category,
110 | modifier = Modifier
111 | .textAlign(TextAlign.Center)
112 | .fontWeight(FontWeight.Bold)
113 | .fontSize(1.25.cssRem)
114 | .color(getSitePalette().secondary.toRgb())
115 | )
116 | }
117 |
118 | SimpleGrid(
119 | modifier = Modifier.fillMaxWidth().padding(leftRight = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem),
120 | numColumns = ResponsiveValues(1, 2, 3, 3, 3),
121 | content = {
122 | list.forEach { article ->
123 | LatestArticleItem(
124 | article = article
125 | )
126 | }
127 | }
128 | )
129 | }
130 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/pages/index/IOSHeroContent.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.index
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.canerture.androidhub.common.Res
5 | import com.canerture.androidhub.getSitePalette
6 | import com.varabyte.kobweb.compose.css.AlignContent
7 | import com.varabyte.kobweb.compose.css.FontWeight
8 | import com.varabyte.kobweb.compose.css.TextAlign
9 | import com.varabyte.kobweb.compose.foundation.layout.Column
10 | import com.varabyte.kobweb.compose.foundation.layout.Row
11 | import com.varabyte.kobweb.compose.foundation.layout.Spacer
12 | import com.varabyte.kobweb.compose.ui.Alignment
13 | import com.varabyte.kobweb.compose.ui.Modifier
14 | import com.varabyte.kobweb.compose.ui.modifiers.alignContent
15 | import com.varabyte.kobweb.compose.ui.modifiers.color
16 | import com.varabyte.kobweb.compose.ui.modifiers.display
17 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
18 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
19 | import com.varabyte.kobweb.compose.ui.modifiers.lineHeight
20 | import com.varabyte.kobweb.compose.ui.modifiers.margin
21 | import com.varabyte.kobweb.compose.ui.modifiers.padding
22 | import com.varabyte.kobweb.compose.ui.modifiers.size
23 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
24 | import com.varabyte.kobweb.silk.components.graphics.Image
25 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
26 | import com.varabyte.kobweb.silk.components.text.SpanText
27 | import org.jetbrains.compose.web.css.Color
28 | import org.jetbrains.compose.web.css.DisplayStyle
29 | import org.jetbrains.compose.web.css.cssRem
30 | import org.jetbrains.compose.web.css.px
31 |
32 | @Composable
33 | fun AndroidHeroContent(
34 | breakpoint: Breakpoint
35 | ) {
36 | if (breakpoint < Breakpoint.SM) {
37 | Column(
38 | horizontalAlignment = Alignment.CenterHorizontally
39 | ) {
40 | Image(
41 | src = Res.Image.IOS_FIGURE,
42 | description = "iOS Figure",
43 | modifier = Modifier
44 | .size(140.px, 160.px)
45 | .display(DisplayStyle.Block)
46 | )
47 | TextArea(
48 | breakpoint = breakpoint,
49 | modifier = Modifier
50 | .padding(leftRight = 2.cssRem)
51 | .alignContent(AlignContent.Center)
52 | .margin(top = 64.px)
53 | )
54 | }
55 | } else {
56 | Row(
57 | modifier = Modifier.padding(leftRight = 13.cssRem),
58 | verticalAlignment = Alignment.CenterVertically
59 | ) {
60 | TextArea(breakpoint)
61 | Image(
62 | src = Res.Image.IOS_FIGURE,
63 | description = "iOS Figure",
64 | modifier = Modifier
65 | .size(260.px)
66 | .margin(left = 2.cssRem)
67 | .display(DisplayStyle.Block)
68 | )
69 | }
70 | }
71 | }
72 |
73 | @Composable
74 | private fun TextArea(breakpoint: Breakpoint, modifier: Modifier = Modifier) {
75 | Column(
76 | modifier = modifier
77 | ) {
78 | SpanText(
79 | text = "Hi \uD83D\uDC4B",
80 | modifier = Modifier
81 | .fontWeight(FontWeight.Bold)
82 | .textAlign(TextAlign.Start)
83 | .color(getSitePalette().primary)
84 | .fontSize(if (breakpoint < Breakpoint.MD) 18.px else 28.px)
85 | )
86 | Spacer()
87 | SpanText(
88 | text = "Let’s explore everything about the exciting world of iOS together. On this platform, you can delve into the vast opportunities offered by the iOS operating system and examine the latest developments in the field. Come on, let’s discover the limitless world of iOS together!",
89 | modifier = Modifier
90 | .textAlign(TextAlign.Start)
91 | .color(Color.gray)
92 | .fontSize(if (breakpoint < Breakpoint.MD) 14.px else 20.px)
93 | .lineHeight(2)
94 | .padding(right = 2.cssRem)
95 | )
96 | }
97 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/pages/search/Search.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.pages.search
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import com.canerture.androidhub.components.layouts.PageLayout
10 | import com.canerture.androidhub.components.widgets.ErrorView
11 | import com.canerture.androidhub.components.widgets.LoadingIndicator
12 | import com.canerture.androidhub.data.model.Post
13 | import com.canerture.androidhub.data.searchPostsByTitle
14 | import com.canerture.androidhub.getSitePalette
15 | import com.canerture.androidhub.pages.index.LatestArticleItem
16 | import com.varabyte.kobweb.compose.css.FontWeight
17 | import com.varabyte.kobweb.compose.css.TextAlign
18 | import com.varabyte.kobweb.compose.foundation.layout.Row
19 | import com.varabyte.kobweb.compose.ui.Alignment
20 | import com.varabyte.kobweb.compose.ui.Modifier
21 | import com.varabyte.kobweb.compose.ui.modifiers.alignContent
22 | import com.varabyte.kobweb.compose.ui.modifiers.color
23 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
24 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
25 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
26 | import com.varabyte.kobweb.compose.ui.modifiers.height
27 | import com.varabyte.kobweb.compose.ui.modifiers.margin
28 | import com.varabyte.kobweb.compose.ui.modifiers.padding
29 | import com.varabyte.kobweb.compose.ui.modifiers.textAlign
30 | import com.varabyte.kobweb.core.Page
31 | import com.varabyte.kobweb.core.rememberPageContext
32 | import com.varabyte.kobweb.silk.components.icons.fa.FaBoltLightning
33 | import com.varabyte.kobweb.silk.components.layout.SimpleGrid
34 | import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint
35 | import com.varabyte.kobweb.silk.components.style.breakpoint.ResponsiveValues
36 | import com.varabyte.kobweb.silk.components.text.SpanText
37 | import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint
38 | import kotlinx.browser.document
39 | import org.jetbrains.compose.web.css.AlignContent
40 | import org.jetbrains.compose.web.css.cssRem
41 | import org.jetbrains.compose.web.css.px
42 |
43 | data class SearchUIState(
44 | val isLoading: Boolean = true,
45 | val posts: List = emptyList(),
46 | val query: String = "",
47 | val isError: Boolean = false,
48 | )
49 |
50 | @Page("/search")
51 | @Composable
52 | fun Search() {
53 | val context = rememberPageContext()
54 |
55 | var state by remember { mutableStateOf(SearchUIState()) }
56 |
57 | state = state.copy(query = context.route.queryParams["query"].orEmpty())
58 |
59 | LaunchedEffect(state.query) {
60 | state = state.copy(isLoading = true)
61 | document.title = state.query
62 | searchPostsByTitle(
63 | title = state.query,
64 | onSuccess = { posts ->
65 | state = SearchUIState(
66 | isLoading = false,
67 | posts = posts
68 | )
69 | },
70 | onError = {
71 | state = state.copy(
72 | isLoading = false,
73 | isError = true
74 | )
75 | }
76 | )
77 | }
78 |
79 | if (state.isLoading) {
80 | LoadingIndicator()
81 | } else {
82 | PageLayout("Search") {
83 | if (state.isError) {
84 | ErrorView()
85 | return@PageLayout
86 | }
87 |
88 | if (state.posts.isNotEmpty()) {
89 | SearchPosts(state.query, state.posts)
90 | }
91 | }
92 | }
93 | }
94 |
95 | @Composable
96 | private fun SearchPosts(query: String, list: List) {
97 | val breakpoint = rememberBreakpoint()
98 | Row(
99 | modifier = Modifier
100 | .fillMaxWidth()
101 | .padding(bottom = 2.cssRem, top = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem, leftRight = 6.cssRem),
102 | verticalAlignment = Alignment.CenterVertically
103 | ) {
104 | FaBoltLightning(
105 | modifier = Modifier
106 | .height(if (breakpoint < Breakpoint.MD) 10.px else 20.px)
107 | .color(getSitePalette().primary)
108 | .margin(right = 0.5.cssRem)
109 | .alignContent(AlignContent.Center),
110 | )
111 | SpanText(
112 | text = "${list.size} posts found for \"$query\"",
113 | modifier = Modifier
114 | .textAlign(TextAlign.Center)
115 | .fontWeight(FontWeight.Bold)
116 | .fontSize(if (breakpoint < Breakpoint.MD) 14.px else 18.px)
117 | .color(getSitePalette().secondary.toRgb())
118 | )
119 | }
120 |
121 | SimpleGrid(
122 | modifier = Modifier.fillMaxWidth().padding(leftRight = if (breakpoint < Breakpoint.MD) 1.cssRem else 6.cssRem),
123 | numColumns = ResponsiveValues(1, 1, 3, 3, 3),
124 | content = {
125 | list.forEach { article ->
126 | LatestArticleItem(
127 | article = article
128 | )
129 | }
130 | }
131 | )
132 | }
133 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/kotlin/com/canerture/androidhub/utils/ApiUtils.kt:
--------------------------------------------------------------------------------
1 | package com.canerture.androidhub.utils
2 |
3 | import com.canerture.androidhub.common.Constants
4 | import com.canerture.androidhub.data.model.BaseResponse
5 | import com.varabyte.kobweb.browser.http.HttpMethod
6 | import com.varabyte.kobweb.browser.http.fetch
7 | import com.varabyte.kobweb.browser.http.http
8 | import kotlinx.browser.window
9 | import kotlinx.serialization.decodeFromString
10 | import kotlinx.serialization.json.Json
11 |
12 | object ApiUtils {
13 | inline fun safeApiCall(
14 | call: () -> ByteArray,
15 | onSuccess: (T) -> Unit,
16 | onError: (String) -> Unit,
17 | ) {
18 | try {
19 | val response = call().parseData>()
20 | if (response.status == 200) {
21 | onSuccess(response.data!!)
22 | } else {
23 | onError(response.message.orEmpty())
24 | }
25 | } catch (e: Exception) {
26 | onError("Something went wrong!")
27 | }
28 | }
29 |
30 | suspend fun get(path: String, param: Pair? = null): ByteArray {
31 | val url = if (param == null) {
32 | Constants.BASE_URL.plus(path)
33 | } else {
34 | Constants.BASE_URL.plus(path).plus("?${param.first}=${param.second}")
35 | }
36 | return window.http.get(url)
37 | }
38 |
39 | suspend fun post(path: String, body: ByteArray): ByteArray {
40 | return window.http.post(
41 | resource = Constants.BASE_URL.plus(path),
42 | body = body
43 | )
44 | }
45 |
46 | suspend fun put(path: String, body: ByteArray): ByteArray {
47 | return window.http.put(
48 | resource = Constants.BASE_URL.plus(path),
49 | body = body
50 | )
51 | }
52 |
53 | suspend fun delete(path: String, param: Pair? = null): ByteArray {
54 | val url = if (param == null) {
55 | Constants.BASE_URL.plus(path)
56 | } else {
57 | Constants.BASE_URL.plus(path).plus("?${param.first}=${param.second}")
58 | }
59 | return window.http.delete(url)
60 | }
61 |
62 | inline fun ByteArray?.parseData(): T {
63 | val json = this?.decodeToString().orEmpty()
64 | return Json.decodeFromString(json)
65 | }
66 | }
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/markdown/About.md:
--------------------------------------------------------------------------------
1 | ---
2 | root: .components.layouts.MarkdownLayout("About")
3 | ---
4 |
5 | # About this template
6 |
7 | This template is intended to both demonstrate some fundamentals of the Kobweb framework and act a
8 | starting point you can
9 | build your own site from.
10 |
11 | ---
12 |
13 | ## Learn
14 |
15 | If this is your first time using Kobweb, please open this site's project in an IDE and take a few
16 | minutes to look around
17 | the code.
18 |
19 | ### Files
20 |
21 | #### AppEntry.kt
22 |
23 | This file declares a method that is an entry point for all pages on your site. You can rename the
24 | file and the method if
25 | you like. Kobweb searches for a single method at compile time annotated with `@App`.
26 |
27 | #### AppStyles.kt
28 |
29 | An example of declaring generally useful styles that can be used anywhere across the whole site.
30 | Otherwise, you normally
31 | declare styles close to the widget that uses them.
32 |
33 | #### SiteTheme.kt
34 |
35 | An example of how to define some site-specific colors, effectively extending the palette provided by
36 | Silk.
37 |
38 | #### components/
39 |
40 | By convention, Kobweb codebases organize reusable site components under this folder. Within it, you
41 | have:
42 |
43 | * `layout/`
44 | Represents top-level organization for pages
45 | * `sections/`
46 | Areas of content that appear across multiple pages (such as nav bars and footers)
47 | * `widgets/`
48 | Home for low-level UI pieces that you can use around your site
49 |
50 | #### pages/
51 |
52 | Any `@Composable` under this folder additionally tagged with `@Page` will have a route generated for
53 | it automatically.
54 | Defining a page outside of this folder will be flagged as an error by the Kobweb Gradle plugin at
55 | compile time. Note
56 | that additional pages (like this one!) might live under the `resources/markdown` folder.
57 |
58 | #### resources/
59 |
60 | * `public`
61 | If you want to host any media on your site (such as an icon, an image, text configuration files,
62 | movies, fonts, etc.),
63 | you should put it under this folder.
64 | * `markdown`
65 | Any markdown discovered in here by Kobweb at compile time will be converted into pages on your
66 | site.
67 |
68 | ### Classes
69 |
70 | The Kobweb and Silk APIs introduce a lot of powerful concepts. Here are some of the most important
71 | ones to know about
72 | which you can find used throughout this template.
73 |
74 | #### Modifier
75 |
76 | Kobweb introduces the `Modifier` keyword that Android developers will recognize from the Jetpack
77 | Compose API. In a
78 | webdev context, this is used for setting CSS styles and html attributes on elements in the page.
79 |
80 | #### ComponentStyle
81 |
82 | Traditional HTML pages use CSS to style their UI. In Kobweb, these styles can be declared using
83 | the `ComponentStyle`
84 | class in a Kotlin-idiomatic way. You can find examples of component styles used throughout the
85 | template.
86 |
87 | #### ComponentVariant
88 |
89 | You can generate variants from component styles, which are ways to take base component styles and
90 | tweak them further.
91 |
92 | #### Keyframes
93 |
94 | You can create animations by declaring keyframes for them.
95 |
96 | ## Starting Point
97 |
98 | This template aims to create some generally useful pieces that most sites will want to use. Making
99 | your own site could
100 | be as easy as deleting this *About* page and working from there. However, you are welcome to modify
101 | or delete anything
102 | you find in the template that you don't plan to use in your final site.
103 |
104 | If instead you'd like to start from scratch, you can run
105 |
106 | ```
107 | $ kobweb create app/empty
108 | ```
109 |
110 | which will create a new project with nothing inside of it except for a minimal, skeletal structure.
111 |
112 | ## Export and Deploy
113 |
114 | When you are ready to share your site with the world, you'll want to export it first. This will
115 | create a production
116 | snapshot of your site.
117 |
118 | There are two flavors of Kobweb sites: *static layout* and *full stack*. You
119 | can [read more about these choices here](https://github.com/varabyte/kobweb#static-layout-vs-full-stack-sites).
120 |
121 | For most sites, a static layout site is what you want, so to do that, return to the command line and
122 | run:
123 |
124 | ```
125 | $ kobweb export --layout static
126 | ```
127 |
128 | After that runs for a little while, your production site should be generated! You can find the files
129 | under the
130 | `.kobweb/site` folder.
131 |
132 | Test it locally by running:
133 |
134 | ```
135 | $ kobweb run --layout static --env prod
136 | ```
137 |
138 | If you're satisfied, you can upload your site files to the static website host provider of your
139 | choice. Each provider
140 | has its own instructions for how it discovers your files, so please refer to their documentation.
141 |
142 | You can [read this blog post](https://bitspittle.dev/blog/2022/staticdeploy) for some concrete
143 | examples of exporting a
144 | Kobweb site to two popular static website hosting providers.
145 |
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/bold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/bold.png
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/code.png
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/favicon.ico
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/image.png
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/ios-figure.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/ios-figure.webp
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/ioshub-logo-dark.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/ioshub-logo-dark.webp
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/ioshub-logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/ioshub-logo.webp
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/italic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/italic.png
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/jetpack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/jetpack.webp
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/link.png
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/quote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/quote.png
--------------------------------------------------------------------------------
/site-ios/src/jsMain/resources/public/title-subtitle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnrture/AndroidHub/227823ff53b5344cb9784e7acf2baa482283c4df/site-ios/src/jsMain/resources/public/title-subtitle.png
--------------------------------------------------------------------------------