From d129c407d329ff89a864bc4ce9a36186a3647ab8 Mon Sep 17 00:00:00 2001 From: Reinout Meliesie Date: Thu, 25 Jul 2024 23:27:14 +0200 Subject: [PATCH] Project setup --- .gitignore | 3 + app/build.gradle.kts | 62 +++++++ app/src/main/AndroidManifest.xml | 28 +++ .../java/com/kernelmaft/zanbur/app-state.kt | 17 ++ .../main/java/com/kernelmaft/zanbur/config.kt | 30 ++++ .../main/java/com/kernelmaft/zanbur/main.kt | 112 ++++++++++++ .../main/java/com/kernelmaft/zanbur/mqtt.kt | 63 +++++++ .../java/com/kernelmaft/zanbur/ontology.kt | 62 +++++++ .../main/java/com/kernelmaft/zanbur/theme.kt | 26 +++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++++ .../res/drawable/ic_launcher_foreground.xml | 30 ++++ .../main/res/mipmap-anydpi/ic_launcher.xml | 6 + .../res/mipmap-anydpi/ic_launcher_round.xml | 6 + app/src/main/res/values/strings.xml | 4 + app/src/main/res/values/themes.xml | 7 + build.gradle.kts | 2 + gradle.properties | 25 +++ gradle/libs.versions.toml | 37 ++++ settings.gradle.kts | 23 +++ 19 files changed, 713 insertions(+) create mode 100644 app/build.gradle.kts create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/kernelmaft/zanbur/app-state.kt create mode 100644 app/src/main/java/com/kernelmaft/zanbur/config.kt create mode 100644 app/src/main/java/com/kernelmaft/zanbur/main.kt create mode 100644 app/src/main/java/com/kernelmaft/zanbur/mqtt.kt create mode 100644 app/src/main/java/com/kernelmaft/zanbur/ontology.kt create mode 100644 app/src/main/java/com/kernelmaft/zanbur/theme.kt create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/mipmap-anydpi/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi/ic_launcher_round.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 settings.gradle.kts diff --git a/.gitignore b/.gitignore index b1706a0..8541e12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ /app/build/ +/app/release/ /.idea/ /gradle/wrapper/ /gradlew /gradlew.bat +/keystore.properties +/.kotlin/ # Android Studio defaults *.iml diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..7e1710b --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,62 @@ +plugins { + alias ( libs . plugins . android . application ) + alias ( libs . plugins . kotlin . android ) + alias ( libs . plugins . kotlin . compose ) + alias ( libs . plugins . kotlin . serialization ) +} + +android { + namespace = "com.kernelmaft.zanbur" + compileSdk = 34 + + defaultConfig { + applicationId = "com.kernelmaft.zanbur" + minSdk = 31 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + isMinifyEnabled = true + isShrinkResources = true + } + } + compileOptions { + targetCompatibility = JavaVersion . VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildToolsVersion = "34.0.0" + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.14" + } +} + +dependencies { + // Runtime libraries + implementation ( libs . android . material ) + implementation ( libs . androidx . activity . compose ) + implementation ( libs . androidx . core . ktx ) + implementation ( libs . androidx . compose . material3 ) + implementation ( libs . androidx . compose . ui ) + implementation ( libs . androidx . compose . ui . graphics ) + debugImplementation ( libs . androidx . compose . ui . tooling ) + implementation ( libs . androidx . lifecycle . runtime . ktx ) + implementation ( libs . kotlinx . coroutines . android ) + implementation ( libs . kotlinx . serialization . json ) + // Other libraries + implementation ( libs . kmqtt . common ) + implementation ( libs . kmqtt . client ) +} + +tasks . withType ( org . jetbrains . kotlin . gradle . tasks . KotlinCompile :: class ) . all { + compilerOptions { + freeCompilerArgs . addAll ( "-opt-in=kotlin.ExperimentalUnsignedTypes" ) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6d13121 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/kernelmaft/zanbur/app-state.kt b/app/src/main/java/com/kernelmaft/zanbur/app-state.kt new file mode 100644 index 0000000..8968415 --- /dev/null +++ b/app/src/main/java/com/kernelmaft/zanbur/app-state.kt @@ -0,0 +1,17 @@ +package com.kernelmaft.zanbur + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf + + +typealias MutableStateMap < Key , Value > = MutableState < Map < Key , Value > > + +object AppState { + val devices : MutableStateMap < DeviceName , Device > = mutableStateOf ( emptyMap () ) + val deviceStates : MutableStateMap < Device , LampState > = mutableStateOf ( emptyMap () ) + val scenes : MutableStateMap < SceneName , Scene > = mutableStateOf ( emptyMap () ) + val currentScene : MutableState < Scene ? > = mutableStateOf (null) + + fun putDevice ( device : Device ) { devices . value += Pair ( device . name , device ) } + fun putScene ( scene : Scene ) { scenes . value += Pair ( scene . name , scene ) } +} diff --git a/app/src/main/java/com/kernelmaft/zanbur/config.kt b/app/src/main/java/com/kernelmaft/zanbur/config.kt new file mode 100644 index 0000000..8e15bca --- /dev/null +++ b/app/src/main/java/com/kernelmaft/zanbur/config.kt @@ -0,0 +1,30 @@ +package com.kernelmaft.zanbur + + +object Config { + const val MQTT_SERVER_HOST = "antoinette.kernelmaft.com" + const val MQTT_SERVER_PORT = 1883 + const val MQTT_TOPIC = "zigbee2mqtt" + + private val deskLamp = TemperatureLamp ( DeviceName ("Desk lamp") ) + private val standingLamp = TemperatureLamp ( DeviceName ("Standing lamp") ) + private val diningTableLamp = TemperatureLamp ( DeviceName ("Dining table lamp") ) + private val ledStrip = RgbLamp ( DeviceName ("LED strip") ) + + val devices = listOf ( deskLamp , standingLamp , diningTableLamp , ledStrip ) + + val scenes = listOf ( + Scene ( SceneName ("All off") , mapOf ( + Pair ( deskLamp , TemperatureLampState ( Power . OFF , 254 , 250 ) ) , + Pair ( standingLamp , TemperatureLampState ( Power . OFF , 254 , 250 ) ) , + Pair ( diningTableLamp , TemperatureLampState ( Power . OFF , 254 , 250 ) ) , + Pair ( ledStrip , RgbLampState ( Power . OFF , 254 , Cie1931Color ( 0.0 , 0.0 ) ) ) , + ) ) , + Scene ( SceneName ("Evening") , mapOf ( + Pair ( deskLamp , TemperatureLampState ( Power . ON , 128 , 454 ) ) , + Pair ( standingLamp , TemperatureLampState ( Power . ON , 192 , 454 ) ) , + Pair ( diningTableLamp , TemperatureLampState ( Power . ON , 192 , 454 ) ) , + Pair ( ledStrip , RgbLampState ( Power . ON , 128 , Cie1931Color ( 0.25 , 0.05 ) ) ) , + ) ) , + ) +} diff --git a/app/src/main/java/com/kernelmaft/zanbur/main.kt b/app/src/main/java/com/kernelmaft/zanbur/main.kt new file mode 100644 index 0000000..67ab86c --- /dev/null +++ b/app/src/main/java/com/kernelmaft/zanbur/main.kt @@ -0,0 +1,112 @@ +package com.kernelmaft.zanbur + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Arrangement.Center +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.lifecycle.lifecycleScope + + +class MainActivity : ComponentActivity () { + override fun onCreate ( savedInstanceState : Bundle ? ) { + super . onCreate (savedInstanceState) + + val devices by AppState . devices + val scenes by AppState . scenes + var currentScene by AppState . currentScene + + for ( device in Config . devices ) AppState . putDevice (device) + for ( scene in Config . scenes ) AppState . putScene (scene) + + enableEdgeToEdge () + setContent { + ZanburTheme { + Scaffold { scaffoldPadding -> + val wrapperModifier = Modifier + .padding(scaffoldPadding) + .fillMaxWidth() + .fillMaxHeight() + + Column ( wrapperModifier , Center , CenterHorizontally ) { + SceneSwitcher ( scenes . values . map { it . name } , currentScene ?. name ) { + val newScene = scenes . getValue (it) + currentScene = newScene + handleSceneChange (newScene) + } + } + } + } + } + + MqttClient . run (lifecycleScope) + } +} + +@Composable private fun SceneSwitcher ( + scenes : Collection , + currentScene : SceneName ? , + onSwitch : (SceneName) -> Unit , +) = Column { + for ( scene in scenes ) { + val colors = + if ( scene == currentScene ) ButtonDefaults . buttonColors () + else ButtonDefaults . filledTonalButtonColors () + + Button ( { onSwitch (scene) } , Modifier , true , ButtonDefaults . shape , colors ) { + Text ( scene . value ) + } + } +} + +private fun handleSceneChange ( newScene : Scene ) { + for ( ( device , newState ) in newScene . states ) { + val jsonString = when (device) { + is TemperatureLamp -> Json . encodeToString ( newState as TemperatureLampState ) + is RgbLamp -> Json . encodeToString ( newState as RgbLampState ) + else -> throw Error ("Unknown type of device state") + } + MqttClient . publish ( + Config . MQTT_TOPIC + "/" + device . name . value + "/set" , + jsonString . toByteArray () . asUByteArray () , + ) + } +} + +//private fun handlePowerChange ( deviceName : String , power : Power ) { +// when ( val device = AppState . devices . value . getValue (deviceName) ) { +// +// is TemperatureLamp -> +// if ( device . currentState != null && device . currentState . power != power ) { +// MqttClient . publish ( +// "zigbee2mqtt/$deviceName/set/state" , +// power . toString () . toByteArray () . asUByteArray () , +// ) +// AppState . putDevice ( TemperatureLamp ( deviceName , device . currentState . copy (power) ) ) +// } +// +// is RgbLamp -> +// if ( device . currentState != null && device . currentState . power != power ) { +// MqttClient . publish ( +// "zigbee2mqtt/$deviceName/set/state" , +// power . toString () . toByteArray () . asUByteArray () , +// ) +// AppState . putDevice ( RgbLamp ( deviceName , device . currentState . copy (power) ) ) +// } +// } +//} diff --git a/app/src/main/java/com/kernelmaft/zanbur/mqtt.kt b/app/src/main/java/com/kernelmaft/zanbur/mqtt.kt new file mode 100644 index 0000000..8666dbc --- /dev/null +++ b/app/src/main/java/com/kernelmaft/zanbur/mqtt.kt @@ -0,0 +1,63 @@ +package com.kernelmaft.zanbur + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import MQTTClient +import mqtt.MQTTVersion +import mqtt.Subscription +import mqtt.packets.Qos.AT_MOST_ONCE +import mqtt.packets.mqtt.MQTTPublish + + +typealias MqttPublishHandler = ( MQTTPublish , Json ) -> Unit + +object MqttClient { + private var client : MQTTClient ? = null + private var coroutineScope : CoroutineScope ? = null + private val publishHandlers : MutableList = mutableListOf () + + fun run ( coroutineScope : CoroutineScope ) { + this . coroutineScope = coroutineScope + val json = Json { ignoreUnknownKeys = true } + + coroutineScope . launch (IO) { + client = MQTTClient ( + MQTTVersion . MQTT5 , + Config . MQTT_SERVER_HOST , + Config . MQTT_SERVER_PORT , + null , + ) { + for ( handler in publishHandlers ) handler ( it , json ) + } + client !! . subscribe ( listOf ( Subscription ( Config . MQTT_TOPIC + "/#" ) ) ) + + for ( ( name , device ) in AppState . devices . value ) when (device) { + is TemperatureLamp -> publish ( + Config . MQTT_TOPIC + "/" + name + "/get" , + Json . encodeToString ( TemperatureLampState ( Power . OFF , 0 , 250 ) ) + . toByteArray () + . asUByteArray () , + ) + is RgbLamp -> publish ( + Config . MQTT_TOPIC + "/" + name + "/get" , + Json . encodeToString ( RgbLampState ( Power . OFF , 0 , Cie1931Color ( 0.0 , 0.0 ) ) ) + . toByteArray () + . asUByteArray () , + ) + } + + client !! . run () + } + } + + fun addPublishHandler ( handler : MqttPublishHandler ) = publishHandlers . add (handler) + + fun publish ( topic : String , payload : UByteArray ) { + coroutineScope !! . launch (IO) { + client !! . publish ( false , AT_MOST_ONCE , topic , payload ) + } + } +} diff --git a/app/src/main/java/com/kernelmaft/zanbur/ontology.kt b/app/src/main/java/com/kernelmaft/zanbur/ontology.kt new file mode 100644 index 0000000..80a2bb5 --- /dev/null +++ b/app/src/main/java/com/kernelmaft/zanbur/ontology.kt @@ -0,0 +1,62 @@ +package com.kernelmaft.zanbur + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + + +typealias Real = Double + + +data class Scene ( + val name : SceneName , + val states : Map < Device , LampState > , +) + +data class SceneName ( val value : String ) + + +abstract class Device ( val name : DeviceName ) + +class TemperatureLamp ( name : DeviceName ) : Device (name) +class RgbLamp ( name : DeviceName ) : Device (name) + +data class DeviceName ( val value : String ) + + +abstract class LampState { + abstract val power : Power + abstract val brightness : Int // Range 0 to 254 +} + +@Serializable data class TemperatureLampState ( + @SerialName ("state") override val power : Power , + @SerialName ("brightness") override val brightness : Int , + @SerialName ("color_temp") val colorTemperature : Int , // Range 250 to 454 +) : LampState () + +@Serializable data class RgbLampState ( + @SerialName ("state") override val power : Power , + @SerialName ("brightness") override val brightness : Int , + @SerialName ("color") val color : Cie1931Color , +) : LampState () + +@Serializable enum class Power { + ON , OFF ; + + fun toBoolean () : Boolean = when (this) { + ON -> true + OFF -> false + } + + companion object { + fun fromBoolean ( power : Boolean ) : Power = when (power) { + true -> ON + false -> OFF + } + } +} + +@Serializable data class Cie1931Color ( + val x : Real , // Range 0.0 to 1.0 (for shrimp anyway) + val y : Real , // Idem +) diff --git a/app/src/main/java/com/kernelmaft/zanbur/theme.kt b/app/src/main/java/com/kernelmaft/zanbur/theme.kt new file mode 100644 index 0000000..352d3af --- /dev/null +++ b/app/src/main/java/com/kernelmaft/zanbur/theme.kt @@ -0,0 +1,26 @@ +package com.kernelmaft.zanbur + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp + + +val compactSpacing = 16 . dp + +@Composable fun ZanburTheme ( content : @Composable () -> Unit ) { + val colorScheme = run { + val context = LocalContext . current + if ( isSystemInDarkTheme () ) + dynamicDarkColorScheme (context) + else + dynamicLightColorScheme (context) + } + + MaterialTheme ( colorScheme, shapes , typography , content ) +} 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..4e12b43 --- /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..c6aee64 --- /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/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..52ac069 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..52ac069 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ 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..542a2e4 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + Zanbur + Zanbur + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..f273fc3 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,7 @@ + + + diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..722a1ac --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,2 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins {} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..95e0e0e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,25 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true + +org.gradle.configuration-cache=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..e9d56bf --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,37 @@ +[versions] +# Gradle plugins +android-plugin = "8.5.1" +kotlin = "2.0.0" +# Runtime libraries +androidx-core-ktx = "1.13.1" +android-material = "1.12.0" +androidx-compose-material3 = "1.2.1" +androidx-lifecycle-runtime-ktx = "2.8.3" +androidx-activity-compose = "1.9.0" +androidx-compose-ui = "1.6.8" +kotlinx-coroutines-android = "1.8.1" +kotlinx-serialization-json = "1.7.1" +# Other libraries +kmqtt = "0.4.8" + +[libraries] +# Runtime libraries +android-material = { group = "com.google.android.material", name = "material", version.ref = "android-material" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidx-activity-compose" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidx-compose-material3" } +androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "androidx-compose-ui" } +androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "androidx-compose-ui" } +androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "androidx-compose-ui" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-runtime-ktx" } +kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines-android" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } +# Other libraries +kmqtt-common = { group = "io.github.davidepianca98", name = "kmqtt-common", version.ref = "kmqtt" } +kmqtt-client = { group = "io.github.davidepianca98", name = "kmqtt-client", version.ref = "kmqtt" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "android-plugin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..3c7e692 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "Zanbur" +include(":app")