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