diff --git a/.github/workflows/apply_spotless.yml b/.github/workflows/apply_spotless.yml index db01dad5..5ba1f6fe 100644 --- a/.github/workflows/apply_spotless.yml +++ b/.github/workflows/apply_spotless.yml @@ -44,10 +44,13 @@ jobs: - name: Run spotlessApply run: ./gradlew :compose:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace + - name: Run spotlessApply for Wear + run: ./gradlew :wear:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace + + - name: Run spotlessApply for Misc + run: ./gradlew :misc:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace + - name: Auto-commit if spotlessApply has changes uses: stefanzweifel/git-auto-commit-action@v5 with: - commit_message: Apply Spotless - - - name: Run spotlessApply for Wear - run: ./gradlew :wear:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace + commit_message: Apply Spotless \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8337b423..97b36f46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,3 +45,5 @@ jobs: run: ./gradlew :kotlin:build - name: Build Wear snippets run: ./gradlew :wear:build + - name: Build misc snippets + run: ./gradlew :misc:build diff --git a/misc/.gitignore b/misc/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/misc/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/misc/build.gradle.kts b/misc/build.gradle.kts new file mode 100644 index 00000000..de867bb8 --- /dev/null +++ b/misc/build.gradle.kts @@ -0,0 +1,72 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) + alias(libs.plugins.compose.compiler) +} + +android { + compileSdk = libs.versions.compileSdk.get().toInt() + namespace = "com.example.snippets" + + defaultConfig { + applicationId = "com.example.snippets" + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + kotlin { + jvmToolchain(17) + } + + buildTypes { + getByName("debug") { + signingConfig = signingConfigs.getByName("debug") + } + + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + buildFeatures { + compose = true + // Disable unused AGP features + viewBinding = true + } + +} +dependencies { + val composeBom = platform(libs.androidx.compose.bom) + implementation(composeBom) + androidTestImplementation(composeBom) + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.compose.runtime) + implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.compose.foundation.layout) + implementation(libs.androidx.compose.ui.util) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.material3) + + implementation(libs.hilt.android) + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.kotlinx.serialization.json) + ksp(libs.hilt.compiler) + + implementation(libs.androidx.lifecycle.runtime) + testImplementation(libs.junit) + androidTestImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.core) + androidTestImplementation(libs.androidx.test.runner) + androidTestImplementation(libs.androidx.test.espresso.core) +} diff --git a/shared/proguard-rules.pro b/misc/proguard-rules.pro similarity index 100% rename from shared/proguard-rules.pro rename to misc/proguard-rules.pro diff --git a/misc/src/main/AndroidManifest.xml b/misc/src/main/AndroidManifest.xml new file mode 100644 index 00000000..0a7a5c6f --- /dev/null +++ b/misc/src/main/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/misc/src/main/java/com/example/snippets/BroadcastReceiverJavaSnippets.java b/misc/src/main/java/com/example/snippets/BroadcastReceiverJavaSnippets.java new file mode 100644 index 00000000..08363995 --- /dev/null +++ b/misc/src/main/java/com/example/snippets/BroadcastReceiverJavaSnippets.java @@ -0,0 +1,120 @@ +package com.example.snippets; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; + +import androidx.activity.ComponentActivity; +import androidx.core.content.ContextCompat; + +import java.util.Objects; + +import javax.inject.Inject; + +import dagger.hilt.android.qualifiers.ApplicationContext; + +// Warning for reader: This file has the Java code snippets for completeness of the corresponding +// documentation page, but these snippets are not part of the actual sample. Refer to the Kotlin +// code for the actual sample. +public class BroadcastReceiverJavaSnippets { + + // [START android_broadcast_receiver_2_class_java] + public static class MyBroadcastReceiver extends BroadcastReceiver { + + @Inject + DataRepository dataRepository; + + @Override + public void onReceive(Context context, Intent intent) { + if (Objects.equals(intent.getAction(), "com.example.snippets.ACTION_UPDATE_DATA")) { + String data = intent.getStringExtra("com.example.snippets.DATA"); + // Do something with the data, for example send it to a data repository: + if (data != null) { dataRepository.updateData(data); } + } + } + } + // [END android_broadcast_receiver_2_class_java] + + /** @noinspection ConstantValue, unused */ + public static class BroadcastReceiverViewModel { + Context context; + + public BroadcastReceiverViewModel(@ApplicationContext Context context) { + this.context = context; + } + + // [START android_broadcast_receiver_3_definition_java] + MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver(); + // [END android_broadcast_receiver_3_definition_java] + + public void registerBroadcastReceiver() { + // [START android_broadcast_receiver_4_intent_filter_java] + IntentFilter filter = new IntentFilter("com.example.snippets.ACTION_UPDATE_DATA"); + // [END android_broadcast_receiver_4_intent_filter_java] + // [START android_broadcast_receiver_5_exported_java] + boolean listenToBroadcastsFromOtherApps = false; + int receiverFlags = listenToBroadcastsFromOtherApps + ? ContextCompat.RECEIVER_EXPORTED + : ContextCompat.RECEIVER_NOT_EXPORTED; + // [END android_broadcast_receiver_5_exported_java] + + // [START android_broadcast_receiver_6_register_java] + ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags); + // [END android_broadcast_receiver_6_register_java] + + // [START android_broadcast_receiver_12_register_with_permission_java] + ContextCompat.registerReceiver( + context, myBroadcastReceiver, filter, + android.Manifest.permission.ACCESS_COARSE_LOCATION, + null, // scheduler that defines thread, null means run on main thread + receiverFlags + ); + // [END android_broadcast_receiver_12_register_with_permission_java] + } + + public void unregisterBroadcastReceiver() { + context.unregisterReceiver(myBroadcastReceiver); + } + + public void sendBroadcast(String newData, boolean usePermission) { + if(usePermission) { + // [START android_broadcast_receiver_8_send_java] + Intent intent = new Intent("com.example.snippets.ACTION_UPDATE_DATA"); + intent.putExtra("com.example.snippets.DATA", newData); + intent.setPackage("com.example.snippets"); + context.sendBroadcast(intent); + // [END android_broadcast_receiver_8_send_java] + } else { + Intent intent = new Intent("com.example.snippets.ACTION_UPDATE_DATA"); + intent.putExtra("com.example.snippets.DATA", newData); + intent.setPackage("com.example.snippets"); + // [START android_broadcast_receiver_9_send_with_permission_java] + context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION); + // [END android_broadcast_receiver_9_send_with_permission_java] + } + } + } + + /** @noinspection InnerClassMayBeStatic*/ + // [START android_broadcast_receiver_13_activity_java] + class MyActivity extends ComponentActivity { + MyBroadcastReceiver myBroadcastReceiver; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // [START_EXCLUDE] + IntentFilter filter = new IntentFilter("com.example.snippets.ACTION_UPDATE_DATA"); + boolean listenToBroadcastsFromOtherApps = false; + int receiverFlags = listenToBroadcastsFromOtherApps + ? ContextCompat.RECEIVER_EXPORTED + : ContextCompat.RECEIVER_NOT_EXPORTED; + // [END_EXCLUDE] + ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags); + // Set content + } + } + // [END android_broadcast_receiver_13_activity_java] +} diff --git a/misc/src/main/java/com/example/snippets/BroadcastReceiverSnippets.kt b/misc/src/main/java/com/example/snippets/BroadcastReceiverSnippets.kt new file mode 100644 index 00000000..dda35cbe --- /dev/null +++ b/misc/src/main/java/com/example/snippets/BroadcastReceiverSnippets.kt @@ -0,0 +1,272 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.compose.LifecycleStartEffect +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +// Warning for reader: This file contains both the code snippets for apps _sending_ broadcasts, as +// those that are _receiving_ broadcasts. Do not consider this a reference implementation. +// +// The actual sample demonstrates how data can be passed from a broadcast receiver back to the UI, +// through an intermediary data repository. + +@AndroidEntryPoint +// [START android_broadcast_receiver_2_class] +class MyBroadcastReceiver : BroadcastReceiver() { + + @Inject + lateinit var dataRepository: DataRepository + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == "com.example.snippets.ACTION_UPDATE_DATA") { + val data = intent.getStringExtra("com.example.snippets.DATA") ?: "No data" + // Do something with the data, for example send it to a data repository: + dataRepository.updateData(data) + } + } +} +// [END android_broadcast_receiver_2_class] + +@HiltViewModel +class BroadcastReceiverViewModel @Inject constructor( + @ApplicationContext private val context: Context, + dataRepository: DataRepository +) : ViewModel() { + val data = dataRepository.data + + @Suppress("MemberVisibilityCanBePrivate") + // [START android_broadcast_receiver_3_definition] + val myBroadcastReceiver = MyBroadcastReceiver() + // [END android_broadcast_receiver_3_definition] + + fun registerBroadcastReceiver() { + // [START android_broadcast_receiver_4_intent_filter] + val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA") + // [END android_broadcast_receiver_4_intent_filter] + // [START android_broadcast_receiver_5_exported] + val listenToBroadcastsFromOtherApps = false + val receiverFlags = if (listenToBroadcastsFromOtherApps) { + ContextCompat.RECEIVER_EXPORTED + } else { + ContextCompat.RECEIVER_NOT_EXPORTED + } + // [END android_broadcast_receiver_5_exported] + + // [START android_broadcast_receiver_6_register] + ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags) + // [END android_broadcast_receiver_6_register] + + // [START android_broadcast_receiver_12_register_with_permission] + ContextCompat.registerReceiver( + context, myBroadcastReceiver, filter, + android.Manifest.permission.ACCESS_COARSE_LOCATION, + null, // scheduler that defines thread, null means run on main thread + receiverFlags + ) + // [END android_broadcast_receiver_12_register_with_permission] + } + + fun unregisterBroadcastReceiver() { + context.unregisterReceiver(myBroadcastReceiver) + } + + fun sendBroadcast(newData: String, usePermission: Boolean = false) { + if (!usePermission) { + // [START android_broadcast_receiver_8_send] + val intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply { + putExtra("com.example.snippets.DATA", newData) + setPackage("com.example.snippets") + } + context.sendBroadcast(intent) + // [END android_broadcast_receiver_8_send] + } else { + val intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply { + putExtra("com.example.snippets.DATA", newData) + setPackage("com.example.snippets") + } + // [START android_broadcast_receiver_9_send_with_permission] + context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION) + // [END android_broadcast_receiver_9_send_with_permission] + } + } +} + +@Suppress("NAME_SHADOWING") +@Composable +fun LifecycleScopedBroadcastReceiver( + registerReceiver: () -> Unit, + unregisterReceiver: () -> Unit +) { + val registerReceiver by rememberUpdatedState(registerReceiver) + val unregisterReceiver by rememberUpdatedState(unregisterReceiver) + // [START android_broadcast_receiver_7_lifecycle_scoped] + LifecycleStartEffect(true) { + registerReceiver() + onStopOrDispose { unregisterReceiver() } + } + // [END android_broadcast_receiver_7_lifecycle_scoped] +} + +@Composable +fun BroadcastReceiverSample( + modifier: Modifier = Modifier, + viewModel: BroadcastReceiverViewModel = hiltViewModel() +) { + val data by viewModel.data.collectAsStateWithLifecycle() + BroadcastReceiverSample( + modifier = modifier, + data = data, + registerBroadcastReceiver = viewModel::registerBroadcastReceiver, + unregisterBroadcastReceiver = viewModel::unregisterBroadcastReceiver, + sendBroadcast = viewModel::sendBroadcast + ) +} + +@Composable +fun BroadcastReceiverSample( + modifier: Modifier = Modifier, + data: String, + registerBroadcastReceiver: () -> Unit, + unregisterBroadcastReceiver: () -> Unit, + sendBroadcast: (newData: String) -> Unit +) { + var newData by remember { mutableStateOf("") } + Scaffold { innerPadding -> + Column( + modifier + .padding(innerPadding) + .padding(16.dp) + ) { + Text("Fill in a word, send broadcast, and see it added to the bottom") + Spacer(Modifier.height(16.dp)) + TextField(newData, onValueChange = { newData = it }, Modifier.widthIn(min = 160.dp)) + Spacer(Modifier.height(16.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button(onClick = { sendBroadcast(newData) }) { + Text("Send broadcast") + } + } + Spacer(Modifier.height(16.dp)) + Text(data, Modifier.verticalScroll(rememberScrollState())) + } + } + LifecycleScopedBroadcastReceiver(registerBroadcastReceiver, unregisterBroadcastReceiver) +} + +class MyBroadcastReceiverWithPermission : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + // no-op, only used to demonstrate manifest registration of receiver with permission + } +} + +// Ignore following code - it's only used to demonstrate best practices, not part of the sample +@Suppress("unused") +// [START android_broadcast_receiver_13_activity] +class MyActivity : ComponentActivity() { + private val myBroadcastReceiver = MyBroadcastReceiver() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // [START_EXCLUDE] + val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA") + val listenToBroadcastsFromOtherApps = false + val receiverFlags = if (listenToBroadcastsFromOtherApps) { + ContextCompat.RECEIVER_EXPORTED + } else { + ContextCompat.RECEIVER_NOT_EXPORTED + } + + // [END_EXCLUDE] + ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags) + setContent { MyApp() } + } + + override fun onDestroy() { + super.onDestroy() + // When you forget to unregister your receiver here, you're causing a leak! + this.unregisterReceiver(myBroadcastReceiver) + } +} +// [END android_broadcast_receiver_13_activity] + +@Composable +fun MyApp() {} + +@Suppress("unused") +// [START android_broadcast_receiver_14_stateless] +@Composable +fun MyStatefulScreen() { + val myBroadcastReceiver = remember { MyBroadcastReceiver() } + val context = LocalContext.current + LifecycleStartEffect(true) { + // [START_EXCLUDE] + val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA") + val listenToBroadcastsFromOtherApps = false + val flags = if (listenToBroadcastsFromOtherApps) { + ContextCompat.RECEIVER_EXPORTED + } else { + ContextCompat.RECEIVER_NOT_EXPORTED + } + // [END_EXCLUDE] + ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, flags) + onStopOrDispose { context.unregisterReceiver(myBroadcastReceiver) } + } + MyStatelessScreen() +} + +@Composable +fun MyStatelessScreen() { + // Implement your screen +} +// [END android_broadcast_receiver_14_stateless] diff --git a/misc/src/main/java/com/example/snippets/DataRepository.kt b/misc/src/main/java/com/example/snippets/DataRepository.kt new file mode 100644 index 00000000..7e626e5a --- /dev/null +++ b/misc/src/main/java/com/example/snippets/DataRepository.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets + +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +@Singleton +class DataRepository @Inject constructor() { + // You would normally save this data in a database or other persistent storage + private val _data: MutableStateFlow = MutableStateFlow("This text will be updated") + val data: StateFlow = _data + + fun updateData(data: String) { + _data.value += "\n$data" + } +} diff --git a/misc/src/main/java/com/example/snippets/MainActivity.kt b/misc/src/main/java/com/example/snippets/MainActivity.kt new file mode 100644 index 00000000..a687939e --- /dev/null +++ b/misc/src/main/java/com/example/snippets/MainActivity.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.ui.Modifier +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.example.snippets.navigation.Destination +import com.example.snippets.navigation.LandingScreen +import com.example.snippets.ui.theme.SnippetsTheme +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + SnippetsTheme { + val navController = rememberNavController() + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + NavHost(navController, startDestination = "LandingScreen") { + composable("LandingScreen") { + LandingScreen { navController.navigate(it.route) } + } + Destination.entries.forEach { destination -> + composable(destination.route) { + when (destination) { + Destination.BroadcastReceiverExamples -> BroadcastReceiverSample() + // Add your destination here + } + } + } + } + } + } + } + } +} diff --git a/misc/src/main/java/com/example/snippets/MyApplication.kt b/misc/src/main/java/com/example/snippets/MyApplication.kt new file mode 100644 index 00000000..4e4a187c --- /dev/null +++ b/misc/src/main/java/com/example/snippets/MyApplication.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MyApplication : Application() diff --git a/misc/src/main/java/com/example/snippets/navigation/Destination.kt b/misc/src/main/java/com/example/snippets/navigation/Destination.kt new file mode 100644 index 00000000..59f424a7 --- /dev/null +++ b/misc/src/main/java/com/example/snippets/navigation/Destination.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.navigation + +enum class Destination(val route: String, val title: String) { + BroadcastReceiverExamples("broadcastReceiverExamples", "Broadcast Receiver Examples"), + // Add your example here +} diff --git a/misc/src/main/java/com/example/snippets/navigation/LandingScreen.kt b/misc/src/main/java/com/example/snippets/navigation/LandingScreen.kt new file mode 100644 index 00000000..f096906d --- /dev/null +++ b/misc/src/main/java/com/example/snippets/navigation/LandingScreen.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.navigation + +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ListItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LandingScreen( + navigate: (Destination) -> Unit +) { + Scaffold( + topBar = { + TopAppBar(title = { + Text(text = "Android snippets",) + }) + } + ) { padding -> + NavigationItems(modifier = Modifier.padding(padding)) { navigate(it) } + } +} + +@Composable +fun NavigationItems( + modifier: Modifier = Modifier, + navigate: (Destination) -> Unit +) { + LazyColumn( + modifier = modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + items(Destination.entries) { destination -> + NavigationItem(destination) { + navigate( + destination + ) + } + } + } +} + +@Composable +fun NavigationItem(destination: Destination, onClick: () -> Unit) { + ListItem( + headlineContent = { + Text(destination.title) + }, + modifier = Modifier + .heightIn(min = 48.dp) + .clickable { + onClick() + } + ) +} diff --git a/misc/src/main/java/com/example/snippets/ui/theme/Color.kt b/misc/src/main/java/com/example/snippets/ui/theme/Color.kt new file mode 100644 index 00000000..662ef48f --- /dev/null +++ b/misc/src/main/java/com/example/snippets/ui/theme/Color.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) diff --git a/misc/src/main/java/com/example/snippets/ui/theme/Theme.kt b/misc/src/main/java/com/example/snippets/ui/theme/Theme.kt new file mode 100644 index 00000000..4dcf62e8 --- /dev/null +++ b/misc/src/main/java/com/example/snippets/ui/theme/Theme.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.ui.theme + +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun SnippetsTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/misc/src/main/java/com/example/snippets/ui/theme/Type.kt b/misc/src/main/java/com/example/snippets/ui/theme/Type.kt new file mode 100644 index 00000000..f383a07b --- /dev/null +++ b/misc/src/main/java/com/example/snippets/ui/theme/Type.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) diff --git a/shared/src/main/res/drawable/ic_launcher_background.xml b/misc/src/main/res/drawable/ic_launcher_background.xml similarity index 54% rename from shared/src/main/res/drawable/ic_launcher_background.xml rename to misc/src/main/res/drawable/ic_launcher_background.xml index 336b6dca..07d5da9c 100644 --- a/shared/src/main/res/drawable/ic_launcher_background.xml +++ b/misc/src/main/res/drawable/ic_launcher_background.xml @@ -1,171 +1,170 @@ - + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> diff --git a/misc/src/main/res/drawable/ic_launcher_foreground.xml b/misc/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/misc/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/shared/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/misc/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 79% rename from shared/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to misc/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index eca70cfe..6f3b755b 100644 --- a/shared/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/misc/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/shared/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/misc/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 79% rename from shared/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to misc/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index eca70cfe..6f3b755b 100644 --- a/shared/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/misc/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/misc/src/main/res/mipmap-hdpi/ic_launcher.webp b/misc/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..c209e78e Binary files /dev/null and b/misc/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/misc/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/misc/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 00000000..b2dfe3d1 Binary files /dev/null and b/misc/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/misc/src/main/res/mipmap-mdpi/ic_launcher.webp b/misc/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..4f0f1d64 Binary files /dev/null and b/misc/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/misc/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/misc/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 00000000..62b611da Binary files /dev/null and b/misc/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/misc/src/main/res/mipmap-xhdpi/ic_launcher.webp b/misc/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..948a3070 Binary files /dev/null and b/misc/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/misc/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/misc/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..1b9a6956 Binary files /dev/null and b/misc/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/misc/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/misc/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..28d4b77f Binary files /dev/null and b/misc/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/misc/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/misc/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9287f508 Binary files /dev/null and b/misc/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/misc/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/misc/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..aa7d6427 Binary files /dev/null and b/misc/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/misc/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/misc/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9126ae37 Binary files /dev/null and b/misc/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/misc/src/main/res/values/colors.xml b/misc/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/misc/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/misc/src/main/res/values/strings.xml b/misc/src/main/res/values/strings.xml new file mode 100644 index 00000000..870fc473 --- /dev/null +++ b/misc/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Background Snippets + \ No newline at end of file diff --git a/misc/src/main/res/values/themes.xml b/misc/src/main/res/values/themes.xml new file mode 100644 index 00000000..65078ebe --- /dev/null +++ b/misc/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + + - - diff --git a/wear/src/main/java/com/example/wear/snippets/rotary/Rotary.kt b/wear/src/main/java/com/example/wear/snippets/rotary/Rotary.kt index a6115816..97bace50 100644 --- a/wear/src/main/java/com/example/wear/snippets/rotary/Rotary.kt +++ b/wear/src/main/java/com/example/wear/snippets/rotary/Rotary.kt @@ -243,7 +243,7 @@ fun SnapScrollableScreen() { } @Composable -fun PositionScrollIndicator(){ +fun PositionScrollIndicator() { // [START android_wear_rotary_position_indicator] val listState = rememberScalingLazyListState() Scaffold( diff --git a/wear/src/main/java/com/example/wear/snippets/tile/Tile.kt b/wear/src/main/java/com/example/wear/snippets/tile/Tile.kt index 74a29483..58cbaa75 100644 --- a/wear/src/main/java/com/example/wear/snippets/tile/Tile.kt +++ b/wear/src/main/java/com/example/wear/snippets/tile/Tile.kt @@ -33,21 +33,25 @@ private const val RESOURCES_VERSION = "1" class MyTileService : TileService() { override fun onTileRequest(requestParams: RequestBuilders.TileRequest) = - Futures.immediateFuture(Tile.Builder() - .setResourcesVersion(RESOURCES_VERSION) - .setTileTimeline( - Timeline.fromLayoutElement( - Text.Builder(this, "Hello World!") - .setTypography(Typography.TYPOGRAPHY_BODY1) - .setColor(argb(0xFFFFFFFF.toInt())) - .build())) - .build()) + Futures.immediateFuture( + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTileTimeline( + Timeline.fromLayoutElement( + Text.Builder(this, "Hello World!") + .setTypography(Typography.TYPOGRAPHY_BODY1) + .setColor(argb(0xFFFFFFFF.toInt())) + .build() + ) + ) + .build() + ) override fun onTileResourcesRequest(requestParams: ResourcesRequest) = - Futures.immediateFuture(Resources.Builder() - .setVersion(RESOURCES_VERSION) - .build() + Futures.immediateFuture( + Resources.Builder() + .setVersion(RESOURCES_VERSION) + .build() ) - } -// [END android_wear_tile_mytileservice] \ No newline at end of file +// [END android_wear_tile_mytileservice]