├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── muhammadsayed │ │ └── websocketsbyktor │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── muhammadsayed │ │ │ └── websocketsbyktor │ │ │ ├── MyApp.kt │ │ │ ├── data │ │ │ ├── KtorRealtimeMessagingClient.kt │ │ │ ├── MessageEvent.kt │ │ │ ├── MessageState.kt │ │ │ └── RealtimeMessagingClient.kt │ │ │ ├── di │ │ │ └── AppModule.kt │ │ │ ├── presentation │ │ │ ├── ChatViewModel.kt │ │ │ └── MainActivity.kt │ │ │ └── ui │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── muhammadsayed │ └── websocketsbyktor │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ktor WebSocket Android Example 2 | ![BookStore](https://github.com/abualgait/KtorWebsocket/assets/38107393/f93f39e5-5535-423d-adba-76d3864cefdf) 3 | 4 | 📝 **Description** 5 | This example showcases how to integrate Ktor WebSocket functionality into your Android app for real-time communication. 6 | 7 | 8 | ## Connect with Me 🌐 9 | 10 | Let's connect! Feel free to reach out on LinkedIn. 11 | 12 | LinkedIn: https://www.linkedin.com/in/abualgait/ 13 | 14 | Happy coding! 🚀✨ 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | id("kotlin-android") 5 | id("kotlin-parcelize") 6 | id("kotlin-kapt") 7 | id("com.google.dagger.hilt.android") 8 | id("com.google.devtools.ksp") 9 | id("org.jetbrains.kotlin.plugin.serialization") 10 | 11 | } 12 | 13 | 14 | android { 15 | namespace = "com.muhammadsayed.websocketsbyktor" 16 | compileSdk = 34 17 | 18 | defaultConfig { 19 | applicationId = "com.muhammadsayed.websocketsbyktor" 20 | minSdk = 24 21 | targetSdk = 34 22 | versionCode = 1 23 | versionName = "1.0" 24 | 25 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 26 | vectorDrawables { 27 | useSupportLibrary = true 28 | } 29 | } 30 | 31 | buildTypes { 32 | release { 33 | isMinifyEnabled = false 34 | proguardFiles( 35 | getDefaultProguardFile("proguard-android-optimize.txt"), 36 | "proguard-rules.pro" 37 | ) 38 | } 39 | } 40 | compileOptions { 41 | sourceCompatibility = JavaVersion.VERSION_1_8 42 | targetCompatibility = JavaVersion.VERSION_1_8 43 | } 44 | kotlinOptions { 45 | jvmTarget = "1.8" 46 | } 47 | buildFeatures { 48 | compose = true 49 | } 50 | composeOptions { 51 | kotlinCompilerExtensionVersion = "1.5.1" 52 | } 53 | packaging { 54 | resources { 55 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 56 | } 57 | } 58 | } 59 | 60 | dependencies { 61 | 62 | implementation("androidx.core:core-ktx:1.12.0") 63 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") 64 | implementation("androidx.activity:activity-compose:1.8.2") 65 | implementation(platform("androidx.compose:compose-bom:2023.08.00")) 66 | implementation("androidx.compose.ui:ui") 67 | implementation("androidx.compose.ui:ui-graphics") 68 | implementation("androidx.compose.ui:ui-tooling-preview") 69 | implementation("androidx.compose.material3:material3") 70 | 71 | implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") 72 | implementation("androidx.navigation:navigation-compose:2.7.7") 73 | implementation("androidx.hilt:hilt-navigation-compose:1.1.0") 74 | 75 | //Dagger - Hilt 76 | implementation("com.google.dagger:hilt-android:2.50") 77 | kapt("com.google.dagger:hilt-android-compiler:2.48.1") 78 | kapt("androidx.hilt:hilt-compiler:1.1.0") 79 | 80 | // Ktor 81 | val ktor_version = "2.3.7" 82 | implementation("io.ktor:ktor-client-core:$ktor_version") 83 | implementation("io.ktor:ktor-client-cio:$ktor_version") 84 | implementation("io.ktor:ktor-client-websockets:$ktor_version") 85 | implementation("io.ktor:ktor-client-logging:$ktor_version") 86 | 87 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") 88 | 89 | testImplementation("junit:junit:4.13.2") 90 | androidTestImplementation("androidx.test.ext:junit:1.1.5") 91 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") 92 | androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00")) 93 | androidTestImplementation("androidx.compose.ui:ui-test-junit4") 94 | debugImplementation("androidx.compose.ui:ui-tooling") 95 | debugImplementation("androidx.compose.ui:ui-test-manifest") 96 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/muhammadsayed/websocketsbyktor/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.muhammadsayed.websocketsbyktor", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/MyApp.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class MyApp : Application() -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/data/KtorRealtimeMessagingClient.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.data 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.plugins.websocket.webSocketSession 5 | import io.ktor.client.request.url 6 | import io.ktor.websocket.Frame 7 | import io.ktor.websocket.WebSocketSession 8 | import io.ktor.websocket.close 9 | import io.ktor.websocket.readText 10 | import kotlinx.coroutines.flow.Flow 11 | import kotlinx.coroutines.flow.consumeAsFlow 12 | import kotlinx.coroutines.flow.emitAll 13 | import kotlinx.coroutines.flow.filterIsInstance 14 | import kotlinx.coroutines.flow.flow 15 | import kotlinx.coroutines.flow.mapNotNull 16 | import kotlinx.serialization.encodeToString 17 | import kotlinx.serialization.json.Json 18 | 19 | class KtorRealtimeMessagingClient( 20 | private val client: HttpClient 21 | ) : RealtimeMessagingClient { 22 | 23 | private var session: WebSocketSession? = null 24 | 25 | override fun getStateStream(): Flow { 26 | return flow { 27 | session = client.webSocketSession { 28 | url("wss://echo.websocket.org") 29 | } 30 | val messageStates = session!! 31 | .incoming 32 | .consumeAsFlow() 33 | .filterIsInstance() 34 | .mapNotNull { 35 | it.readText() 36 | } 37 | 38 | emitAll(messageStates) 39 | } 40 | } 41 | 42 | override suspend fun sendAction(action: MessageEvent) { 43 | session?.outgoing?.send( 44 | Frame.Text(Json.encodeToString(action)) 45 | ) 46 | } 47 | 48 | override suspend fun close() { 49 | session?.close() 50 | session = null 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/data/MessageEvent.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.data 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class MessageEvent(val message: String) 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/data/MessageState.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.data 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class MessagesState( 7 | val message: String? = "" 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/data/RealtimeMessagingClient.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.data 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface RealtimeMessagingClient { 6 | fun getStateStream(): Flow 7 | suspend fun sendAction(action: MessageEvent) 8 | suspend fun close() 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.di 2 | 3 | import com.muhammadsayed.websocketsbyktor.data.KtorRealtimeMessagingClient 4 | import com.muhammadsayed.websocketsbyktor.data.RealtimeMessagingClient 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import io.ktor.client.* 10 | import io.ktor.client.engine.cio.* 11 | import io.ktor.client.plugins.logging.Logging 12 | import io.ktor.client.plugins.websocket.WebSockets 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object AppModule { 18 | 19 | @Singleton 20 | @Provides 21 | fun provideHttpClient(): HttpClient { 22 | return HttpClient(CIO) { 23 | install(Logging) 24 | install(WebSockets) 25 | } 26 | } 27 | 28 | @Singleton 29 | @Provides 30 | fun provideRealtimeMessagingClient(httpClient: HttpClient): RealtimeMessagingClient { 31 | return KtorRealtimeMessagingClient(httpClient) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/presentation/ChatViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.presentation 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.muhammadsayed.websocketsbyktor.data.MessageEvent 6 | import com.muhammadsayed.websocketsbyktor.data.MessagesState 7 | import com.muhammadsayed.websocketsbyktor.data.RealtimeMessagingClient 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.SharingStarted 11 | import kotlinx.coroutines.flow.asStateFlow 12 | import kotlinx.coroutines.flow.catch 13 | import kotlinx.coroutines.flow.onEach 14 | import kotlinx.coroutines.flow.onStart 15 | import kotlinx.coroutines.flow.stateIn 16 | import kotlinx.coroutines.launch 17 | import java.net.ConnectException 18 | import javax.inject.Inject 19 | 20 | @HiltViewModel 21 | class ChatViewModel @Inject constructor( 22 | private val client: RealtimeMessagingClient 23 | ) : ViewModel() { 24 | 25 | var messages = mutableListOf() 26 | 27 | val state = client 28 | .getStateStream() 29 | .onStart { _isConnecting.value = true } 30 | .onEach { _isConnecting.value = false } 31 | .catch { t -> 32 | _isConnecting.value = false 33 | _showConnectionError.value = t is ConnectException 34 | } 35 | .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), String()) 36 | 37 | private val _isConnecting = MutableStateFlow(false) 38 | val isConnecting = _isConnecting.asStateFlow() 39 | 40 | private val _showConnectionError = MutableStateFlow(false) 41 | val showConnectionError = _showConnectionError.asStateFlow() 42 | 43 | 44 | fun sendMessage(messageEvent: MessageEvent) { 45 | viewModelScope.launch { 46 | client.sendAction(messageEvent) 47 | } 48 | } 49 | 50 | override fun onCleared() { 51 | super.onCleared() 52 | viewModelScope.launch { 53 | client.close() 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/presentation/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.presentation 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.layout.Box 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.Row 10 | import androidx.compose.foundation.layout.Spacer 11 | import androidx.compose.foundation.layout.fillMaxSize 12 | import androidx.compose.foundation.layout.fillMaxWidth 13 | import androidx.compose.foundation.layout.height 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.layout.size 16 | import androidx.compose.foundation.lazy.LazyColumn 17 | import androidx.compose.foundation.lazy.itemsIndexed 18 | import androidx.compose.foundation.shape.RoundedCornerShape 19 | import androidx.compose.foundation.text.BasicTextField 20 | import androidx.compose.foundation.text.KeyboardActions 21 | import androidx.compose.foundation.text.KeyboardOptions 22 | import androidx.compose.material.icons.Icons 23 | import androidx.compose.material.icons.filled.Send 24 | import androidx.compose.material3.Button 25 | import androidx.compose.material3.CircularProgressIndicator 26 | import androidx.compose.material3.Icon 27 | import androidx.compose.material3.MaterialTheme 28 | import androidx.compose.material3.Surface 29 | import androidx.compose.material3.Text 30 | import androidx.compose.runtime.collectAsState 31 | import androidx.compose.runtime.getValue 32 | import androidx.compose.runtime.mutableStateOf 33 | import androidx.compose.runtime.remember 34 | import androidx.compose.runtime.setValue 35 | import androidx.compose.ui.ExperimentalComposeUiApi 36 | import androidx.compose.ui.Modifier 37 | import androidx.compose.ui.graphics.Color 38 | import androidx.compose.ui.platform.LocalSoftwareKeyboardController 39 | import androidx.compose.ui.text.input.ImeAction 40 | import androidx.compose.ui.text.input.TextFieldValue 41 | import androidx.compose.ui.text.style.TextAlign 42 | import androidx.compose.ui.unit.dp 43 | import androidx.hilt.navigation.compose.hiltViewModel 44 | import com.muhammadsayed.websocketsbyktor.data.MessageEvent 45 | import com.muhammadsayed.websocketsbyktor.data.MessagesState 46 | import com.muhammadsayed.websocketsbyktor.ui.theme.WebSocketsByKtorTheme 47 | import dagger.hilt.android.AndroidEntryPoint 48 | import kotlinx.serialization.json.Json 49 | 50 | @AndroidEntryPoint 51 | class MainActivity : ComponentActivity() { 52 | @OptIn(ExperimentalComposeUiApi::class) 53 | override fun onCreate(savedInstanceState: Bundle?) { 54 | super.onCreate(savedInstanceState) 55 | setContent { 56 | WebSocketsByKtorTheme { 57 | val viewModel = hiltViewModel() 58 | val state by viewModel.state.collectAsState() 59 | val isConnecting by viewModel.isConnecting.collectAsState() 60 | val showConnectionError by viewModel.showConnectionError.collectAsState() 61 | var textState by remember { mutableStateOf(TextFieldValue("")) } 62 | val keyboardController = LocalSoftwareKeyboardController.current 63 | 64 | 65 | runCatching { 66 | Json.decodeFromString(state) 67 | }.onSuccess { 68 | viewModel.messages += MessagesState(it.message) 69 | } 70 | 71 | Surface( 72 | modifier = Modifier.fillMaxSize(), 73 | color = MaterialTheme.colorScheme.background 74 | ) { 75 | 76 | Column { 77 | if (isConnecting) { 78 | Text( 79 | text = "Connecting...", modifier = Modifier 80 | .padding(16.dp) 81 | .fillMaxWidth(), textAlign = TextAlign.Center 82 | ) 83 | } 84 | 85 | if (!isConnecting) { 86 | Text( 87 | text = "Connected", modifier = Modifier 88 | .padding(16.dp) 89 | .fillMaxWidth(), textAlign = TextAlign.Center 90 | ) 91 | } 92 | 93 | LazyColumn(Modifier.weight(1f)) { 94 | itemsIndexed(viewModel.messages) { _, item -> 95 | Text( 96 | text = item.message ?: "", 97 | modifier = Modifier.padding(16.dp), 98 | ) 99 | } 100 | } 101 | 102 | Row( 103 | modifier = Modifier 104 | .padding(10.dp) 105 | .fillMaxWidth() 106 | ) { 107 | 108 | Box( 109 | modifier = Modifier 110 | .weight(1f) 111 | .background( 112 | shape = RoundedCornerShape(200.dp), 113 | color = Color.Gray.copy(alpha = 0.3f) 114 | ) 115 | .padding(16.dp) 116 | ) { 117 | BasicTextField( 118 | keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send), 119 | keyboardActions = KeyboardActions( 120 | onSend = { keyboardController?.hide() }), 121 | modifier = Modifier.fillMaxWidth(), 122 | value = textState, 123 | onValueChange = { 124 | textState = it 125 | }) 126 | if (textState.text.isEmpty()) 127 | Text(text = "Enter Message ...") 128 | 129 | } 130 | Spacer(modifier = Modifier.size(10.dp)) 131 | if (isConnecting) { 132 | CircularProgressIndicator() 133 | } else { 134 | Button(onClick = { 135 | viewModel.sendMessage( 136 | MessageEvent( 137 | textState.text 138 | ) 139 | ) 140 | textState = TextFieldValue("") 141 | }) { 142 | Icon( 143 | imageVector = Icons.Default.Send, 144 | contentDescription = null 145 | ) 146 | } 147 | } 148 | 149 | Spacer(modifier = Modifier.size(10.dp)) 150 | 151 | if (showConnectionError) { 152 | Text( 153 | text = "Connection Error", 154 | modifier = Modifier.padding(16.dp), 155 | textAlign = TextAlign.Center 156 | ) 157 | } 158 | } 159 | Spacer(modifier = Modifier.height(20.dp)) 160 | } 161 | 162 | 163 | } 164 | } 165 | } 166 | } 167 | } 168 | 169 | -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.SideEffect 13 | import androidx.compose.ui.graphics.toArgb 14 | import androidx.compose.ui.platform.LocalContext 15 | import androidx.compose.ui.platform.LocalView 16 | import androidx.core.view.WindowCompat 17 | 18 | private val DarkColorScheme = darkColorScheme( 19 | primary = Purple80, 20 | secondary = PurpleGrey80, 21 | tertiary = Pink80 22 | ) 23 | 24 | private val LightColorScheme = lightColorScheme( 25 | primary = Purple40, 26 | secondary = PurpleGrey40, 27 | tertiary = Pink40 28 | 29 | /* Other default colors to override 30 | background = Color(0xFFFFFBFE), 31 | surface = Color(0xFFFFFBFE), 32 | onPrimary = Color.White, 33 | onSecondary = Color.White, 34 | onTertiary = Color.White, 35 | onBackground = Color(0xFF1C1B1F), 36 | onSurface = Color(0xFF1C1B1F), 37 | */ 38 | ) 39 | 40 | @Composable 41 | fun WebSocketsByKtorTheme( 42 | darkTheme: Boolean = isSystemInDarkTheme(), 43 | // Dynamic color is available on Android 12+ 44 | dynamicColor: Boolean = true, 45 | content: @Composable () -> Unit 46 | ) { 47 | val colorScheme = when { 48 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 49 | val context = LocalContext.current 50 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 51 | } 52 | 53 | darkTheme -> DarkColorScheme 54 | else -> LightColorScheme 55 | } 56 | val view = LocalView.current 57 | if (!view.isInEditMode) { 58 | SideEffect { 59 | val window = (view.context as Activity).window 60 | window.statusBarColor = colorScheme.primary.toArgb() 61 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme 62 | } 63 | } 64 | 65 | MaterialTheme( 66 | colorScheme = colorScheme, 67 | typography = Typography, 68 | content = content 69 | ) 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muhammadsayed/websocketsbyktor/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.muhammadsayed.websocketsbyktor.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abualgait/KtorWebsocket/12b73362203a53919f366970fc0c392aeb0e26fe/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | WebSocketsByKtor 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |