Merge pull request #2240 from anayw2001/main

Add Android Nearby Sharing sample app
This commit is contained in:
Anay Wadhera
2024-01-10 17:43:03 -08:00
committed by GitHub
36 changed files with 901 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
# Nearby Sharing Android API
## The slice API
The nearby module in GMSCore provides a third-party accessible slice to bind to, to provide live data on nearby targets using Nearby Share. Clicking on any of these targets will take you to the main Nearby Share screen, where the transfer will begin and continually update the progress.
The slice URI is `content://com.google.android.gms.nearby.sharing/scan`. Upon first launch after install, you will be prompted to grant permission to the client application. After that, the slice will be kept up to date with the latest targets nearby.
## How to use the API ##
In Kotlin:
```kotlin
// Derive the Slice URI.
val sliceUri = Uri.parse("content://com.google.android.gms.nearby.sharing/scan")
// Get the SliceViewManager
val sliceManager = SliceViewManager.getInstance(context)
// Pin the slice and register your callback.
sliceManager.registerSliceCallback(sliceUri, { slice: Slice? ->
if (slice == null) {
return
}
for (targetItem in slice.items.reversed()) {
// Each row containing a target has the hints LIST_ITEM and ACTIVITY.
if (!(targetItem.format == SLICE && targetItem.hints.containsAll(listOf(LIST_ITEM, ACTIVITY)))) {
continue
}
val targetSlice = targetItem.slice
var deviceName: String? = null
var action: PendingIntent? = null
var profileIcon: IconCompat? = null
for (item in targetSlice.items) {
// The slice item of the target's device name contains the TITLE hint.
if (item.format == TEXT && item.hints.contains(TITLE)) {
deviceName = item.text.toString()
}
// The slice item of the target action contains the SHORTCUT and TITLE hints.
if (item.format == ACTION && item.hints.containsAll(listOf(SHORTCUT, TITLE))) {
action = item.action
val iconSlice: Slice? = item.slice
if (iconSlice != null) {
for (iconitem in iconSlice.items) {
// The target's icon is indicated by the IMAGE slice item format and the NO_TINT hint.
if (iconitem.format == IMAGE && iconitem.hints.contains(NO_TINT)) {
profileIcon = iconitem.icon
}
}
}
}
}
// Returns null if the data parsed from the slice is incomplete.
if (deviceName == null || action == null || profileIcon == null) {
continue
}
}
})
// Remember to bind the slice after you pin it to avoid race conditions!
val slice = sliceManager.bindSlice(sliceUri)
```
Refer to the [SliceViewManager#registerSliceCallback() documentation](https://developer.android.com/reference/androidx/slice/SliceViewManager#registerSliceCallback(android.net.Uri,androidx.slice.SliceViewManager.SliceCallback)) for more information about the race condition described above.
+16
View File
@@ -0,0 +1,16 @@
*.iml
.gradle
.idea
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
+25
View File
@@ -0,0 +1,25 @@
# Nearby Share sample app
This app is a demonstration of how to bind to and use the Nearby Share slice.
## Build instructions
From the root of the repository:
```
// Run this before importing into Android Studio!
$ cd sharing/android/example
$ gradle wrapper
$ ./gradlew build
```
For the gradle wrapper, we recommend Gradle 8.0+.
## Key callouts
MainViewModel - contains the business logic for processing and binding to the slice, as well as the lifecycle of the slice.
MainActivity - hosts the `MainView` composable and provides the intent to fill in the slice's PendingIntent action with, providing the data to send via Nearby Share.
## Screenshots
<p align="left">
<img alt="No devices available screenshot" src="screenshots/no_devices.png" width="33%">
<img alt="Devices available screenshot" src="screenshots/devices.png" width="33%">
<img alt="Consent prompt screenshot" src="screenshots/consent.png" width="33%">
</p>
+1
View File
@@ -0,0 +1 @@
/build
@@ -0,0 +1,62 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.google.nearby.sharedemo"
compileSdk = 33
defaultConfig {
applicationId = "com.google.nearby.sharedemo"
minSdk = 30
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.3"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.7.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.slice:slice-core:1.1.0-alpha02")
implementation("androidx.slice:slice-view:1.1.0-alpha02")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.NearbyShareDemo"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.NearbyShareDemo">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,165 @@
package com.google.nearby.sharedemo
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.OpenableColumns
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.slice.Slice
import androidx.slice.widget.SliceView
import com.google.nearby.sharedemo.ui.theme.NearbyShareDemoTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel: MainViewModel by viewModels(factoryProducer = { MainViewModel.Factory })
setContent {
NearbyShareDemoTheme {
// A surface container using the 'background' color from the theme
val state by viewModel.targetsFlow.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
) {
MainView(
state,
onShareTargetClicked = { data, intent ->
viewModel.onShareTargetClicked(data, this@MainActivity, intent)
})
}
}
}
}
}
@Composable
fun MainView(
state: SliceState,
onShareTargetClicked: (ShareTargetData, Intent) -> Unit,
) {
when (state) {
is SliceState.PermissionNeeded -> {
AndroidView(factory = {
val view = SliceView(it)
view.slice = state.slice
return@AndroidView view
})
}
is SliceState.Active -> {
var uris by rememberSaveable { mutableStateOf(listOf<Uri>()) }
val launcher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetMultipleContents()) {
uris = it
}
Column {
Button(onClick = { launcher.launch("*/*") }) { Text("Select files") }
if (uris.isNotEmpty()) {
for (uri in uris) {
Text(
uri.toString(),
style = MaterialTheme.typography.bodySmall
)
}
val sendIntent =
Intent("com.google.android.gms.SHARE_NEARBY").apply {
if (uris.size == 1) {
putExtra(Intent.EXTRA_STREAM, uris[0])
} else {
putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris.toArrayList())
}
type = "*/*"
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
val context = LocalContext.current
// We need to call sendIntent.migrateExtraStreamToClipData() to show the preview images on
// Android Q and above but the method is hidden. Hence we call PendingIntent.getActivity as
// a proxy/wrapper which calls the above method. This method can throw exception if files
// are too large.
// We need to call sendIntent.migrateExtraStreamToClipData() to show the preview images on
// Android Q and above but the method is hidden. Hence we call PendingIntent.getActivity as
// a proxy/wrapper which calls the above method. This method can throw exception if files
// are too large.
val pendingIntentFlags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.getActivity(context.applicationContext, 0, sendIntent, pendingIntentFlags)
ShareDestinations(sendIntent, state.targets, onShareTargetClicked = { data, intent ->
for (uri in uris) {
context.grantUriPermission(
"com.google.android.gms",
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
onShareTargetClicked(data, intent)
})
}
}
}
}
}
@Composable
fun ShareDestinations(
sendIntent: Intent,
targets: Set<ShareTargetData>,
onShareTargetClicked: (ShareTargetData, Intent) -> Unit,
) {
Card(
modifier = Modifier.padding(16.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
) {
if (targets.isEmpty()) {
Text("No devices nearby!")
return@Card
}
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 72.dp)) {
items(targets.toList()) {
ShareTarget(data = it, onShareTargetClicked = { data ->
onShareTargetClicked(data, sendIntent)
})
}
}
}
}
private fun <T> List<T>.toArrayList(): java.util.ArrayList<T> {
val list = java.util.ArrayList<T>()
list.addAll(this)
return list
}
sealed class SliceState {
data class PermissionNeeded(val slice: Slice) : SliceState()
data class Active(val targets: Set<ShareTargetData>) : SliceState()
}
@@ -0,0 +1,123 @@
package com.google.nearby.sharedemo
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.slice.Slice
import androidx.slice.SliceViewManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class MainViewModel(context: Context) : ViewModel() {
private val _targetsFlow: MutableStateFlow<SliceState> = MutableStateFlow(SliceState.Active(setOf()))
val targetsFlow: StateFlow<SliceState> = _targetsFlow.asStateFlow()
private var targetMapping = mapOf<ShareTargetData, PendingIntent>()
private val sliceManager = SliceViewManager.getInstance(context)
private val sliceCallback: (Slice?) -> Unit = {
targetMapping = parseSlice(it)
if (targetMapping.isEmpty() && it != null) {
_targetsFlow.value = SliceState.PermissionNeeded(it)
} else {
_targetsFlow.value = SliceState.Active(targetMapping.keys)
}
}
init {
sliceManager.registerSliceCallback(SCAN_SLICE_URI, sliceCallback)
val slice = sliceManager.bindSlice(SCAN_SLICE_URI)
targetMapping = parseSlice(slice)
if (targetMapping.isEmpty() && slice != null) {
_targetsFlow.value = SliceState.PermissionNeeded(slice)
} else {
_targetsFlow.value = SliceState.Active(targetMapping.keys)
}
}
/**
* This function is run just before the ViewModel is closed, allowing us to unpin the sharing
* slice.
*/
override fun onCleared() {
super.onCleared()
sliceManager.unregisterSliceCallback(SCAN_SLICE_URI, sliceCallback)
}
/**
* Called when a slice's share target is tapped, as represented by [ShareTarget].
*/
fun onShareTargetClicked(data: ShareTargetData, context: Context, sendIntent: Intent) {
targetMapping[data]!!.send(context, 0, sendIntent)
}
private fun parseSlice(slice: Slice?): Map<ShareTargetData, PendingIntent> {
android.util.Log.d("NSDemo", "slice: $slice")
if (slice == null) {
return mapOf()
}
val ret = mutableMapOf<ShareTargetData, PendingIntent>()
for (targetItem in slice.items.reversed()) {
if (!(targetItem.format == SLICE && targetItem.hints.containsAll(listOf(LIST_ITEM, ACTIVITY)))) {
continue
}
val targetSlice = targetItem.slice
var deviceName: String? = null
var action: PendingIntent? = null
var profileIcon: IconCompat? = null
for (item in targetSlice.items) {
if (item.format == TEXT && item.hints.contains(TITLE)) {
deviceName = item.text.toString()
}
if (item.format == ACTION && item.hints.containsAll(listOf(SHORTCUT, TITLE))) {
action = item.action
val iconSlice: Slice? = item.slice
if (iconSlice != null) {
for (iconitem in iconSlice.items) {
if (iconitem.format == IMAGE && iconitem.hints.contains(NO_TINT)) {
profileIcon = iconitem.icon
}
}
}
}
}
// Returns null if the data parsed from the slice is incomplete.
if (deviceName == null || action == null || profileIcon == null) {
continue
}
ret[ShareTargetData(profileIcon, deviceName)] = action
}
return ret
}
companion object {
private val SCAN_SLICE_URI: Uri =
Uri.parse("content://com.google.android.gms.nearby.sharing/scan")
// Slice parsing.
private const val SLICE = "slice"
private const val LIST_ITEM = "list_item"
private const val ACTIVITY = "activity"
private const val TEXT = "text"
private const val TITLE = "title"
private const val ACTION = "action"
private const val SHORTCUT = "shortcut"
private const val IMAGE = "image"
private const val NO_TINT = "no_tint"
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
MainViewModel(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]!!)
}
}
}
}
@@ -0,0 +1,43 @@
package com.google.nearby.sharedemo
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
@Composable
fun ShareTarget(data: ShareTargetData, onShareTargetClicked: (ShareTargetData) -> Unit) {
val context = LocalContext.current
Column(
modifier = Modifier
.padding(8.dp)
.clickable { onShareTargetClicked(data) },
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
data.profileIcon.loadDrawable(context)!!.toBitmap().asImageBitmap(),
contentDescription = null,
tint = Color.Unspecified,
)
Text(
data.deviceName,
style = MaterialTheme.typography.bodySmall,
)
}
}
data class ShareTargetData(
val profileIcon: IconCompat,
val deviceName: String,
)
@@ -0,0 +1,51 @@
package com.google.nearby.sharedemo.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Typography
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.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme()
private val LightColorScheme = lightColorScheme()
@Composable
fun NearbyShareDemoTheme(
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
dynamicLightColorScheme(context)
}
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography(),
content = content
)
}
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Nearby Share Demo</string>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.NearbyShareDemo" parent="android:Theme.Material.Light.NoActionBar" />
</resources>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>
+5
View File
@@ -0,0 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
}
+23
View File
@@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

@@ -0,0 +1,17 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Nearby Share Demo"
include(":app")