diff --git a/.github/scripts/gitlog.sh b/.github/scripts/gitlog.sh
deleted file mode 100755
index a6041ce2..00000000
--- a/.github/scripts/gitlog.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-LOG=releaseNotes.txt
-TEMP=releaseNotes.temp
-
-git log --oneline $(git describe --abbrev=0 --tags 2>&1).. > ${LOG}
-
-cut -d' ' -f2- ${LOG} > ${TEMP}
-
-while read -r line
-do
- echo "* $line"
-done <${TEMP} > ${LOG}
-
-rm ${TEMP}
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9b3036e1..365f1674 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -145,6 +145,142 @@ jobs:
-F message_thread_id="${THREAD_ID}" \
-F parse_mode="HTML"
+ - name: send telegram message bundle
+ env:
+ TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
+ CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
+ THREAD_ID: ${{ secrets.TELEGRAM_THREAD_ID }}
+ MESSAGE: |
+ ✅ ${{ env.VERSION_NAME }} (${{ env.VERSION_CODE }})
+ Ветка: ${{ github.ref_name }}
+ Коммит: ${{ env.SHORT_SHA }}
+ run: |
+ curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendDocument" \
+ -F chat_id="${CHAT_ID}" \
+ -F document="@${{ env.BUNDLE_APK_PATH }}" \
+ -F caption="${{ env.MESSAGE }}" \
+ -F message_thread_id="${THREAD_ID}" \
+ -F parse_mode="HTML"
+
+ build-wear:
+ if: ${{ vars.ANDROID_BUILD_ENABLED == 'true' }}
+ needs: job-common
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ env:
+ KEYSTORE_FILE: ${{ secrets.KEYSTORE_FILE }}
+ KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }}
+ KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }}
+ KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }}
+ SHORT_SHA: ${{ needs.job-common.outputs.SHORT_SHA }}
+ steps:
+ - name: checkout repo
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # клонирует репозиторий с историей коммитов
+
+ - name: setup jdk 21
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+
+ - name: cache gradle dependencies
+ uses: actions/cache@v4
+ with:
+ path: ~/.gradle/caches
+ key: gradle-${{ runner.os }}-${{ hashFiles('/*.gradle*', '/gradle-wrapper.properties') }}
+ restore-keys: |
+ gradle-${{ runner.os }}-
+
+ - name: cache gradle wrapper
+ uses: actions/cache@v3
+ with:
+ path: ~/.gradle/wrapper
+ key: gradle-wrapper-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
+
+ - name: make gradlew executable
+ run: chmod +x ./gradlew
+
+ - name: decode keystore file
+ id: decode_keystore_file
+ uses: timheuer/base64-to-file@v1
+ with:
+ fileName: 'keystore_release.jks'
+ encodedString: ${{ secrets.KEYSTORE_FILE }}
+
+ - name: set decoded file location as environment
+ run: echo "KEYSTORE_FILE=${{ steps.decode_keystore_file.outputs.filePath }}" >> $GITHUB_ENV
+
+ - name: assemble debug artifact
+ run: ./gradlew wear:assembleDebug
+
+ - name: assemble release artifact
+ run: ./gradlew wear:assembleRelease
+
+ - name: bundle release artifact
+ run: ./gradlew wear:bundleRelease
+
+ - name: upload artifacts to outputs
+ uses: actions/upload-artifact@v4
+ with:
+ path: |
+ wear/build/outputs/apk/debug
+ wear/build/outputs/apk/release
+ wear/build/outputs/bundle/release
+
+ - name: expose version name
+ id: version_name
+ run: |
+ VERSION_NAME=$(./gradlew wear:printVersionName -q)
+ echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_ENV
+
+ - name: expose version code
+ id: version_code
+ run: |
+ VERSION_CODE=$(./gradlew wear:printVersionCode -q)
+ echo "VERSION_CODE=$VERSION_CODE" >> $GITHUB_ENV
+
+ - name: expose artifacts
+ run: |
+ echo "DEBUG_APK_PATH=$(find wear/build/outputs/apk/debug -name '*.apk' -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2)" >> $GITHUB_ENV
+ echo "RELEASE_APK_PATH=$(find wear/build/outputs/apk/release -name '*.apk' -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2)" >> $GITHUB_ENV
+ echo "BUNDLE_APK_PATH=$(find wear/build/outputs/bundle/release -name '*.aab' -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2)" >> $GITHUB_ENV
+
+ - name: send telegram message debug
+ env:
+ TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
+ CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
+ THREAD_ID: ${{ secrets.TELEGRAM_THREAD_ID }}
+ MESSAGE: |
+ ✅ ${{ env.VERSION_NAME }} (${{ env.VERSION_CODE }})
+ Ветка: ${{ github.ref_name }}
+ Коммит: ${{ env.SHORT_SHA }}
+ run: |
+ curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendDocument" \
+ -F chat_id="${CHAT_ID}" \
+ -F document="@${{ env.DEBUG_APK_PATH }}" \
+ -F caption="${{ env.MESSAGE }}" \
+ -F message_thread_id="${THREAD_ID}" \
+ -F parse_mode="HTML"
+
+ - name: send telegram message release
+ env:
+ TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
+ CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
+ THREAD_ID: ${{ secrets.TELEGRAM_THREAD_ID }}
+ MESSAGE: |
+ ✅ ${{ env.VERSION_NAME }} (${{ env.VERSION_CODE }})
+ Ветка: ${{ github.ref_name }}
+ Коммит: ${{ env.SHORT_SHA }}
+ run: |
+ curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendDocument" \
+ -F chat_id="${CHAT_ID}" \
+ -F document="@${{ env.RELEASE_APK_PATH }}" \
+ -F caption="${{ env.MESSAGE }}" \
+ -F message_thread_id="${THREAD_ID}" \
+ -F parse_mode="HTML"
+
- name: send telegram message bundle
env:
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e7fe8a08..ac1a0ea1 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -16,6 +16,7 @@ kotlinx-atomicfu = "0.26.0"
google-ksp = "2.0.21-1.0.27"
google-services = "4.4.2"
google-gson = "2.11.0"
+google-horologist = "0.6.20"
google-gms-play-services-ads = "23.5.0"
google-gms-play-services-ads-identifier = "18.1.0"
@@ -550,6 +551,15 @@ google-material-compose-theme-adapter = { module = "com.google.android.material:
google-material-compose-theme-adapter3 = { module = "com.google.android.material:compose-theme-adapter-3", version.ref = "google-material-compose-theme-adapter" }
google-material-compose-theme-core = { module = "com.google.android.material:compose-theme-adapter-core", version.ref = "google-material-compose-theme-adapter" }
google-gson = { module = "com.google.code.gson:gson", version.ref = "google-gson" }
+google-horologist-audio-ui = { module = "com.google.android.horologist:horologist-audio-ui", version.ref = "google-horologist" }
+google-horologist-composables = { module = "com.google.android.horologist:horologist-composables", version.ref = "google-horologist" }
+google-horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "google-horologist" }
+google-horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "google-horologist" }
+google-horologist-compose-tools = { module = "com.google.android.horologist:horologist-compose-tools", version.ref = "google-horologist" }
+google-horologist-images-coil = { module = "com.google.android.horologist:horologist-images-coil", version.ref = "google-horologist" }
+google-horologist-media-data = { module = "com.google.android.horologist:horologist-media-data", version.ref = "google-horologist" }
+google-horologist-media-ui = { module = "com.google.android.horologist:horologist-media-ui", version.ref = "google-horologist" }
+google-horologist-roboscreenshots = { module = "com.google.android.horologist:horologist-roboscreenshots", version.ref = "google-horologist" }
#endregion
#region firebase
google-firebase-abt = { module = "com.google.firebase:firebase-abt", version.ref = "google-firebase-abt" }
diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts
index 87b24723..26f0ab25 100644
--- a/mobile/build.gradle.kts
+++ b/mobile/build.gradle.kts
@@ -101,15 +101,6 @@ base {
archivesName.set("Template-v${android.defaultConfig.versionName}(${android.defaultConfig.versionCode})") // Replace with your own app's name
}
-tasks.register("prepareReleaseNotes") {
- doLast {
- exec {
- workingDir(rootDir)
- executable("./.github/scripts/gitlog.sh")
- }
- }
-}
-
tasks.register("printVersionName") {
doLast {
println(android.defaultConfig.versionName)
@@ -120,9 +111,4 @@ tasks.register("printVersionCode") {
doLast {
println(android.defaultConfig.versionCode.toString())
}
-}
-
-afterEvaluate {
- tasks.findByName("assembleDebug")?.finalizedBy("prepareReleaseNotes")
- tasks.findByName("assembleRelease")?.finalizedBy("prepareReleaseNotes")
}
\ No newline at end of file
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index 97eac933..865ed950 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -1,8 +1,7 @@
+ xmlns:tools="http://schemas.android.com/tools">
;
+}
\ No newline at end of file
diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..47166c41
--- /dev/null
+++ b/wear/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/App.kt b/wear/src/main/kotlin/org/michaelbel/template/App.kt
new file mode 100644
index 00000000..82d94148
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/App.kt
@@ -0,0 +1,16 @@
+package org.michaelbel.template
+
+import android.app.Application
+import org.koin.android.ext.koin.androidContext
+import org.koin.core.context.startKoin
+
+class App: Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+ startKoin {
+ androidContext(this@App)
+ modules(appModule)
+ }
+ }
+}
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/AppModule.kt b/wear/src/main/kotlin/org/michaelbel/template/AppModule.kt
new file mode 100644
index 00000000..66c5d9cf
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/AppModule.kt
@@ -0,0 +1,83 @@
+package org.michaelbel.template
+
+import androidx.datastore.preferences.core.PreferenceDataStoreFactory
+import com.chuckerteam.chucker.api.ChuckerCollector
+import com.chuckerteam.chucker.api.ChuckerInterceptor
+import com.chuckerteam.chucker.api.RetentionManager
+import io.ktor.client.HttpClient
+import io.ktor.client.engine.okhttp.OkHttp
+import io.ktor.client.plugins.HttpTimeout
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.client.plugins.defaultRequest
+import io.ktor.http.ContentType
+import io.ktor.http.contentType
+import io.ktor.serialization.kotlinx.json.json
+import kotlinx.serialization.json.Json
+import okhttp3.logging.HttpLoggingInterceptor
+import okio.Path.Companion.toPath
+import org.koin.android.ext.koin.androidContext
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.dsl.module
+import org.michaelbel.core.dispatchers.di.dispatchersKoinModule
+import org.michaelbel.template.datastore.AppPreferences
+import org.michaelbel.template.interactor.AppInteractor
+import org.michaelbel.template.ktor.AppService
+import org.michaelbel.template.repository.AppRepository
+import org.michaelbel.template.room.AppDao
+import org.michaelbel.template.room.AppDatabase
+
+val appModule = module {
+ includes(dispatchersKoinModule)
+ single {
+ val dataStore = PreferenceDataStoreFactory.createWithPath(
+ migrations = emptyList(),
+ produceFile = { androidContext().filesDir.resolve(AppPreferences.DATA_STORE_NAME).absolutePath.toPath() }
+ )
+ AppPreferences(dataStore)
+ }
+ single {
+ val appDatabase = AppDatabase.getInstance(androidContext())
+ appDatabase.appDao()
+ }
+ single {
+ val chuckerInterceptor = ChuckerInterceptor.Builder(androidContext())
+ .collector(
+ collector = ChuckerCollector(
+ context = androidContext(),
+ showNotification = true,
+ retentionPeriod = RetentionManager.Period.ONE_HOUR
+ )
+ )
+ .maxContentLength(
+ length = 250_000L
+ )
+ .alwaysReadResponseBody(true)
+ .build()
+ val httpLoggingInterceptor = HttpLoggingInterceptor()
+ val ktorHttpClient = HttpClient(OkHttp) {
+ defaultRequest {
+ contentType(ContentType.Application.Json)
+ url("https://api.example.com/")
+ }
+ install(ContentNegotiation) {
+ json(Json { ignoreUnknownKeys = true })
+ }
+ install(HttpTimeout) {
+ requestTimeoutMillis = AppService.REQUEST_TIMEOUT_MILLIS
+ connectTimeoutMillis = AppService.CONNECT_TIMEOUT_MILLIS
+ socketTimeoutMillis = AppService.SOCKET_TIMEOUT_SECONDS
+ }
+ engine {
+ clientCacheSize = AppService.HTTP_CACHE_SIZE_BYTES
+ config {
+ addInterceptor(chuckerInterceptor)
+ addInterceptor(httpLoggingInterceptor)
+ }
+ }
+ }
+ AppService(ktorHttpClient)
+ }
+ single { AppRepository(get(), get(), get()) }
+ single { AppInteractor(get(), get()) }
+ viewModelOf(::MainViewModel)
+}
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/MainActivity.kt b/wear/src/main/kotlin/org/michaelbel/template/MainActivity.kt
new file mode 100644
index 00000000..cb00daba
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/MainActivity.kt
@@ -0,0 +1,18 @@
+package org.michaelbel.template
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import org.michaelbel.template.ui.MainActivityContent
+
+class MainActivity: ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ installSplashScreen()
+ super.onCreate(savedInstanceState)
+ setContent {
+ MainActivityContent()
+ }
+ }
+}
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/MainViewModel.kt b/wear/src/main/kotlin/org/michaelbel/template/MainViewModel.kt
new file mode 100644
index 00000000..b97933e0
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/MainViewModel.kt
@@ -0,0 +1,8 @@
+package org.michaelbel.template
+
+import org.michaelbel.core.viewmodel.BaseViewModel
+import org.michaelbel.template.interactor.AppInteractor
+
+class MainViewModel(
+ private val appInteractor: AppInteractor
+): BaseViewModel()
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/datastore/AppPreferences.kt b/wear/src/main/kotlin/org/michaelbel/template/datastore/AppPreferences.kt
new file mode 100644
index 00000000..5c752eff
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/datastore/AppPreferences.kt
@@ -0,0 +1,40 @@
+package org.michaelbel.template.datastore
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.stringPreferencesKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+
+class AppPreferences(
+ private val dataStore: DataStore
+) {
+ fun valueFlow(key: PreferenceKey, default: T): Flow {
+ return dataStore.data.map { preferences -> preferences[key.preferenceKey] ?: default }
+ }
+
+ suspend fun getValue(key: PreferenceKey): T? {
+ return dataStore.data.first()[key.preferenceKey]
+ }
+
+ suspend fun setValue(key: PreferenceKey, value: T) {
+ dataStore.edit { preferences -> preferences[key.preferenceKey] = value }
+ }
+
+ suspend fun removeValue(key: PreferenceKey) {
+ dataStore.edit { preferences -> preferences.remove(key.preferenceKey) }
+ }
+
+ companion object {
+ const val DATA_STORE_NAME = "app.preferences_pb"
+ private val PREFERENCE_USERNAME_KEY = stringPreferencesKey("username")
+ }
+
+ sealed class PreferenceKey(
+ val preferenceKey: Preferences.Key
+ ) {
+ data object PreferenceUsernameKey: PreferenceKey(PREFERENCE_USERNAME_KEY)
+ }
+}
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/interactor/AppInteractor.kt b/wear/src/main/kotlin/org/michaelbel/template/interactor/AppInteractor.kt
new file mode 100644
index 00000000..6b8bc916
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/interactor/AppInteractor.kt
@@ -0,0 +1,9 @@
+package org.michaelbel.template.interactor
+
+import org.michaelbel.core.dispatchers.AppDispatchers
+import org.michaelbel.template.repository.AppRepository
+
+class AppInteractor(
+ private val appDispatchers: AppDispatchers,
+ private val appRepository: AppRepository
+)
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/ktor/AppResponse.kt b/wear/src/main/kotlin/org/michaelbel/template/ktor/AppResponse.kt
new file mode 100644
index 00000000..9d1a41ef
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/ktor/AppResponse.kt
@@ -0,0 +1,10 @@
+package org.michaelbel.template.ktor
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AppResponse(
+ @SerialName("id") val id: Int,
+ @SerialName("name") val name: String
+)
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/ktor/AppService.kt b/wear/src/main/kotlin/org/michaelbel/template/ktor/AppService.kt
new file mode 100644
index 00000000..c0b99046
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/ktor/AppService.kt
@@ -0,0 +1,24 @@
+package org.michaelbel.template.ktor
+
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.request.get
+import io.ktor.client.request.parameter
+
+class AppService(
+ private val httpClient: HttpClient
+) {
+
+ suspend fun getAppResponse(id: Int): AppResponse {
+ return httpClient.get("route/$id") {
+ parameter("key", "1234")
+ }.body()
+ }
+
+ companion object {
+ const val REQUEST_TIMEOUT_MILLIS = 10_000L
+ const val SOCKET_TIMEOUT_SECONDS = 10_000L
+ const val HTTP_CACHE_SIZE_BYTES = 1024 * 1024 * 50
+ const val CONNECT_TIMEOUT_MILLIS = 10_000L
+ }
+}
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/repository/AppRepository.kt b/wear/src/main/kotlin/org/michaelbel/template/repository/AppRepository.kt
new file mode 100644
index 00000000..d11f8593
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/repository/AppRepository.kt
@@ -0,0 +1,11 @@
+package org.michaelbel.template.repository
+
+import org.michaelbel.template.datastore.AppPreferences
+import org.michaelbel.template.ktor.AppService
+import org.michaelbel.template.room.AppDao
+
+class AppRepository(
+ private val appPreferences: AppPreferences,
+ private val appDao: AppDao,
+ private val appService: AppService
+)
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/room/AppDao.kt b/wear/src/main/kotlin/org/michaelbel/template/room/AppDao.kt
new file mode 100644
index 00000000..105f0a6e
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/room/AppDao.kt
@@ -0,0 +1,16 @@
+package org.michaelbel.template.room
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface AppDao {
+
+ @Query("SELECT * FROM entities")
+ fun entitiesFlow(): Flow>
+
+ @Insert
+ suspend fun insertEntities(entities: List)
+}
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/room/AppDatabase.kt b/wear/src/main/kotlin/org/michaelbel/template/room/AppDatabase.kt
new file mode 100644
index 00000000..b3d0e938
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/room/AppDatabase.kt
@@ -0,0 +1,34 @@
+package org.michaelbel.template.room
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+
+@Database(
+ entities = [
+ AppEntity::class
+ ],
+ version = AppDatabase.DATABASE_VERSION,
+ exportSchema = false
+)
+abstract class AppDatabase: RoomDatabase() {
+
+ abstract fun appDao(): AppDao
+
+ companion object {
+ const val DATABASE_NAME = "app.db"
+ const val DATABASE_VERSION = 1
+
+ @Volatile
+ private var INSTANCE: AppDatabase? = null
+
+ fun getInstance(context: Context): AppDatabase =
+ INSTANCE ?: synchronized(this) {
+ INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
+ }
+
+ private fun buildDatabase(context: Context) =
+ Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, DATABASE_NAME).build()
+ }
+}
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/room/AppEntity.kt b/wear/src/main/kotlin/org/michaelbel/template/room/AppEntity.kt
new file mode 100644
index 00000000..1fe35185
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/room/AppEntity.kt
@@ -0,0 +1,9 @@
+package org.michaelbel.template.room
+
+import androidx.room.Entity
+
+@Entity(tableName = "entities", primaryKeys = ["id"])
+data class AppEntity(
+ val id: Int,
+ val name: String
+)
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/ui/MainActivityContent.kt b/wear/src/main/kotlin/org/michaelbel/template/ui/MainActivityContent.kt
new file mode 100644
index 00000000..7056c197
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/ui/MainActivityContent.kt
@@ -0,0 +1,108 @@
+@file:OptIn(ExperimentalHorologistApi::class)
+
+package org.michaelbel.template.ui
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Home
+import androidx.compose.material.icons.outlined.MailOutline
+import androidx.compose.material.icons.outlined.Settings
+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.unit.dp
+import androidx.wear.compose.navigation.SwipeDismissableNavHost
+import androidx.wear.compose.navigation.composable
+import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
+import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import com.google.android.horologist.compose.layout.AppScaffold
+import com.google.android.horologist.compose.layout.ScreenScaffold
+import com.google.android.horologist.compose.material.Chip
+import com.google.android.horologist.images.base.paintable.ImageVectorPaintable.Companion.asPaintable
+import org.koin.androidx.compose.koinViewModel
+import org.michaelbel.template.MainViewModel
+
+@Composable
+fun MainActivityContent(
+ modifier: Modifier = Modifier,
+ viewModel: MainViewModel = koinViewModel()
+) {
+ val navController = rememberSwipeDismissableNavController()
+ val navHostState = rememberSwipeDismissableNavHostState()
+
+ WearAppTheme {
+ AppScaffold {
+ SwipeDismissableNavHost(
+ startDestination = Navigation.Home.route,
+ navController = navController,
+ modifier = Modifier.background(Color.Transparent),
+ state = navHostState
+ ) {
+ composable(
+ route = Navigation.Home.route
+ ) {
+ ScreenScaffold {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ Chip(
+ label = "Navigate to Chat",
+ onClick = { navController.navigate(Navigation.Chat.route) },
+ modifier = Modifier.padding(horizontal = 16.dp),
+ icon = Icons.Outlined.MailOutline.asPaintable()
+ )
+ }
+ }
+ }
+ composable(
+ route = Navigation.Chat.route
+ ) {
+ ScreenScaffold {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ Chip(
+ label = "Navigate to Settings",
+ onClick = { navController.navigate(Navigation.Settings.route) },
+ modifier = Modifier.padding(horizontal = 16.dp),
+ icon = Icons.Outlined.Settings.asPaintable()
+ )
+ }
+ }
+ }
+ composable(
+ route = Navigation.Settings.route
+ ) {
+ ScreenScaffold {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ Chip(
+ label = "Navigate to Home",
+ onClick = { navController.navigate(Navigation.Home.route) },
+ modifier = Modifier.padding(horizontal = 16.dp),
+ icon = Icons.Outlined.Home.asPaintable()
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+sealed class Navigation(
+ val route: String
+) {
+ data object Home: Navigation("home")
+ data object Chat: Navigation("chat")
+ data object Settings: Navigation("settings")
+}
\ No newline at end of file
diff --git a/wear/src/main/kotlin/org/michaelbel/template/ui/Theme.kt b/wear/src/main/kotlin/org/michaelbel/template/ui/Theme.kt
new file mode 100644
index 00000000..7559dd66
--- /dev/null
+++ b/wear/src/main/kotlin/org/michaelbel/template/ui/Theme.kt
@@ -0,0 +1,15 @@
+package org.michaelbel.template.ui
+
+import androidx.compose.runtime.Composable
+import androidx.wear.compose.material.Colors
+import androidx.wear.compose.material.MaterialTheme
+
+@Composable
+fun WearAppTheme(
+ content: @Composable () -> Unit
+) {
+ MaterialTheme(
+ colors = Colors(),
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_launcher_background.xml b/wear/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..486ead32
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_launcher_foreground.xml b/wear/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 00000000..9f948082
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_splashscreen.xml b/wear/src/main/res/drawable/ic_splashscreen.xml
new file mode 100644
index 00000000..9ebcc22f
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_splashscreen.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_splashscreen_branding.xml b/wear/src/main/res/drawable/ic_splashscreen_branding.xml
new file mode 100644
index 00000000..b70cc528
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_splashscreen_branding.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_splashscreen_branding_layer_list.xml b/wear/src/main/res/drawable/ic_splashscreen_branding_layer_list.xml
new file mode 100644
index 00000000..32864d0e
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_splashscreen_branding_layer_list.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/mipmap-anydpi/ic_launcher.xml b/wear/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 00000000..6f3b755b
--- /dev/null
+++ b/wear/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/wear/src/main/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 00000000..6f3b755b
--- /dev/null
+++ b/wear/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/mipmap-hdpi/ic_launcher.webp b/wear/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 00000000..c209e78e
Binary files /dev/null and b/wear/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/wear/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/wear/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..b2dfe3d1
Binary files /dev/null and b/wear/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/wear/src/main/res/mipmap-mdpi/ic_launcher.webp b/wear/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 00000000..4f0f1d64
Binary files /dev/null and b/wear/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/wear/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/wear/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..62b611da
Binary files /dev/null and b/wear/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/wear/src/main/res/mipmap-xhdpi/ic_launcher.webp b/wear/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 00000000..948a3070
Binary files /dev/null and b/wear/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/wear/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/wear/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..1b9a6956
Binary files /dev/null and b/wear/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/wear/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/wear/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..28d4b77f
Binary files /dev/null and b/wear/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/wear/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/wear/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9287f508
Binary files /dev/null and b/wear/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/wear/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/wear/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..aa7d6427
Binary files /dev/null and b/wear/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/wear/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/wear/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9126ae37
Binary files /dev/null and b/wear/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/wear/src/main/res/values-night/colors.xml b/wear/src/main/res/values-night/colors.xml
new file mode 100644
index 00000000..316e6543
--- /dev/null
+++ b/wear/src/main/res/values-night/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #1C1B1F
+ #CCC2DC
+
\ No newline at end of file
diff --git a/wear/src/main/res/values-night/themes.xml b/wear/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..df629eb2
--- /dev/null
+++ b/wear/src/main/res/values-night/themes.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/values/colors.xml b/wear/src/main/res/values/colors.xml
new file mode 100644
index 00000000..804d9189
--- /dev/null
+++ b/wear/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #FFFBFE
+ #625B71
+
\ No newline at end of file
diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml
new file mode 100644
index 00000000..d626aa2d
--- /dev/null
+++ b/wear/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Template
+
\ No newline at end of file
diff --git a/wear/src/main/res/values/themes.xml b/wear/src/main/res/values/themes.xml
new file mode 100644
index 00000000..1808cdd1
--- /dev/null
+++ b/wear/src/main/res/values/themes.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file