commit ae585588542eb5d33c1d3fbea2d10c810a7086ae Author: Vova-SH Date: Tue Dec 12 00:53:43 2023 +0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..c1ee0b9 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +/build +/.idea diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..7f465c6 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,81 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.devtools.ksp") +} + +/** + * Не изменяйте ничего при сборки. Все изменения будут проигнорированы + **/ +android { + namespace = "ru.samsung.smartintercom" + compileSdk = 34 + + defaultConfig { + applicationId = "ru.samsung.smartintercom" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.2" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + + +/** + * Используйте только разрешённые библиотеки + **/ +dependencies { + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") + implementation("androidx.activity:activity-compose:1.7.0") + implementation(platform("androidx.compose:compose-bom:2023.03.00")) + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-graphics") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.material3:material3") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2") + implementation("androidx.navigation:navigation-compose:2.7.4") + + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + + implementation("com.google.dagger:dagger:2.48") + ksp("com.google.dagger:dagger-compiler:2.48") + + implementation("io.coil-kt:coil-compose:2.4.0") + + implementation("io.ktor:ktor-network:2.3.5") +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1a0fe66 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/ru/samsung/smartintercom/App.kt b/app/src/main/java/ru/samsung/smartintercom/App.kt new file mode 100644 index 0000000..d77a96b --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/App.kt @@ -0,0 +1,11 @@ +package ru.samsung.smartintercom + +import android.app.Application +import ru.samsung.smartintercom.di.ContextModule +import ru.samsung.smartintercom.di.DaggerAppComponent + +class App : Application() { + val appComponent = DaggerAppComponent.builder() + .contextModule(ContextModule(this)) + .build() +} diff --git a/app/src/main/java/ru/samsung/smartintercom/core/ComposeIds.kt b/app/src/main/java/ru/samsung/smartintercom/core/ComposeIds.kt new file mode 100644 index 0000000..13ddda5 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/core/ComposeIds.kt @@ -0,0 +1,37 @@ +/* НЕ ИЗМЕНЯЙТЕ ФАЙЛ! */ +/* ВСЕ ИЗМЕНЕНИЯ ПРИ ТЕСТИРОВАНИИ БУДУТ ОТМЕНЕНЫ */ +package ru.samsung.smartintercom.core + +typealias ComposeIds = String + +object MainScreenId { + const val screenId: ComposeIds = "main_screen" + const val buttonStart: ComposeIds = "button_start" + const val buttonRetry: ComposeIds = "button_retry" + const val textIntercomModel: ComposeIds = "text_intercom_model" + const val buttonTakePhoto: ComposeIds = "button_take_photo" + const val imageIntercom: ComposeIds = "image_intercom" + const val textError: ComposeIds = "text_error" + const val buttonSettings: ComposeIds = "button_settings" + const val buttonHistory: ComposeIds = "button_history" +} + +object SettingScreenId { + const val screenId: ComposeIds = "setting_screen" + const val inputHouse: ComposeIds = "input_house" + const val inputFlat: ComposeIds = "input_flat" + const val buttonSave: ComposeIds = "button_save" +} + +object CallScreenId { + const val screenId: ComposeIds = "call_screen" + const val buttonOpen: ComposeIds = "button_open" + const val buttonClose: ComposeIds = "button_close" +} + +object CallHistoryId { + const val screenId: ComposeIds = "history_screen" + const val recycler: ComposeIds = "recycler" + const val textDate: ComposeIds = "text_date" + const val textStatus: ComposeIds = "text_status" +} diff --git a/app/src/main/java/ru/samsung/smartintercom/core/CoreConstants.kt b/app/src/main/java/ru/samsung/smartintercom/core/CoreConstants.kt new file mode 100644 index 0000000..62e396e --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/core/CoreConstants.kt @@ -0,0 +1,9 @@ +/* НЕ ИЗМЕНЯЙТЕ ФАЙЛ! */ +/* ВСЕ ИЗМЕНЕНИЯ ПРИ ТЕСТИРОВАНИИ БУДУТ ОТМЕНЕНЫ */ +package ru.samsung.smartintercom.core + +object CoreConstants { + const val HOST = "http://89.208.220.227:82/" + const val HEADER_FLAT = "flat" + const val HEADER_HOUSE = "house" +} diff --git a/app/src/main/java/ru/samsung/smartintercom/data/auth/dto/AuthDto.kt b/app/src/main/java/ru/samsung/smartintercom/data/auth/dto/AuthDto.kt new file mode 100644 index 0000000..dbf363d --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/data/auth/dto/AuthDto.kt @@ -0,0 +1,8 @@ +package ru.samsung.smartintercom.data.auth.dto + +import com.google.gson.annotations.SerializedName + +data class AuthDto( + @SerializedName("model") + val model: String +) diff --git a/app/src/main/java/ru/samsung/smartintercom/data/auth/repo/AuthRepositoryImpl.kt b/app/src/main/java/ru/samsung/smartintercom/data/auth/repo/AuthRepositoryImpl.kt new file mode 100644 index 0000000..292e356 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/data/auth/repo/AuthRepositoryImpl.kt @@ -0,0 +1,15 @@ +package ru.samsung.smartintercom.data.auth.repo + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import ru.samsung.smartintercom.domain.auth.AuthRepository +import ru.samsung.smartintercom.domain.auth.model.AuthEntity +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AuthRepositoryImpl @Inject constructor() : AuthRepository { + + // TODO: Необходимо переопределить праивильно функцию + override val authData: Flow get() = flow { emit(null) } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/data/call/CallDataSource.kt b/app/src/main/java/ru/samsung/smartintercom/data/call/CallDataSource.kt new file mode 100644 index 0000000..abce200 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/data/call/CallDataSource.kt @@ -0,0 +1,102 @@ +/* НЕ ИЗМЕНЯЙТЕ ФАЙЛ! */ +/* ВСЕ ИЗМЕНЕНИЯ ПРИ ТЕСТИРОВАНИИ БУДУТ ОТМЕНЕНЫ */ +package ru.samsung.smartintercom.data.call + +import android.util.Log +import com.google.gson.Gson +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.Socket +import io.ktor.network.sockets.aSocket +import io.ktor.network.sockets.isClosed +import io.ktor.network.sockets.openReadChannel +import io.ktor.network.sockets.openWriteChannel +import io.ktor.utils.io.writeStringUtf8 +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class CallDataSource @Inject constructor( + private val gson: Gson +) { + private val _status = MutableSharedFlow(replay = 1) + val status get() = _status.asSharedFlow() + + private var socketInfo: SocketInfo? = null + + + suspend fun connect(authDto: SocketAuthDto) { + if (socketInfo != null) return + + CoroutineScope(Dispatchers.IO).launch { + try { + val selectorManager = SelectorManager(Dispatchers.IO) + val socket = aSocket(selectorManager).tcp().connect(HOSTNAME, PORT) + socketInfo = SocketInfo( + socket = socket, + selectorManager = selectorManager, + ) + + val receiveChannel = socket.openReadChannel() + val sendChannel = socket.openWriteChannel(autoFlush = true) + sendChannel.writeStringUtf8("${gson.toJson(authDto)}\n") + _status.emit(Status.Connect) + while (!socket.isClosed) { + val message = receiveChannel.readUTF8Line(limit = 100) + if (message != null) { + _status.emit(Status.Message(body = message)) + } else { + Log.d("Socket", "Server closed a connection") + close(socket = socket, selectorManager = selectorManager) + } + } + } catch (e: Exception) { + e.printStackTrace() + delay(TIMEOUT) + socketInfo = null + _status.emit(Status.Close) + } + } + + } + + suspend fun close() { + val data = socketInfo ?: return + close(socket = data.socket, selectorManager = data.selectorManager) + } + + private suspend fun close( + socket: Socket, + selectorManager: SelectorManager + ) { + withContext(Dispatchers.IO) { + socket.close() + selectorManager.close() + socketInfo = null + _status.emit(Status.Close) + } + } + + sealed interface Status { + data object Connect : Status + data class Message(val body: String) : Status + data object Close : Status + } + + private data class SocketInfo( + val socket: Socket, + val selectorManager: SelectorManager, + ) + + private companion object { + const val HOSTNAME = "89.208.220.227" + const val PORT = 9202 + const val TIMEOUT = 5000L + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/data/call/CallRepositoryImpl.kt b/app/src/main/java/ru/samsung/smartintercom/data/call/CallRepositoryImpl.kt new file mode 100644 index 0000000..721b1c9 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/data/call/CallRepositoryImpl.kt @@ -0,0 +1,63 @@ +/* НЕ ИЗМЕНЯЙТЕ ФАЙЛ! */ +/* ВСЕ ИЗМЕНЕНИЯ ПРИ ТЕСТИРОВАНИИ БУДУТ ОТМЕНЕНЫ */ +package ru.samsung.smartintercom.data.call + +import android.util.Log +import dagger.Reusable +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import ru.samsung.smartintercom.domain.auth.AuthRepository +import ru.samsung.smartintercom.domain.auth.model.AuthEntity +import ru.samsung.smartintercom.domain.call.CallRepository +import javax.inject.Inject + +@OptIn(ExperimentalCoroutinesApi::class) +@Reusable +class CallRepositoryImpl @Inject constructor( + authRepo: AuthRepository, + private val callDataSource: CallDataSource, +) : CallRepository { + override val intercomCallStart: Flow = authRepo.authData + .flatMapLatest { data -> + if (data == null) { + callDataSource.close() + } else { + reconnectSocket(data) + } + callDataSource.status.map { status -> status to data } + } + .mapNotNull { (status, authData) -> + when (status) { + is CallDataSource.Status.Close -> { + Log.d(TAG, "Close") + if (authData != null) reconnectSocket(authData) + null + } + is CallDataSource.Status.Connect -> { + Log.d(TAG, "Connect") + null + } + is CallDataSource.Status.Message -> { + Log.d(TAG, "Message") + Unit + } + } + } + .buffer( + capacity = 0, + BufferOverflow.DROP_OLDEST + ) + + private suspend fun reconnectSocket(authEntity: AuthEntity) { + callDataSource.connect(SocketAuthDto(house = authEntity.house, room = authEntity.room)) + } + + private companion object { + const val TAG = "SOCKET" + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/data/call/SocketAuthDto.kt b/app/src/main/java/ru/samsung/smartintercom/data/call/SocketAuthDto.kt new file mode 100644 index 0000000..0a19742 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/data/call/SocketAuthDto.kt @@ -0,0 +1,6 @@ +package ru.samsung.smartintercom.data.call + +data class SocketAuthDto( + val house: String, + val room: String, +) diff --git a/app/src/main/java/ru/samsung/smartintercom/di/AppComponent.kt b/app/src/main/java/ru/samsung/smartintercom/di/AppComponent.kt new file mode 100644 index 0000000..3f61c84 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/di/AppComponent.kt @@ -0,0 +1,13 @@ +package ru.samsung.smartintercom.di + +import dagger.Component +import javax.inject.Singleton + +@Component( + modules = [ + AppModule::class, + ContextModule::class, + ] +) +@Singleton +interface AppComponent : AppProvides diff --git a/app/src/main/java/ru/samsung/smartintercom/di/AppModule.kt b/app/src/main/java/ru/samsung/smartintercom/di/AppModule.kt new file mode 100644 index 0000000..40f519a --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/di/AppModule.kt @@ -0,0 +1,25 @@ +package ru.samsung.smartintercom.di + +import android.content.Context +import android.content.SharedPreferences +import dagger.Binds +import dagger.Module +import dagger.Provides +import ru.samsung.smartintercom.data.auth.repo.AuthRepositoryImpl +import ru.samsung.smartintercom.data.call.CallRepositoryImpl +import ru.samsung.smartintercom.domain.auth.AuthRepository +import ru.samsung.smartintercom.domain.call.CallRepository + + +@Module +interface AppModule { + @Binds + fun authRepo(authRepositoryImpl: AuthRepositoryImpl): AuthRepository + @Binds + fun callRepo(callRepositoryImpl: CallRepositoryImpl): CallRepository + + companion object { + @Provides + fun provideSharedPrefs(context: Context): SharedPreferences = context.getSharedPreferences("PREFS", Context.MODE_PRIVATE) + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/di/AppProvides.kt b/app/src/main/java/ru/samsung/smartintercom/di/AppProvides.kt new file mode 100644 index 0000000..3bda4b0 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/di/AppProvides.kt @@ -0,0 +1,9 @@ +package ru.samsung.smartintercom.di + +import ru.samsung.smartintercom.domain.auth.AuthRepository +import ru.samsung.smartintercom.domain.call.CallRepository + +interface AppProvides { + fun authRepo(): AuthRepository + fun callRepo(): CallRepository +} diff --git a/app/src/main/java/ru/samsung/smartintercom/di/ContextModule.kt b/app/src/main/java/ru/samsung/smartintercom/di/ContextModule.kt new file mode 100644 index 0000000..a37e55c --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/di/ContextModule.kt @@ -0,0 +1,20 @@ +package ru.samsung.smartintercom.di + +import android.content.Context +import com.google.gson.Gson +import dagger.Module +import dagger.Provides +import javax.inject.Singleton + + +@Module +class ContextModule(val context: Context) { + + @Singleton + @Provides + fun provideContext() = context + + @Singleton + @Provides + fun provideGson() = Gson() +} diff --git a/app/src/main/java/ru/samsung/smartintercom/domain/auth/AuthRepository.kt b/app/src/main/java/ru/samsung/smartintercom/domain/auth/AuthRepository.kt new file mode 100644 index 0000000..d9fb47e --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/domain/auth/AuthRepository.kt @@ -0,0 +1,12 @@ +package ru.samsung.smartintercom.domain.auth + +import kotlinx.coroutines.flow.Flow +import ru.samsung.smartintercom.domain.auth.model.AuthEntity + +interface AuthRepository { + /** + * Данное значение передаёт при каждом изменении новое значение сущности авторизации + * в случае, если пользователь неавторизован - необходимо передать null + */ + val authData: Flow +} diff --git a/app/src/main/java/ru/samsung/smartintercom/domain/auth/model/AuthEntity.kt b/app/src/main/java/ru/samsung/smartintercom/domain/auth/model/AuthEntity.kt new file mode 100644 index 0000000..8e74b51 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/domain/auth/model/AuthEntity.kt @@ -0,0 +1,12 @@ +package ru.samsung.smartintercom.domain.auth.model + +/** + * Информация о пользователе, необходимой для похода в API + * + * @param house номер дома + * @param room номер комнаты + */ +data class AuthEntity( + val house: String, + val room: String, +) diff --git a/app/src/main/java/ru/samsung/smartintercom/domain/call/CallRepository.kt b/app/src/main/java/ru/samsung/smartintercom/domain/call/CallRepository.kt new file mode 100644 index 0000000..1eb4d19 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/domain/call/CallRepository.kt @@ -0,0 +1,7 @@ +package ru.samsung.smartintercom.domain.call + +import kotlinx.coroutines.flow.Flow + +interface CallRepository { + val intercomCallStart: Flow +} diff --git a/app/src/main/java/ru/samsung/smartintercom/domain/call/GetCallNeededUseCase.kt b/app/src/main/java/ru/samsung/smartintercom/domain/call/GetCallNeededUseCase.kt new file mode 100644 index 0000000..024ce01 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/domain/call/GetCallNeededUseCase.kt @@ -0,0 +1,10 @@ +package ru.samsung.smartintercom.domain.call + +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetCallNeededUseCase @Inject constructor( + private val callRepository: CallRepository +) { + fun execute(): Flow = callRepository.intercomCallStart +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/activity/MainActivity.kt b/app/src/main/java/ru/samsung/smartintercom/ui/activity/MainActivity.kt new file mode 100644 index 0000000..12ea6d7 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/activity/MainActivity.kt @@ -0,0 +1,51 @@ +package ru.samsung.smartintercom.ui.activity + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.compose.rememberNavController +import ru.samsung.smartintercom.ui.activity.di.MainActivityComponent +import ru.samsung.smartintercom.ui.nav.Navigation +import ru.samsung.smartintercom.ui.nav.Screen +import ru.samsung.smartintercom.ui.nav.navigate +import ru.samsung.smartintercom.ui.screen.main.MainScreen +import ru.samsung.smartintercom.ui.theme.SmartIntercomTheme +import ru.samsung.smartintercom.utils.collectAsEffect +import ru.samsung.smartintercom.utils.daggerViewModel + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + SmartIntercomTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + val navController = rememberNavController() + Navigation(navController = navController) + val component = MainActivityComponent.build() + val viewModel = daggerViewModel { component.viewModel } + viewModel.openCallScreen.collectAsEffect { + navController.navigate(Screen.CALL) + } + } + } + } + } +} + +@Preview(showBackground = true, device = "spec:width=1080px,height=1920px,dpi=320") +@Composable +fun GreetingPreview() { + SmartIntercomTheme { + MainScreen.Render(rememberNavController()) + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/activity/MainActivityViewModel.kt b/app/src/main/java/ru/samsung/smartintercom/ui/activity/MainActivityViewModel.kt new file mode 100644 index 0000000..89d1a7f --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/activity/MainActivityViewModel.kt @@ -0,0 +1,25 @@ +package ru.samsung.smartintercom.ui.activity + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch +import ru.samsung.smartintercom.domain.call.GetCallNeededUseCase +import ru.samsung.smartintercom.utils.MutablePublishFlow +import javax.inject.Inject + +class MainActivityViewModel @Inject constructor( + private val getCallNeededUseCase: GetCallNeededUseCase +) : ViewModel() { + val openCallScreen: SharedFlow get() = _openCallScreen.asSharedFlow() + private val _openCallScreen = MutablePublishFlow() + + init { + viewModelScope.launch { + getCallNeededUseCase.execute().collect { + _openCallScreen.emit(Unit) + } + } + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/activity/di/MainActivityComponent.kt b/app/src/main/java/ru/samsung/smartintercom/ui/activity/di/MainActivityComponent.kt new file mode 100644 index 0000000..b1f9565 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/activity/di/MainActivityComponent.kt @@ -0,0 +1,29 @@ +package ru.samsung.smartintercom.ui.activity.di + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import dagger.Component +import ru.samsung.smartintercom.App +import ru.samsung.smartintercom.di.AppComponent +import ru.samsung.smartintercom.ui.activity.MainActivityViewModel + +@Component(dependencies = [AppComponent::class]) +@MainActivityScope +interface MainActivityComponent { + + @Component.Factory + interface Factory { + fun create(appComponent: AppComponent): MainActivityComponent + } + + val viewModel: MainActivityViewModel + + companion object { + @Composable + fun build(): MainActivityComponent { + return DaggerMainActivityComponent.factory().create( + (LocalContext.current.applicationContext as App).appComponent + ) + } + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/activity/di/MainActivityScope.kt b/app/src/main/java/ru/samsung/smartintercom/ui/activity/di/MainActivityScope.kt new file mode 100644 index 0000000..013d3c8 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/activity/di/MainActivityScope.kt @@ -0,0 +1,8 @@ +package ru.samsung.smartintercom.ui.activity.di + +import javax.inject.Scope + + +@Scope +@Retention(AnnotationRetention.RUNTIME) +annotation class MainActivityScope diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/nav/NavGraph.kt b/app/src/main/java/ru/samsung/smartintercom/ui/nav/NavGraph.kt new file mode 100644 index 0000000..990823b --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/nav/NavGraph.kt @@ -0,0 +1,34 @@ +package ru.samsung.smartintercom.ui.nav + +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.runtime.Composable +import androidx.navigation.NavController +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable + +/** + * Данная функция инициализирует граф инициализации + */ +@Composable +fun Navigation(navController: NavHostController) { + NavHost( + navController = navController, + startDestination = Screen.MAIN.route, + enterTransition = { EnterTransition.None }, + exitTransition = { ExitTransition.None } + ) + { + // Здесь происходит перебор функций для инициализации всех возможных экранов + Screen.entries.forEach { screen -> + composable(route = screen.route) { + screen.baseScreen.Render(navController) + } + } + } +} + +fun NavController.navigate(screen: Screen) { + navigate(route = screen.route) +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/nav/Screen.kt b/app/src/main/java/ru/samsung/smartintercom/ui/nav/Screen.kt new file mode 100644 index 0000000..fbec3d7 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/nav/Screen.kt @@ -0,0 +1,16 @@ +package ru.samsung.smartintercom.ui.nav + +import ru.samsung.smartintercom.ui.screen.ScreenBaseData +import ru.samsung.smartintercom.ui.screen.call.CallScreen +import ru.samsung.smartintercom.ui.screen.main.MainScreen + +/** + * Класс, в котором перечислены все экраны + * + * @param route путь до экрана + * @param baseScreen сущность экрана, которая будет отрендерена при открытии + */ +enum class Screen(val route: String, internal val baseScreen: ScreenBaseData) { + MAIN("main", MainScreen), + CALL("call", CallScreen), +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/ScreenBaseData.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/ScreenBaseData.kt new file mode 100644 index 0000000..a948f75 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/ScreenBaseData.kt @@ -0,0 +1,22 @@ +package ru.samsung.smartintercom.ui.screen + +import androidx.compose.runtime.Composable +import androidx.navigation.NavController + +/** + * Базовый интерфейс экрана + */ +interface ScreenBaseData { + /** + * Название экрана + */ + val name: String + + /** + * Функция, вызываемая при отрисовки экрана + * + * @param navController контроллер, который необходим для открытия других экранов + */ + @Composable + fun Render(navController: NavController) +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/CallScreen.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/CallScreen.kt new file mode 100644 index 0000000..fc8d8c8 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/CallScreen.kt @@ -0,0 +1,135 @@ +package ru.samsung.smartintercom.ui.screen.call + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.compose.rememberNavController +import ru.samsung.smartintercom.R +import ru.samsung.smartintercom.core.CallScreenId +import ru.samsung.smartintercom.ui.nav.Screen +import ru.samsung.smartintercom.ui.nav.navigate +import ru.samsung.smartintercom.ui.screen.ScreenBaseData +import ru.samsung.smartintercom.ui.screen.call.di.CallScreenComponent +import ru.samsung.smartintercom.ui.theme.SmartIntercomTheme +import ru.samsung.smartintercom.ui.theme.button +import ru.samsung.smartintercom.utils.collectAsEffect +import ru.samsung.smartintercom.utils.daggerViewModel +import ru.samsung.smartintercom.utils.setupScreenData + +/** + * Экран вызова + * + * ВНИМАНИЕ! + * Данный экран приведён для примера. + * Вы можете его модифицировать любыми удобными способами и дорабатывать его. + */ +object CallScreen : ScreenBaseData { + override val name = CallScreenId.screenId + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + override fun Render(navController: NavController) { + val component = CallScreenComponent.build() + val viewModel = daggerViewModel { component.viewModel } + viewModel.goBackOrOpenMainScreen.collectAsEffect { + if (navController.graph.hierarchy.count() > 0) { + navController.popBackStack() + } else { + navController.navigate(Screen.MAIN) + } + } + Scaffold( + modifier = Modifier.setupScreenData(CallScreen), + content = { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues = paddingValues) + .padding(16.dp), + ) { + Text( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .wrapContentHeight(align = Alignment.CenterVertically), + text = stringResource(R.string.call_screen_title), + style = MaterialTheme.typography.displayLarge, + textAlign = TextAlign.Center, + ) + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Button( + id = CallScreenId.buttonOpen, + text = stringResource(R.string.open_intercom), + color = colorResource(R.color.call_open), + onClick = viewModel::clickOpen, + ) + Spacer(modifier = Modifier.width(16.dp)) + Button( + id = CallScreenId.buttonClose, + text = stringResource(R.string.decline_intercom), + color = colorResource(R.color.call_close), + onClick = viewModel::clickDecline, + ) + } + } + } + ) + } + + @Composable + fun RowScope.Button( + id: String, + text: String, + color: Color, + onClick: () -> Unit + ) { + Button( + modifier = Modifier + .testTag(id) + .weight(1.0f, true), + colors = ButtonDefaults.buttonColors(containerColor = color), + onClick = onClick, + ) { + Text( + style = MaterialTheme.typography.button.large, + text = text, + ) + } + + } +} + +@Preview(showBackground = true, device = "spec:width=1080px,height=1920px,dpi=320") +@Composable +fun CallScreenPreview() { + SmartIntercomTheme { + CallScreen.Render(rememberNavController()) + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/CallViewModel.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/CallViewModel.kt new file mode 100644 index 0000000..f9d4e5f --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/CallViewModel.kt @@ -0,0 +1,27 @@ +package ru.samsung.smartintercom.ui.screen.call + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch +import ru.samsung.smartintercom.utils.MutablePublishFlow +import javax.inject.Inject + +class CallViewModel @Inject constructor() : ViewModel() { + val goBackOrOpenMainScreen: SharedFlow get() = _goBackOrOpenMainScreen.asSharedFlow() + private val _goBackOrOpenMainScreen = MutablePublishFlow() + fun clickOpen() { + viewModelScope.launch { + // TODO: необходимо добавить обработку нажатия на кнопку открытия + _goBackOrOpenMainScreen.emit(Unit) + } + } + + fun clickDecline() { + viewModelScope.launch { + // TODO: необходимо добавить обработку нажатия на кнопку закрытия + _goBackOrOpenMainScreen.emit(Unit) + } + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/di/CallScreenComponent.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/di/CallScreenComponent.kt new file mode 100644 index 0000000..58e7747 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/di/CallScreenComponent.kt @@ -0,0 +1,29 @@ +package ru.samsung.smartintercom.ui.screen.call.di + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import dagger.Component +import ru.samsung.smartintercom.App +import ru.samsung.smartintercom.di.AppComponent +import ru.samsung.smartintercom.ui.screen.call.CallViewModel + +@Component(dependencies = [AppComponent::class]) +@CallScreenScope +interface CallScreenComponent { + + @Component.Factory + interface Factory { + fun create(appComponent: AppComponent): CallScreenComponent + } + + val viewModel: CallViewModel + + companion object { + @Composable + fun build(): CallScreenComponent { + return DaggerCallScreenComponent.factory().create( + (LocalContext.current.applicationContext as App).appComponent + ) + } + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/di/CallScreenScope.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/di/CallScreenScope.kt new file mode 100644 index 0000000..5d7c6b4 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/call/di/CallScreenScope.kt @@ -0,0 +1,8 @@ +package ru.samsung.smartintercom.ui.screen.call.di + +import javax.inject.Scope + + +@Scope +@Retention(AnnotationRetention.RUNTIME) +annotation class CallScreenScope diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/MainScreen.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/MainScreen.kt new file mode 100644 index 0000000..6797d6e --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/MainScreen.kt @@ -0,0 +1,131 @@ +package ru.samsung.smartintercom.ui.screen.main + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import ru.samsung.smartintercom.R +import ru.samsung.smartintercom.core.MainScreenId +import ru.samsung.smartintercom.ui.screen.ScreenBaseData +import ru.samsung.smartintercom.ui.screen.main.di.MainScreenComponent +import ru.samsung.smartintercom.ui.theme.SmartIntercomTheme +import ru.samsung.smartintercom.ui.theme.button +import ru.samsung.smartintercom.utils.daggerViewModel +import ru.samsung.smartintercom.utils.setupScreenData + +object MainScreen : ScreenBaseData { + override val name = MainScreenId.screenId + + @Composable + override fun Render(navController: NavController) { + val component = MainScreenComponent.build() + val viewModel = daggerViewModel { component.viewModel } + val state by viewModel.uiState.collectAsState() + RenderState( + state = state, + openSettingClick = {}, + ) + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + internal fun RenderState( + state: MainState, + openSettingClick: () -> Unit, + ) { + Scaffold( + modifier = Modifier.setupScreenData(this), + content = { paddingValues -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + when (state) { + is MainState.Intro -> { + IntroState( + openSettingClick = openSettingClick + ) + } + + is MainState.Loading -> TODO("Добавить состояние загрузки") + } + } + } + ) + } + + @Composable + private fun IntroState( + openSettingClick: () -> Unit, + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Column( + modifier = Modifier + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = stringResource(R.string.main_screen_intro_title), + style = MaterialTheme.typography.displayLarge, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.main_screen_intro_text), + style = MaterialTheme.typography.headlineLarge, + textAlign = TextAlign.Center, + ) + } + Button( + modifier = Modifier.testTag(MainScreenId.buttonStart), + onClick = openSettingClick + ) { + Text( + text = stringResource(id = R.string.main_screen_intro_button), + style = MaterialTheme.typography.button.normal + ) + } + } + } +} + +@Preview(showBackground = true, device = "spec:width=1080px,height=1920px,dpi=320") +@Composable +fun RenderPreview() { + val mockAction = {} + SmartIntercomTheme { + Row { + MainScreen.RenderState( + state = MainState.Intro, + openSettingClick = mockAction, + ) + } + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/MainState.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/MainState.kt new file mode 100644 index 0000000..40f9058 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/MainState.kt @@ -0,0 +1,7 @@ +package ru.samsung.smartintercom.ui.screen.main + +sealed interface MainState { + data object Intro: MainState + data object Loading: MainState + // TODO: необходимо добавить состояние ошибки и отображения +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/MainViewModel.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/MainViewModel.kt new file mode 100644 index 0000000..6650268 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/MainViewModel.kt @@ -0,0 +1,22 @@ +package ru.samsung.smartintercom.ui.screen.main + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@OptIn(ExperimentalCoroutinesApi::class) +class MainViewModel @Inject constructor() : ViewModel() { + val uiState: StateFlow get() = _uiState.asStateFlow() + private val _uiState = MutableStateFlow(MainState.Loading) + + init { + viewModelScope.launch { + _uiState.emit(MainState.Intro) + } + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/di/MainScreenComponent.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/di/MainScreenComponent.kt new file mode 100644 index 0000000..33705d1 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/di/MainScreenComponent.kt @@ -0,0 +1,29 @@ +package ru.samsung.smartintercom.ui.screen.main.di + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import dagger.Component +import ru.samsung.smartintercom.App +import ru.samsung.smartintercom.di.AppComponent +import ru.samsung.smartintercom.ui.screen.main.MainViewModel + +@Component(dependencies = [AppComponent::class]) +@MainScreenScope +interface MainScreenComponent { + + @Component.Factory + interface Factory { + fun create(appComponent: AppComponent): MainScreenComponent + } + + val viewModel: MainViewModel + + companion object { + @Composable + fun build(): MainScreenComponent { + return DaggerMainScreenComponent.factory().create( + (LocalContext.current.applicationContext as App).appComponent + ) + } + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/di/MainScreenScope.kt b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/di/MainScreenScope.kt new file mode 100644 index 0000000..67a4396 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/screen/main/di/MainScreenScope.kt @@ -0,0 +1,8 @@ +package ru.samsung.smartintercom.ui.screen.main.di + +import javax.inject.Scope + + +@Scope +@Retention(AnnotationRetention.RUNTIME) +annotation class MainScreenScope diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/theme/Color.kt b/app/src/main/java/ru/samsung/smartintercom/ui/theme/Color.kt new file mode 100644 index 0000000..c8a3888 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package ru.samsung.smartintercom.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/theme/Theme.kt b/app/src/main/java/ru/samsung/smartintercom/ui/theme/Theme.kt new file mode 100644 index 0000000..c70f810 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/theme/Theme.kt @@ -0,0 +1,60 @@ +package ru.samsung.smartintercom.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 +) + +@Composable +fun SmartIntercomTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/app/src/main/java/ru/samsung/smartintercom/ui/theme/Type.kt b/app/src/main/java/ru/samsung/smartintercom/ui/theme/Type.kt new file mode 100644 index 0000000..af928fe --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/ui/theme/Type.kt @@ -0,0 +1,37 @@ +package ru.samsung.smartintercom.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) +) + +val Typography.button get() = Button + +object Button { + val large = base.copy( + fontSize = 32.sp, + lineHeight = 36.sp, + ) + + val normal = base.copy( + fontSize = 16.sp, + lineHeight = 24.sp, + ) + + private val base get() = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + ) +} diff --git a/app/src/main/java/ru/samsung/smartintercom/utils/ComposeExtensions.kt b/app/src/main/java/ru/samsung/smartintercom/utils/ComposeExtensions.kt new file mode 100644 index 0000000..5821c30 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/utils/ComposeExtensions.kt @@ -0,0 +1,21 @@ +package ru.samsung.smartintercom.utils + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +@Suppress("ComposableNaming", "StateFlowValueCalledInComposition") +@Composable +fun Flow.collectAsEffect( + context: CoroutineContext = EmptyCoroutineContext, + block: (T) -> Unit +) { + LaunchedEffect(key1 = Unit) { + onEach(block).flowOn(context).launchIn(this) + } +} diff --git a/app/src/main/java/ru/samsung/smartintercom/utils/DaggerViewModelExtension.kt b/app/src/main/java/ru/samsung/smartintercom/utils/DaggerViewModelExtension.kt new file mode 100644 index 0000000..a55d13c --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/utils/DaggerViewModelExtension.kt @@ -0,0 +1,23 @@ +package ru.samsung.smartintercom.utils + +import androidx.compose.runtime.Composable +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel + + +@Composable +inline fun daggerViewModel( + key: String? = null, + crossinline viewModelInstanceCreator: () -> T +): T { + return viewModel( + modelClass = T::class.java, + key = key, + factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return viewModelInstanceCreator() as T + } + } + ) +} diff --git a/app/src/main/java/ru/samsung/smartintercom/utils/FlowUtils.kt b/app/src/main/java/ru/samsung/smartintercom/utils/FlowUtils.kt new file mode 100644 index 0000000..5d9d064 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/utils/FlowUtils.kt @@ -0,0 +1,10 @@ +package ru.samsung.smartintercom.utils + +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow + +fun MutablePublishFlow() = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1, + BufferOverflow.DROP_OLDEST +) diff --git a/app/src/main/java/ru/samsung/smartintercom/utils/ModifierExtensions.kt b/app/src/main/java/ru/samsung/smartintercom/utils/ModifierExtensions.kt new file mode 100644 index 0000000..6ac8142 --- /dev/null +++ b/app/src/main/java/ru/samsung/smartintercom/utils/ModifierExtensions.kt @@ -0,0 +1,17 @@ +package ru.samsung.smartintercom.utils + +import androidx.compose.runtime.Stable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId +import ru.samsung.smartintercom.ui.screen.ScreenBaseData + +@OptIn(ExperimentalComposeUiApi::class) +@Stable +fun Modifier.setupScreenData(screenBaseData: ScreenBaseData) = semantics { + testTagsAsResourceId = true +} + .testTag(tag = screenBaseData.name) + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..1e4408c --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..fde1368 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..9cd96da --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,12 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + #43A047 + #E53935 + diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 0000000..97a83fd --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..49d3e36 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,13 @@ + + SmartIntercom + + + Incoming call + Open + Decline + + + Welcome + Setup your intercom for working app + Go to setting + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..0bde0a7 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,4 @@ + + +