wear decompose stub
This commit is contained in:
@@ -5,9 +5,10 @@ plugins {
|
||||
alias(libs.plugins.convention.compose)
|
||||
alias(libs.plugins.convention.koin)
|
||||
alias(libs.plugins.convention.ktor.client)
|
||||
alias(libs.plugins.convention.serialization)
|
||||
}
|
||||
|
||||
val appVersion = "1.27.1"
|
||||
val appVersion = "1.0"
|
||||
|
||||
android {
|
||||
buildFeatures { buildConfig = true }
|
||||
@@ -24,6 +25,9 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(project(":libs:ui"))
|
||||
implementation(project(":libs:http-client"))
|
||||
implementation(project(":libs:base"))
|
||||
implementation(libs.activity.compose)
|
||||
implementation(libs.androidx.compose.foundation)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
|
||||
@@ -6,51 +6,21 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name=".presentation.App"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
<service
|
||||
android:name=".complication.MainComplicationService"
|
||||
android:exported="true"
|
||||
android:label="@string/complication_label"
|
||||
android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
|
||||
<intent-filter>
|
||||
<action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.wearable.complications.SUPPORTED_TYPES"
|
||||
android:value="SHORT_TEXT" />
|
||||
<meta-data
|
||||
android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
|
||||
android:value="3600" />
|
||||
</service>
|
||||
<service
|
||||
android:name=".tile.MainTileService"
|
||||
android:exported="true"
|
||||
android:label="@string/tile_label"
|
||||
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.wear.tiles.PREVIEW"
|
||||
android:resource="@drawable/tile_preview" />
|
||||
</service>
|
||||
|
||||
<uses-library
|
||||
android:name="com.google.android.wearable"
|
||||
android:required="true" />
|
||||
android:required="false" />
|
||||
|
||||
<uses-library
|
||||
android:name="wear-sdk"
|
||||
android:required="false" />
|
||||
<!--
|
||||
Set to true if your app is Standalone, that is, it does not require the handheld
|
||||
app to run.
|
||||
-->
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.wearable.standalone"
|
||||
android:value="true" />
|
||||
@@ -68,4 +38,4 @@
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
package ru.shadowsparky.gigawear.complication
|
||||
|
||||
import androidx.wear.watchface.complications.data.ComplicationData
|
||||
import androidx.wear.watchface.complications.data.ComplicationType
|
||||
import androidx.wear.watchface.complications.data.PlainComplicationText
|
||||
import androidx.wear.watchface.complications.data.ShortTextComplicationData
|
||||
import androidx.wear.watchface.complications.datasource.ComplicationRequest
|
||||
import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService
|
||||
import java.util.Calendar
|
||||
|
||||
/**
|
||||
* Skeleton for complication data source that returns short text.
|
||||
*/
|
||||
class MainComplicationService : SuspendingComplicationDataSourceService() {
|
||||
|
||||
override fun getPreviewData(type: ComplicationType): ComplicationData? {
|
||||
if (type != ComplicationType.SHORT_TEXT) {
|
||||
return null
|
||||
}
|
||||
return createComplicationData("Mon", "Monday")
|
||||
}
|
||||
|
||||
override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData {
|
||||
return when (Calendar.getInstance().get(Calendar.DAY_OF_WEEK)) {
|
||||
Calendar.SUNDAY -> createComplicationData("Sun", "Sunday")
|
||||
Calendar.MONDAY -> createComplicationData("Mon", "Monday")
|
||||
Calendar.TUESDAY -> createComplicationData("Tue", "Tuesday")
|
||||
Calendar.WEDNESDAY -> createComplicationData("Wed", "Wednesday")
|
||||
Calendar.THURSDAY -> createComplicationData("Thu", "Thursday")
|
||||
Calendar.FRIDAY -> createComplicationData("Fri", "Friday")
|
||||
Calendar.SATURDAY -> createComplicationData("Sat", "Saturday")
|
||||
else -> throw IllegalArgumentException("too many days")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createComplicationData(text: String, contentDescription: String) =
|
||||
ShortTextComplicationData.Builder(
|
||||
text = PlainComplicationText.Builder(text).build(),
|
||||
contentDescription = PlainComplicationText.Builder(contentDescription).build()
|
||||
).build()
|
||||
}
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
/* While this template provides a good starting point for using Wear Compose, you can always
|
||||
* take a look at https://github.com/android/wear-os-samples/tree/main/ComposeStarter to find the
|
||||
* most up to date changes to the libraries and their usages.
|
||||
*/
|
||||
|
||||
package ru.shadowsparky.gigawear.presentation
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.speech.RecognizerIntent
|
||||
import android.speech.tts.TextToSpeech
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
|
||||
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
|
||||
import androidx.wear.compose.material3.AppScaffold
|
||||
import androidx.wear.compose.material3.Button
|
||||
import androidx.wear.compose.material3.ButtonDefaults
|
||||
import androidx.wear.compose.material3.EdgeButton
|
||||
import androidx.wear.compose.material3.ListHeader
|
||||
import androidx.wear.compose.material3.MaterialTheme
|
||||
import androidx.wear.compose.material3.ScreenScaffold
|
||||
import androidx.wear.compose.material3.SurfaceTransformation
|
||||
import androidx.wear.compose.material3.Text
|
||||
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
|
||||
import androidx.wear.compose.material3.lazy.transformedHeight
|
||||
import ru.shadowsparky.gigawear.R
|
||||
import ru.shadowsparky.gigawear.presentation.theme.AndroidDevTheme
|
||||
import java.util.Locale
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
WearApp("Android")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WearApp(greetingName: String) {
|
||||
val context = LocalContext.current
|
||||
var spokenText by remember { mutableStateOf("Тут будет твой текст") }
|
||||
val tts = remember { mutableStateOf<TextToSpeech?>(null) }
|
||||
DisposableEffect(Unit) {
|
||||
tts.value = TextToSpeech(context) { status ->
|
||||
if (status == TextToSpeech.SUCCESS) {
|
||||
tts.value?.language = Locale.getDefault()
|
||||
}
|
||||
}
|
||||
onDispose {
|
||||
tts.value?.stop()
|
||||
tts.value?.shutdown()
|
||||
}
|
||||
}
|
||||
val voiceLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val results = result.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
|
||||
val text = results?.firstOrNull()
|
||||
if (!text.isNullOrBlank()) {
|
||||
spokenText = text
|
||||
tts.value?.speak(text, TextToSpeech.QUEUE_FLUSH, null, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
AndroidDevTheme {
|
||||
AppScaffold {
|
||||
val listState = rememberTransformingLazyColumnState()
|
||||
val transformationSpec = rememberTransformationSpec()
|
||||
ScreenScaffold(
|
||||
scrollState = listState,
|
||||
edgeButton = {
|
||||
EdgeButton(
|
||||
onClick = {
|
||||
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
|
||||
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
|
||||
putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault().toString())
|
||||
}
|
||||
try {
|
||||
voiceLauncher.launch(intent)
|
||||
} catch (e: Exception) {
|
||||
spokenText = " Ошибка: Голосовой ввод недоступен"
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
) {
|
||||
Text(" Начать ввод")
|
||||
}
|
||||
},
|
||||
) { contentPadding ->
|
||||
TransformingLazyColumn(contentPadding = contentPadding, state = listState) {
|
||||
item {
|
||||
ListHeader(
|
||||
modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
|
||||
transformation = SurfaceTransformation(transformationSpec),
|
||||
) {
|
||||
Text(text = stringResource(R.string.hello_world, greetingName))
|
||||
}
|
||||
}
|
||||
item {
|
||||
Button(
|
||||
onClick = {
|
||||
tts.value?.speak(spokenText, TextToSpeech.QUEUE_FLUSH, null, null)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
|
||||
transformation = SurfaceTransformation(transformationSpec),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Text(text = spokenText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package ru.shadowsparky.gigawear.presentation.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.wear.compose.material3.MaterialTheme
|
||||
|
||||
@Composable
|
||||
fun AndroidDevTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
/**
|
||||
* Empty theme to customize for your app.
|
||||
* See: https://developer.android.com/jetpack/compose/designsystems/custom
|
||||
*/
|
||||
MaterialTheme(
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package ru.shadowsparky.gigawear.tile
|
||||
|
||||
import android.content.Context
|
||||
import androidx.wear.protolayout.ResourceBuilders.Resources
|
||||
import androidx.wear.protolayout.TimelineBuilders
|
||||
import androidx.wear.protolayout.material3.Typography.BODY_LARGE
|
||||
import androidx.wear.tiles.RequestBuilders
|
||||
import androidx.wear.tiles.TileBuilders
|
||||
import androidx.wear.tiles.TileService
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import androidx.wear.protolayout.material3.materialScope
|
||||
import androidx.wear.protolayout.material3.primaryLayout
|
||||
import androidx.wear.protolayout.material3.text
|
||||
import androidx.wear.protolayout.types.layoutString
|
||||
import androidx.wear.tiles.RequestBuilders.ResourcesRequest
|
||||
import androidx.wear.tiles.tooling.preview.Preview
|
||||
import androidx.wear.tiles.tooling.preview.TilePreviewData
|
||||
import androidx.wear.tooling.preview.devices.WearDevices
|
||||
import ru.shadowsparky.gigawear.R
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
|
||||
private const val RESOURCES_VERSION = "0"
|
||||
|
||||
class MainTileService : TileService() {
|
||||
override fun onTileRequest(requestParams: RequestBuilders.TileRequest): ListenableFuture<TileBuilders.Tile> =
|
||||
Futures.immediateFuture(tile(requestParams, this))
|
||||
|
||||
override fun onTileResourcesRequest(requestParams: ResourcesRequest): ListenableFuture<Resources> =
|
||||
Futures.immediateFuture(resources(requestParams))
|
||||
}
|
||||
|
||||
private fun resources(requestParams: ResourcesRequest): Resources {
|
||||
return Resources.Builder()
|
||||
.setVersion(RESOURCES_VERSION)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun tile(
|
||||
requestParams: RequestBuilders.TileRequest,
|
||||
context: Context,
|
||||
): TileBuilders.Tile {
|
||||
return TileBuilders.Tile.Builder()
|
||||
.setResourcesVersion(RESOURCES_VERSION)
|
||||
.setTileTimeline(
|
||||
TimelineBuilders.Timeline.fromLayoutElement(
|
||||
materialScope(context, requestParams.deviceConfiguration) {
|
||||
primaryLayout(
|
||||
mainSlot = {
|
||||
text(
|
||||
context.getString(R.string.hello_world, "Tile").layoutString,
|
||||
typography = BODY_LARGE
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Preview(device = WearDevices.SMALL_ROUND)
|
||||
@Preview(device = WearDevices.LARGE_ROUND)
|
||||
fun tilePreview(context: Context) = TilePreviewData(::resources) {
|
||||
tile(it, context)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package ru.shadowsparky.gigawear
|
||||
|
||||
import org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Configuration
|
||||
import org.koin.core.annotation.Module
|
||||
|
||||
@Module
|
||||
@ComponentScan
|
||||
@Configuration
|
||||
class GigaWearModule
|
||||
@@ -0,0 +1,16 @@
|
||||
package ru.shadowsparky.gigawear.presentation
|
||||
|
||||
import android.app.Application
|
||||
import org.koin.core.annotation.KoinApplication
|
||||
import org.koin.plugin.module.dsl.startKoin
|
||||
|
||||
@KoinApplication
|
||||
annotation class GigaWearApp
|
||||
|
||||
class App : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
startKoin<GigaWearApp>()
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package ru.shadowsparky.gigawear.presentation
|
||||
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import kotlinx.serialization.KSerializer
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.annotation.Factory
|
||||
import ru.shadowsparky.gigawear.presentation.nav.Route
|
||||
import ru.shadowsparky.ui.nav.RootComponent
|
||||
|
||||
class GigaWearRootComponent(
|
||||
context: ComponentContext
|
||||
) : RootComponent<Route>(context) {
|
||||
override val initStack: List<Route> = listOf(Route.MainScreen())
|
||||
override val serializer: KSerializer<Route> = Route.serializer()
|
||||
override val koin: Koin = ru.shadowsparky.koin
|
||||
}
|
||||
|
||||
@Factory
|
||||
class RootComponentFactory {
|
||||
|
||||
fun create(context: ComponentContext): GigaWearRootComponent {
|
||||
return GigaWearRootComponent(context)
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package ru.shadowsparky.gigawear.presentation
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import com.arkivanov.decompose.retainedComponent
|
||||
import org.koin.android.ext.android.inject
|
||||
import ru.shadowsparky.gigawear.presentation.main.MainScreen
|
||||
import ru.shadowsparky.gigawear.presentation.nav.Child
|
||||
import ru.shadowsparky.ui.nav.Nav
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val rootFactory by inject<RootComponentFactory>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val root = retainedComponent { rootFactory.create(it) }
|
||||
setContent {
|
||||
Nav(root) { child, _ ->
|
||||
when (child) {
|
||||
is Child.MainChild -> MainScreen(child.component)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package ru.shadowsparky.gigawear.presentation.main
|
||||
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.koin.core.annotation.Named
|
||||
import ru.shadowsparky.gigawear.presentation.nav.Child
|
||||
import ru.shadowsparky.gigawear.presentation.nav.Route
|
||||
import ru.shadowsparky.ui.nav.ComponentFactory
|
||||
import ru.shadowsparky.ui.nav.RouteNavigator
|
||||
|
||||
class MainComponent(
|
||||
private val context: ComponentContext
|
||||
) : ComponentContext by context {
|
||||
}
|
||||
|
||||
@Factory
|
||||
@Named(Route.MAIN)
|
||||
class MainComponentFactory : ComponentFactory<Child, Route.MainScreen, Route> {
|
||||
override fun create(
|
||||
route: Route.MainScreen,
|
||||
child: ComponentContext,
|
||||
root: RouteNavigator<Route>
|
||||
): Child {
|
||||
val component = MainComponent(child)
|
||||
return Child.MainChild(component)
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package ru.shadowsparky.gigawear.presentation.main
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.safeContentPadding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun MainScreen(component: MainComponent) {
|
||||
Box(modifier = Modifier.safeContentPadding()) {
|
||||
Text("MainScreen $component")
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package ru.shadowsparky.gigawear.presentation.nav
|
||||
|
||||
import ru.shadowsparky.gigawear.presentation.main.MainComponent
|
||||
|
||||
sealed class Child : ru.shadowsparky.ui.nav.Child {
|
||||
class MainChild(val component: MainComponent) : Child()
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package ru.shadowsparky.gigawear.presentation.nav
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class Route : ru.shadowsparky.ui.nav.Route() {
|
||||
|
||||
@Serializable
|
||||
data class MainScreen(override val routeId: String = MAIN) : Route()
|
||||
|
||||
companion object {
|
||||
const val MAIN = "main"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
<resources>
|
||||
|
||||
<style name="MainActivityTheme.Starting" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@android:color/black</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||
<item name="postSplashScreenTheme">@android:style/Theme.DeviceDefault</item>
|
||||
</style>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user