commit 13a0dc8818650e615a90451afbe83b8bf3c0a5c2 Author: Vineyro Date: Mon Mar 20 17:47:56 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/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..a9f4e52 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..54d5acd --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..a64e1a1 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,78 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' + id 'dagger.hilt.android.plugin' +} + +android { + namespace 'llc.arma.ble' + compileSdk 33 + + defaultConfig { + applicationId "llc.arma.ble" + minSdk 24 + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } + } + + buildTypes { + release { + minifyEnabled 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.4.3' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' + implementation 'androidx.activity:activity-compose:1.3.1' + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.compose.material3:material3:1.0.0-alpha11' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" + + implementation "androidx.compose.material:material-icons-extended:1.4.0-rc01" + + implementation 'androidx.core:core-splashscreen:1.0.0' + implementation 'androidx.navigation:navigation-compose:2.5.3' + + implementation("androidx.hilt:hilt-navigation-compose:1.1.0-alpha01") + implementation('com.google.dagger:hilt-android:2.45') + kapt('com.google.dagger:hilt-android-compiler:2.45') + kapt("androidx.hilt:hilt-compiler:1.0.0") + + implementation "androidx.datastore:datastore-preferences:1.0.0" + +} \ No newline at end of file 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/androidTest/java/llc/arma/ble/ExampleInstrumentedTest.kt b/app/src/androidTest/java/llc/arma/ble/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..1b74337 --- /dev/null +++ b/app/src/androidTest/java/llc/arma/ble/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package llc.arma.ble + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("llc.arma.ble", appContext.packageName) + } +} \ 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..7d29aff --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/framework/App.kt b/app/src/main/java/llc/arma/ble/app/framework/App.kt new file mode 100644 index 0000000..620a0ee --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/framework/App.kt @@ -0,0 +1,8 @@ +package llc.arma.ble.app.framework + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp() +class App : Application() { +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/framework/di/RepositoryBinding.kt b/app/src/main/java/llc/arma/ble/app/framework/di/RepositoryBinding.kt new file mode 100644 index 0000000..731a966 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/framework/di/RepositoryBinding.kt @@ -0,0 +1,17 @@ +package llc.arma.ble.app.framework.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import llc.arma.ble.data.BleRepositoryImpl +import llc.arma.ble.domain.repository.BleRepository + +@Module +@InstallIn(SingletonComponent::class) +interface RepositoryBinding { + + @Binds + fun bindBleRepository(bleRepositoryImpl: BleRepositoryImpl): BleRepository + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/MainActivity.kt b/app/src/main/java/llc/arma/ble/app/ui/MainActivity.kt new file mode 100644 index 0000000..f32970d --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/MainActivity.kt @@ -0,0 +1,29 @@ +package llc.arma.ble.app.ui + +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.ui.Modifier +import dagger.hilt.android.AndroidEntryPoint +import llc.arma.ble.app.ui.screen.main.MainScreen +import llc.arma.ble.app.ui.theme.BleTheme + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + BleTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + MainScreen() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/common/BaseViewModel.kt b/app/src/main/java/llc/arma/ble/app/ui/common/BaseViewModel.kt new file mode 100644 index 0000000..a9a3e12 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/common/BaseViewModel.kt @@ -0,0 +1,63 @@ +package llc.arma.ble.app.ui.common + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +abstract class BaseViewModel< + UiState : ViewState, + Event : ViewEvent, + Effect : ViewSideEffect> : ViewModel() { + + private val initialState: UiState by lazy { setInitialState() } + abstract fun setInitialState(): UiState + + private val _viewState: MutableState = mutableStateOf(initialState) + val viewState: State = _viewState + + private val _event: MutableSharedFlow = MutableSharedFlow() + + private val _effect: Channel = Channel() + val effect = _effect.receiveAsFlow() + + init { + subscribeToEvents() + } + + fun setEvent(event: Event) { + viewModelScope.launch { _event.emit(event) } + } + + fun setState(reducer: UiState.() -> UiState) { + val newState = viewState.value.reducer() + _viewState.value = newState + } + + private fun subscribeToEvents() { + viewModelScope.launch { + _event.collect { + handleEvents(it) + } + } + } + + protected abstract fun handleEvents(event: Event) + + protected fun setEffect(builder: () -> Effect) { + val effectValue = builder() + viewModelScope.launch { _effect.send(effectValue) } + } + +} + +interface ViewState + +interface ViewEvent + +interface ViewSideEffect \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconContract.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconContract.kt new file mode 100644 index 0000000..192bcdc --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconContract.kt @@ -0,0 +1,38 @@ +package llc.arma.ble.app.ui.screen.beacon + +import llc.arma.ble.app.ui.common.ViewEvent +import llc.arma.ble.app.ui.common.ViewSideEffect +import llc.arma.ble.app.ui.common.ViewState +import llc.arma.ble.domain.model.Ble + +class BeaconContract { + + sealed class Event : ViewEvent { + + data class OnTxChanged(val tx: Int) : Event() + + object OnNavigateUpClicked : Event() + + } + + sealed class State : ViewState { + + object Loading : State() + + data class Display( + val beacon: Ble.Beacon + ) : State() + + } + + sealed class Effect : ViewSideEffect { + + sealed class Navigation : Effect() { + + object NavigateUp : Navigation() + + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconScreen.kt new file mode 100644 index 0000000..8e77bc9 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconScreen.kt @@ -0,0 +1,102 @@ +package llc.arma.ble.app.ui.screen.beacon + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +@Composable +fun BeaconScreen( + onNavigationEvent: (BeaconContract.Effect.Navigation) -> Unit +) { + + val viewModel = hiltViewModel() + val state = viewModel.viewState.value + + LaunchedEffect("effect"){ + viewModel.effect.onEach { + when(it){ + is BeaconContract.Effect.Navigation -> onNavigationEvent(it) + } + }.launchIn(this) + } + + Column { + + CenterAlignedTopAppBar( + navigationIcon = { + IconButton( + onClick = { + viewModel.setEvent(BeaconContract.Event.OnNavigateUpClicked) + }, + content = { + Icon( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = null + ) + } + ) + }, + title = { + + } + ) + + when(state){ + is BeaconContract.State.Display -> DisplayState() + is BeaconContract.State.Loading -> LoadingState() + } + + } + +} + +@Composable +private fun LoadingState(){ + + Box(modifier = Modifier.fillMaxSize()){ + + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + + } + +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun DisplayState(){ + + Surface( + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + shape = CircleShape, + color = MaterialTheme.colorScheme.primaryContainer, + onClick = { + + } + ) { + + Box(modifier = Modifier.fillMaxSize()) { + + Text( + modifier = Modifier.align(Alignment.Center), + color = MaterialTheme.colorScheme.background, + style = MaterialTheme.typography.labelLarge, + text = "Сохранить" + ) + + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconViewModel.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconViewModel.kt new file mode 100644 index 0000000..dea4b4b --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconViewModel.kt @@ -0,0 +1,54 @@ +package llc.arma.ble.app.ui.screen.beacon + +import androidx.lifecycle.SavedStateHandle +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import llc.arma.ble.app.ui.common.BaseViewModel +import llc.arma.ble.domain.model.Ble +import javax.inject.Inject + +@HiltViewModel +class BeaconViewModel @Inject constructor( + savedStateHandle: SavedStateHandle +) : BaseViewModel() { + + init { + + savedStateHandle.get("serial")?.let { + CoroutineScope(Dispatchers.IO).launch { + delay(5000) + setState { + BeaconContract.State.Display(Ble.Beacon()) + } + } + } + + } + + override fun setInitialState() = BeaconContract.State.Loading + + override fun handleEvents(event: BeaconContract.Event) { + when(event){ + is BeaconContract.Event.OnNavigateUpClicked -> reduce(viewState.value, event) + is BeaconContract.Event.OnTxChanged -> reduce(viewState.value, event) + } + } + + private fun reduce( + state: BeaconContract.State, + event: BeaconContract.Event.OnNavigateUpClicked + ) { + setEffect { BeaconContract.Effect.Navigation.NavigateUp } + } + + private fun reduce( + state: BeaconContract.State, + event: BeaconContract.Event.OnTxChanged + ) { + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListContract.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListContract.kt new file mode 100644 index 0000000..26827f4 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListContract.kt @@ -0,0 +1,35 @@ +package llc.arma.ble.app.ui.screen.ble + +import llc.arma.ble.app.ui.common.ViewEvent +import llc.arma.ble.app.ui.common.ViewSideEffect +import llc.arma.ble.app.ui.common.ViewState +import llc.arma.ble.domain.model.Ble +import llc.arma.ble.domain.model.BleInfo + +class BleListContract { + + sealed class Event : ViewEvent { + + data class OnConnectToBle( + val ble: BleInfo + ) : Event() + + } + + data class State( + val bleList: List + ) : ViewState + + sealed class Effect : ViewSideEffect { + + sealed class Navigation : Effect() { + + data class NavigateToBle( + val serial: String + ) : Navigation() + + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt new file mode 100644 index 0000000..d2ab390 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt @@ -0,0 +1,81 @@ +package llc.arma.ble.app.ui.screen.ble + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import llc.arma.ble.domain.model.BleInfo + +@Composable +fun BleListScreen( + onNavigationEvent: (BleListContract.Effect.Navigation) -> Unit +) { + + val viewModel = hiltViewModel() + val state = viewModel.viewState.value + + LaunchedEffect("effect"){ + viewModel.effect.onEach { + when(it){ + is BleListContract.Effect.Navigation -> onNavigationEvent(it) + } + }.launchIn(this) + } + + Column { + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + + items(items = state.bleList) { + + BleItem( + ble = it, + onClick = { + viewModel.setEvent(BleListContract.Event.OnConnectToBle(it)) + } + ) + + } + + } + + } + +} + +@Composable +fun BleItem( + ble: BleInfo, + onClick: () -> Unit +){ + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() } + ) { + + Text(text = ble.rssi.toString()) + + Column { + Text(text = ble.name) + Text(text = ble.serial) + Text(text = ble.uuid) + Text(text = ble.batteryLevel.toString()) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListViewModel.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListViewModel.kt new file mode 100644 index 0000000..28edb6c --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListViewModel.kt @@ -0,0 +1,44 @@ +package llc.arma.ble.app.ui.screen.ble + +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import llc.arma.ble.app.ui.common.BaseViewModel +import llc.arma.ble.domain.usecase.GetBleAroundFlow +import javax.inject.Inject + +@HiltViewModel +class BleListViewModel @Inject constructor( + getBleAroundFlow: GetBleAroundFlow +) : BaseViewModel() { + + init { + + getBleAroundFlow().onEach { + setState { + BleListContract.State(it) + } + }.launchIn(viewModelScope) + + } + + override fun setInitialState(): BleListContract.State = BleListContract.State(emptyList()) + + override fun handleEvents(event: BleListContract.Event) { + when(event){ + is BleListContract.Event.OnConnectToBle -> reduce(viewState.value, event) + } + } + + private fun reduce( + state: BleListContract.State, + event: BleListContract.Event.OnConnectToBle + ) { + setEffect { + BleListContract.Effect.Navigation.NavigateToBle(serial = event.ble.serial) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionContract.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionContract.kt new file mode 100644 index 0000000..f3dff6a --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionContract.kt @@ -0,0 +1,47 @@ +package llc.arma.ble.app.ui.screen.connection + +import llc.arma.ble.app.ui.common.ViewEvent +import llc.arma.ble.app.ui.common.ViewSideEffect +import llc.arma.ble.app.ui.common.ViewState +import llc.arma.ble.app.ui.screen.beacon.BeaconContract +import llc.arma.ble.domain.model.Ble +import llc.arma.ble.domain.usecase.GetBleBySerial + +class ConnectionContract { + + sealed class Event : ViewEvent { + + data class OnBeaconNavigationEvent( + val event: BeaconContract.Effect.Navigation + ) + + } + + sealed class State : ViewState { + + object Loading : State() + + data class DisplayException( + val exception: GetBleBySerial.GetBleException + ) : State() + + } + + sealed class Effect : ViewSideEffect { + + sealed class ChildNavigation : Effect() { + + data class NavigateToBeacon( + val ble: Ble + ) : ChildNavigation() + + } + + sealed class Navigation : Effect() { + + object NavigateUp : Navigation() + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionScreen.kt new file mode 100644 index 0000000..dc710af --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionScreen.kt @@ -0,0 +1,42 @@ +package llc.arma.ble.app.ui.screen.connection + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import llc.arma.ble.domain.usecase.GetBleBySerial + +@Composable +fun ConnectionScreen( + onNavigationEvent: (ConnectionContract.Effect.Navigation) -> Unit +) { + + val viewModel = hiltViewModel() + val state = viewModel.viewState.value + + Column() { + + when (state) { + is ConnectionContract.State.DisplayException -> DisplayException(state.exception) + is ConnectionContract.State.Loading -> LoadingState() + } + + } + +} + +@Composable +private fun LoadingState(){ + Box(modifier = Modifier.fillMaxSize()){ + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } +} + +@Composable +private fun DisplayException(exception: GetBleBySerial.GetBleException){ + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionViewModel.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionViewModel.kt new file mode 100644 index 0000000..2add5cc --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionViewModel.kt @@ -0,0 +1,53 @@ +package llc.arma.ble.app.ui.screen.connection + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import llc.arma.ble.app.ui.common.BaseViewModel +import llc.arma.ble.domain.model.Ble +import llc.arma.ble.domain.usecase.GetBleBySerial +import javax.inject.Inject + +@HiltViewModel +class ConnectionViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + getBleBySerial: GetBleBySerial +) : BaseViewModel() { + + init { + + val serial = savedStateHandle.get("serial") + + if(serial != null){ + viewModelScope.launch { + getBleBySerial(serial).fold( + onSuccess = { + setEffect { + when(it){ + is Ble.Beacon -> ConnectionContract.Effect.ChildNavigation.NavigateToBeacon(it) + is Ble.Thermometer -> TODO() + } + } + }, + onFailure = { + setState { + ConnectionContract.State.DisplayException(it) + } + } + ) + } + } else { + throw IllegalArgumentException("serial arg must not be null") + } + + } + + override fun setInitialState() = ConnectionContract.State.Loading + + override fun handleEvents(event: ConnectionContract.Event) { + TODO("Not yet implemented") + } + + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/main/MainScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/main/MainScreen.kt new file mode 100644 index 0000000..74485e8 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/main/MainScreen.kt @@ -0,0 +1,67 @@ +package llc.arma.ble.app.ui.screen.main + +import androidx.compose.runtime.Composable +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import llc.arma.ble.app.ui.screen.beacon.BeaconContract +import llc.arma.ble.app.ui.screen.thermometer.ThermometerScreen +import llc.arma.ble.app.ui.screen.beacon.BeaconScreen +import llc.arma.ble.app.ui.screen.ble.BleListContract +import llc.arma.ble.app.ui.screen.ble.BleListScreen +import llc.arma.ble.app.ui.screen.connection.ConnectionContract +import llc.arma.ble.app.ui.screen.connection.ConnectionScreen + +@Composable +fun MainScreen() { + + val controller = rememberNavController() + + NavHost( + navController = controller, + startDestination = "bleList", + builder = { + + composable( + route = "bleList", + content = { + + BleListScreen( + onNavigationEvent = { + when(it){ + is BleListContract.Effect.Navigation.NavigateToBle -> controller.navigate("beacon/${it.serial}") + } + } + ) + + } + ) + + composable( + route = "connection/{serial}", + content = { + + ConnectionScreen( + onNavigationEvent = { + when(it){ + ConnectionContract.Effect.Navigation.NavigateUp -> controller.navigateUp() + } + } + ) + + } + ) + + composable( + route = "thermometer", + content = { + + ThermometerScreen() + + } + ) + + } + ) + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/ThermometerScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/ThermometerScreen.kt new file mode 100644 index 0000000..256452f --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/ThermometerScreen.kt @@ -0,0 +1,7 @@ +package llc.arma.ble.app.ui.screen.thermometer + +import androidx.compose.runtime.Composable + +@Composable +fun ThermometerScreen() { +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/theme/Color.kt b/app/src/main/java/llc/arma/ble/app/ui/theme/Color.kt new file mode 100644 index 0000000..6c5ab5f --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package llc.arma.ble.app.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/llc/arma/ble/app/ui/theme/Theme.kt b/app/src/main/java/llc/arma/ble/app/ui/theme/Theme.kt new file mode 100644 index 0000000..fe95c42 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/theme/Theme.kt @@ -0,0 +1,85 @@ +package llc.arma.ble.app.ui.theme + +import android.app.Activity +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat +import llc.arma.nfc.app.ui.theme.Typography +import llc.arma.nfc.app.ui.theme.shapes + +val LightColorScheme = lightColorScheme( + primary = Color(0xFF1B1B1F), + onPrimary = Color(0xFFFFFFFF), + primaryContainer = Color(0xFF2D2C27), + onPrimaryContainer = Color(0xFFFFFFFF), + secondary = Color(0xFF555555), + onSecondary = Color(0xFFFFFFFF), + secondaryContainer = Color(0xFFDFDFDF), + onSecondaryContainer = Color(0xFF1B1B1F), + tertiary = Color(0xFFFFE418), + onTertiary = Color(0xFF1B1B1F), + tertiaryContainer = Color(0xFFFFF396), + onTertiaryContainer = Color(0xFF1B1B1F), + error = Color(0xFFBA1A1A), + onError = Color(0xFFFFFFFF), + errorContainer = Color(0xFFFFDAD6), + onErrorContainer = Color(0xFF410002), + background = Color(0xFFFFFFFF), + onBackground = Color(0xFF212121), + surface = Color(0xFFFFFFFF), + onSurface = Color(0xFF1D1C16), + surfaceVariant = Color(0xFFF1F1F1), + onSurfaceVariant = Color(0xFF212121), + outline = Color(0xFFACACAC), + inverseSurface = Color(0xFF32302A), + inverseOnSurface = Color(0xFFF5F0E7), + inversePrimary = Color(0xFFDFC700), + surfaceTint = Color(0xFF755B00), +) + +@Composable +fun BleTheme( + colorScheme: ColorScheme, + content: @Composable () -> Unit +){ + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content, + shapes = shapes + ) +} + + +@Composable +fun BleTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + + val view = LocalView.current + if (!view.isInEditMode) { + + val currentWindow = (view.context as? Activity)?.window + ?: throw Exception("Not in an activity - unable to get Window reference") + + SideEffect { + currentWindow.statusBarColor = LightColorScheme.primary.copy(alpha = 0f).toArgb() + WindowCompat.getInsetsController(currentWindow, view).isAppearanceLightStatusBars = darkTheme.not() + } + } + + MaterialTheme( + colorScheme = LightColorScheme, + typography = Typography, + content = content, + shapes = shapes + ) +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/theme/Type.kt b/app/src/main/java/llc/arma/ble/app/ui/theme/Type.kt new file mode 100644 index 0000000..a34089d --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/theme/Type.kt @@ -0,0 +1,113 @@ +package llc.arma.nfc.app.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Shapes +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import llc.arma.ble.R + +val font = FontFamily( + Font(resId = R.font.nunito_black, weight = FontWeight.W900), + Font(resId = R.font.nunito_bold, weight = FontWeight.W700), + Font(resId = R.font.nunito_extra_bold, weight = FontWeight.W800), + Font(resId = R.font.nunito_extra_light, weight = FontWeight.W200), + Font(resId = R.font.nunito_light, weight = FontWeight.W300), + Font(resId = R.font.nunito_regular, weight = FontWeight.W400), + Font(resId = R.font.nunito_semi_bold, weight = FontWeight.W600) +) + +val shapes = Shapes( + extraSmall = RoundedCornerShape(8.dp) +) + +val Typography = Typography( + headlineLarge = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W400, + fontSize = 32.sp, + lineHeight = 40.sp, + letterSpacing = 0.sp + ), + headlineMedium = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W400, + fontSize = 32.sp, + lineHeight = 40.sp, + letterSpacing = 0.sp + ), + headlineSmall = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W400, + fontSize = 24.sp, + lineHeight = 32.sp, + letterSpacing = 0.sp + ), + titleLarge = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W500, + lineHeight = 28.sp, + fontSize = 22.sp, + letterSpacing = 0.1.sp + ), + titleMedium = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W500, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.1.sp + ), + titleSmall = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W500, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.1.sp + ), + bodyLarge = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W400, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.sp + ), + bodyMedium = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W400, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.25.sp + ), + bodySmall = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W400, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.4.sp + ), + labelLarge = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W600, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.sp + ), + labelMedium = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W600, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ), + labelSmall = TextStyle( + fontFamily = font, + fontWeight = FontWeight.W600, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.sp + ) +) \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt b/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt new file mode 100644 index 0000000..b89debf --- /dev/null +++ b/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt @@ -0,0 +1,179 @@ +package llc.arma.ble.data + +import android.Manifest +import android.app.Application +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothProfile +import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanFilter +import android.bluetooth.le.ScanResult +import android.bluetooth.le.ScanSettings +import android.content.pm.PackageManager +import android.util.Log +import androidx.core.app.ActivityCompat +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flow +import llc.arma.ble.domain.Result +import llc.arma.ble.domain.model.Ble +import llc.arma.ble.domain.model.BleInfo +import llc.arma.ble.domain.repository.BleRepository +import llc.arma.ble.domain.usecase.GetBleBySerial +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton + +class BleRepositoryImpl @Inject constructor( + private val app: Application +) : BleRepository { + + override fun getBleAroundFlow(): Flow> { + + val resultList = mutableMapOf() + + return callbackFlow { + + val bleCallback = object : ScanCallback() { + + override fun onScanResult( + callbackType: Int, + result: ScanResult + ) { + + super.onScanResult(callbackType, result) + + if (ActivityCompat.checkSelfPermission( + app, + Manifest.permission.BLUETOOTH_CONNECT + ) == PackageManager.PERMISSION_GRANTED + ) { + + resultList[result.device.address] = BleInfo( + name = result.scanRecord?.deviceName ?: "", + serial = result.device.address, + uuid = result.device.uuids?.firstOrNull()?.toString() ?: "", + batteryLevel = 1, + rssi = result.rssi + ) + + } + + } + + } + + val bleScanner = app.getSystemService(BluetoothManager::class.java).adapter.bluetoothLeScanner + bleScanner.startScan(bleCallback) + + val timer = Timer().apply { + schedule(object : TimerTask() { + override fun run() { + CoroutineScope(Dispatchers.IO).launch { + send(resultList.values.toList()) + } + } + }, 0, 1000) + } + + awaitClose { + bleScanner.stopScan(bleCallback) + timer.cancel() + } + + } + + } + + @OptIn(ExperimentalCoroutinesApi::class) + override suspend fun getBleBySerial(serial: String): Result = suspendCancellableCoroutine { + + val bluetoothManager = app.getSystemService(BluetoothManager::class.java) + val bleScanner = bluetoothManager.adapter.bluetoothLeScanner + + if (ActivityCompat.checkSelfPermission( + app, + Manifest.permission.BLUETOOTH_CONNECT + ) != PackageManager.PERMISSION_GRANTED + ) { + + it.resume(Result.failure(GetBleBySerial.GetBleException.BlePermissionDenied)){ + + } + + } + + val connected = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT) + .firstOrNull { device -> device.address == serial } + + if(connected != null){ + + it.resume( + Result.success( + Ble.Beacon( + info = BleInfo( + name = connected.name, + serial = connected.address, + uuid = connected.uuids?.firstOrNull()?.toString() ?: "", + rssi = 0, + batteryLevel = 1 + ) + ) + ) + ){} + + } else { + + val bleCallback = object : ScanCallback() { + + override fun onScanResult( + callbackType: Int, + result: ScanResult + ) { + + super.onScanResult(callbackType, result) + + if (ActivityCompat.checkSelfPermission( + app, + Manifest.permission.BLUETOOTH_CONNECT + ) == PackageManager.PERMISSION_GRANTED + ) { + + it.resume( + Result.success( + Ble.Beacon( + info = BleInfo( + name = result.device.name, + serial = result.device.address, + uuid = result.device.uuids?.firstOrNull()?.toString() ?: "", + rssi = result.rssi, + batteryLevel = 1 + ) + ) + ) + ){} + + } + + } + + } + + bleScanner.startScan( + listOf(ScanFilter.Builder().setDeviceAddress(serial).build()), + ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH) + .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) + .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) + .setReportDelay(0L) + .build(), + bleCallback + ) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/Result.kt b/app/src/main/java/llc/arma/ble/domain/Result.kt new file mode 100644 index 0000000..b3d64d5 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/domain/Result.kt @@ -0,0 +1,76 @@ +package llc.arma.ble.domain + +class Result internal constructor( + val value: Any? +) { + + class Failure( + val error: E + ) + + companion object { + + fun success(value: T): Result { + return Result(value) + } + + fun failure(error: E): Result { + return Result(Failure(error)) + } + + } + + val isSuccess: Boolean get() = value !is Failure<*> + + val isFailure: Boolean get() = value is Failure<*> + + inline fun fold( + onSuccess: (value: T) -> R, + onFailure: (error: E) -> R + ): R { + return when (value) { + is Failure<*> -> onFailure(value.error as E) + else -> onSuccess(value as T) + } + } + + inline fun map( + mapper: (value: T) -> R, + ): Result { + return when (value) { + is Failure<*> -> failure(value.error as E) + else -> success(mapper.invoke(value as T)) + } + } + + inline fun onSuccess( + let: (result: T) -> Unit + ){ + if(isSuccess){ + let.invoke(value as T) + } + } + + inline fun onFailure( + let: (error: E) -> Unit + ){ + if(value is Failure<*>){ + let.invoke(value.error as E) + } + } + + fun getOrNull(): T? { + return when (value) { + is Failure<*> -> null + else -> value as T + } + } + + fun getErrorOrNull(): E? { + return when (value) { + is Failure<*> -> value.error as E + else -> null + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/model/Ble.kt b/app/src/main/java/llc/arma/ble/domain/model/Ble.kt new file mode 100644 index 0000000..a887e17 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/domain/model/Ble.kt @@ -0,0 +1,29 @@ +package llc.arma.ble.domain.model + +import android.service.controls.templates.TemperatureControlTemplate + +sealed class Ble( + val info: BleInfo +) { + + class Beacon( + info: BleInfo + ) : Ble(info){ + + + + } + + class Thermometer( + info: BleInfo, + val currentTemperature: Float + ) : Ble(info) { + + class TemperatureRecord( + val temperature: Float, + val date: Long + ) + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/model/BleInfo.kt b/app/src/main/java/llc/arma/ble/domain/model/BleInfo.kt new file mode 100644 index 0000000..e7baecc --- /dev/null +++ b/app/src/main/java/llc/arma/ble/domain/model/BleInfo.kt @@ -0,0 +1,9 @@ +package llc.arma.ble.domain.model + +class BleInfo( + val name: String, + val serial: String, + val uuid: String, + val batteryLevel: Int, + val rssi: Int +) \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt b/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt new file mode 100644 index 0000000..4bc0eb1 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt @@ -0,0 +1,15 @@ +package llc.arma.ble.domain.repository + +import kotlinx.coroutines.flow.Flow +import llc.arma.ble.domain.Result +import llc.arma.ble.domain.model.Ble +import llc.arma.ble.domain.model.BleInfo +import llc.arma.ble.domain.usecase.GetBleBySerial + +interface BleRepository { + + fun getBleAroundFlow(): Flow> + + suspend fun getBleBySerial(serial: String): Result + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/usecase/GetBleAroundFlow.kt b/app/src/main/java/llc/arma/ble/domain/usecase/GetBleAroundFlow.kt new file mode 100644 index 0000000..a25a75b --- /dev/null +++ b/app/src/main/java/llc/arma/ble/domain/usecase/GetBleAroundFlow.kt @@ -0,0 +1,15 @@ +package llc.arma.ble.domain.usecase + +import kotlinx.coroutines.flow.Flow +import llc.arma.ble.domain.model.Ble +import llc.arma.ble.domain.model.BleInfo +import llc.arma.ble.domain.repository.BleRepository +import javax.inject.Inject + +class GetBleAroundFlow @Inject constructor( + private val bleRepository: BleRepository +) { + + operator fun invoke(): Flow> = bleRepository.getBleAroundFlow() + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/usecase/GetBleBySerial.kt b/app/src/main/java/llc/arma/ble/domain/usecase/GetBleBySerial.kt new file mode 100644 index 0000000..d9b2ce1 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/domain/usecase/GetBleBySerial.kt @@ -0,0 +1,23 @@ +package llc.arma.ble.domain.usecase + +import llc.arma.ble.domain.model.Ble +import llc.arma.ble.domain.repository.BleRepository +import javax.inject.Inject +import llc.arma.ble.domain.Result + +class GetBleBySerial @Inject constructor( + private val bleRepository: BleRepository +) { + + suspend operator fun invoke(serial: String): Result = + bleRepository.getBleBySerial(serial) + + sealed class GetBleException { + + object TimeOut : GetBleException() + + object BlePermissionDenied : GetBleException() + + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file 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..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/font/nunito_black.ttf b/app/src/main/res/font/nunito_black.ttf new file mode 100644 index 0000000..299e0a7 Binary files /dev/null and b/app/src/main/res/font/nunito_black.ttf differ diff --git a/app/src/main/res/font/nunito_bold.ttf b/app/src/main/res/font/nunito_bold.ttf new file mode 100644 index 0000000..f41ef20 Binary files /dev/null and b/app/src/main/res/font/nunito_bold.ttf differ diff --git a/app/src/main/res/font/nunito_extra_bold.ttf b/app/src/main/res/font/nunito_extra_bold.ttf new file mode 100644 index 0000000..406ab9a Binary files /dev/null and b/app/src/main/res/font/nunito_extra_bold.ttf differ diff --git a/app/src/main/res/font/nunito_extra_light.ttf b/app/src/main/res/font/nunito_extra_light.ttf new file mode 100644 index 0000000..da3020d Binary files /dev/null and b/app/src/main/res/font/nunito_extra_light.ttf differ diff --git a/app/src/main/res/font/nunito_light.ttf b/app/src/main/res/font/nunito_light.ttf new file mode 100644 index 0000000..51f7109 Binary files /dev/null and b/app/src/main/res/font/nunito_light.ttf differ diff --git a/app/src/main/res/font/nunito_regular.ttf b/app/src/main/res/font/nunito_regular.ttf new file mode 100644 index 0000000..1f03818 Binary files /dev/null and b/app/src/main/res/font/nunito_regular.ttf differ diff --git a/app/src/main/res/font/nunito_semi_bold.ttf b/app/src/main/res/font/nunito_semi_bold.ttf new file mode 100644 index 0000000..51e2b8d Binary files /dev/null and b/app/src/main/res/font/nunito_semi_bold.ttf differ 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..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ 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..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v33/ic_launcher.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..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..d71696a --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Ble + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..40909c2 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +