@tryvital/vital-health-react-native 5.4.3 → 6.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +27 -1
- package/android/src/main/java/com/vitalhealthreactnative/HealthConnectVitalHealthProvider.kt +162 -0
- package/android/src/main/java/com/vitalhealthreactnative/UnavailableVitalHealthProviderDefinition.kt +37 -0
- package/android/src/main/java/com/vitalhealthreactnative/VitalHealthEvent.kt +6 -11
- package/android/src/main/java/com/vitalhealthreactnative/VitalHealthProvider.kt +100 -0
- package/android/src/main/java/com/vitalhealthreactnative/VitalHealthReactNativeModule.kt +350 -201
- package/android/src/samsungHealthDisabled/java/com/vitalhealthreactnative/SamsungHealthVitalHealthProvider.kt +3 -0
- package/android/src/samsungHealthEnabled/java/com/vitalhealthreactnative/SamsungHealthVitalHealthProvider.kt +162 -0
- package/app.plugin.js +226 -13
- package/ios/VitalHealthReactNative.m +28 -14
- package/ios/VitalHealthReactNative.swift +192 -100
- package/lib/commonjs/app.plugin.js +226 -13
- package/lib/commonjs/ask_config.js.map +1 -1
- package/lib/commonjs/health_config.js +10 -1
- package/lib/commonjs/health_config.js.map +1 -1
- package/lib/commonjs/healthkit.js.map +1 -1
- package/lib/commonjs/index.js +221 -79
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/ask_config.js.map +1 -1
- package/lib/module/health_config.js +9 -0
- package/lib/module/health_config.js.map +1 -1
- package/lib/module/healthkit.js.map +1 -1
- package/lib/module/index.js +222 -79
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/ask_config.d.ts +3 -3
- package/lib/typescript/ask_config.d.ts.map +1 -1
- package/lib/typescript/health_config.d.ts +8 -0
- package/lib/typescript/health_config.d.ts.map +1 -1
- package/lib/typescript/healthkit.d.ts +1 -1
- package/lib/typescript/healthkit.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +55 -19
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/ask_config.ts +16 -16
- package/src/health_config.ts +14 -0
- package/src/healthkit.ts +205 -234
- package/src/index.tsx +332 -81
- package/vital-health-react-native.podspec +2 -17
package/android/build.gradle
CHANGED
|
@@ -18,6 +18,20 @@ def isNewArchitectureEnabled() {
|
|
|
18
18
|
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
def getSamsungHealthAarPath() {
|
|
22
|
+
if (!rootProject.hasProperty('samsungHealthAarPath')) {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def path = rootProject.getProperty('samsungHealthAarPath')
|
|
27
|
+
return path ? path.toString().trim() : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def hasSamsungHealthAarPath() {
|
|
31
|
+
def path = getSamsungHealthAarPath()
|
|
32
|
+
return path != null && !path.isEmpty()
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
apply plugin: 'com.android.library'
|
|
22
36
|
apply plugin: 'kotlin-android'
|
|
23
37
|
|
|
@@ -56,6 +70,14 @@ android {
|
|
|
56
70
|
targetCompatibility JavaVersion.VERSION_1_8
|
|
57
71
|
}
|
|
58
72
|
|
|
73
|
+
sourceSets {
|
|
74
|
+
main {
|
|
75
|
+
java.srcDirs += hasSamsungHealthAarPath()
|
|
76
|
+
? ['src/samsungHealthEnabled/java']
|
|
77
|
+
: ['src/samsungHealthDisabled/java']
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
}
|
|
60
82
|
|
|
61
83
|
repositories {
|
|
@@ -129,7 +151,7 @@ repositories {
|
|
|
129
151
|
}
|
|
130
152
|
|
|
131
153
|
def kotlin_version = getExtOrDefault('kotlinVersion')
|
|
132
|
-
def vital_sdk_version = '
|
|
154
|
+
def vital_sdk_version = '5.0.0-rc.1'
|
|
133
155
|
|
|
134
156
|
dependencies {
|
|
135
157
|
//noinspection GradleDynamicVersion
|
|
@@ -140,6 +162,10 @@ dependencies {
|
|
|
140
162
|
implementation "io.tryvital:vital-client:$vital_sdk_version"
|
|
141
163
|
implementation "io.tryvital:vital-health-connect:$vital_sdk_version"
|
|
142
164
|
|
|
165
|
+
if (hasSamsungHealthAarPath()) {
|
|
166
|
+
implementation "io.tryvital:vital-samsung-health:$vital_sdk_version"
|
|
167
|
+
}
|
|
168
|
+
|
|
143
169
|
implementation project(':tryvital_vital-core-react-native')
|
|
144
170
|
// From node_modules
|
|
145
171
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
package com.vitalhealthreactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.activity.result.contract.ActivityResultContract
|
|
5
|
+
import io.tryvital.vitalhealthconnect.DefaultSyncNotificationBuilder
|
|
6
|
+
import io.tryvital.vitalhealthconnect.DefaultSyncNotificationContent
|
|
7
|
+
import io.tryvital.vitalhealthconnect.ExperimentalVitalApi
|
|
8
|
+
import io.tryvital.vitalhealthconnect.VitalHealthConnectManager
|
|
9
|
+
import io.tryvital.vitalhealthconnect.autoSyncThrottle
|
|
10
|
+
import io.tryvital.vitalhealthconnect.backgroundSyncMinimumInterval
|
|
11
|
+
import io.tryvital.vitalhealthconnect.disableBackgroundSync
|
|
12
|
+
import io.tryvital.vitalhealthconnect.enableBackgroundSyncContract
|
|
13
|
+
import io.tryvital.vitalhealthconnect.isBackgroundSyncEnabled
|
|
14
|
+
import io.tryvital.vitalhealthconnect.model.PermissionOutcome as HealthConnectPermissionOutcome
|
|
15
|
+
import io.tryvital.vitalhealthcore.model.ConnectionPolicy
|
|
16
|
+
import io.tryvital.vitalhealthcore.model.ProviderAvailability
|
|
17
|
+
import io.tryvital.vitalhealthcore.model.VitalResource
|
|
18
|
+
import io.tryvital.vitalhealthcore.model.WritableVitalResource
|
|
19
|
+
import kotlinx.coroutines.Deferred
|
|
20
|
+
import java.time.Instant
|
|
21
|
+
|
|
22
|
+
internal object HealthConnectVitalHealthProviderDefinition : VitalHealthProviderDefinition {
|
|
23
|
+
override val provider = AndroidProvider.HealthConnect
|
|
24
|
+
override val displayName = "Health Connect"
|
|
25
|
+
override val supportedWriteResources = setOf(
|
|
26
|
+
WritableVitalResource.Water,
|
|
27
|
+
WritableVitalResource.Glucose,
|
|
28
|
+
)
|
|
29
|
+
override val syncStatusEvent = VitalHealthEvent.HealthConnectSyncStatus
|
|
30
|
+
override val connectionStatusEvent = VitalHealthEvent.HealthConnectConnectionStatus
|
|
31
|
+
|
|
32
|
+
override fun isAvailable(context: Context): ProviderAvailability {
|
|
33
|
+
return VitalHealthConnectManager.isAvailable(context)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun openPlatformHealthAppIntent(context: Context) =
|
|
37
|
+
VitalHealthConnectManager.openHealthConnectIntent(context)
|
|
38
|
+
|
|
39
|
+
override fun getOrCreateManager(context: Context): VitalHealthManagerBridge {
|
|
40
|
+
return HealthConnectVitalHealthManagerBridge(
|
|
41
|
+
VitalHealthConnectManager.getOrCreate(context),
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@OptIn(ExperimentalVitalApi::class)
|
|
47
|
+
private class HealthConnectVitalHealthManagerBridge(
|
|
48
|
+
private val manager: VitalHealthConnectManager,
|
|
49
|
+
) : VitalHealthManagerBridge {
|
|
50
|
+
override val status = manager.status
|
|
51
|
+
override val connectionStatus = manager.connectionStatus
|
|
52
|
+
|
|
53
|
+
override var pauseSynchronization: Boolean
|
|
54
|
+
get() = manager.pauseSynchronization
|
|
55
|
+
set(value) {
|
|
56
|
+
manager.pauseSynchronization = value
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override val isBackgroundSyncEnabled: Boolean
|
|
60
|
+
get() = manager.isBackgroundSyncEnabled
|
|
61
|
+
|
|
62
|
+
override var autoSyncThrottle
|
|
63
|
+
get() = manager.autoSyncThrottle
|
|
64
|
+
set(value) {
|
|
65
|
+
manager.autoSyncThrottle = value
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override var backgroundSyncMinimumInterval
|
|
69
|
+
get() = manager.backgroundSyncMinimumInterval
|
|
70
|
+
set(value) {
|
|
71
|
+
manager.backgroundSyncMinimumInterval = value
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override fun configure(
|
|
75
|
+
syncOnAppStart: Boolean,
|
|
76
|
+
numberOfDaysToBackFill: Int,
|
|
77
|
+
logsEnabled: Boolean,
|
|
78
|
+
connectionPolicy: ConnectionPolicy,
|
|
79
|
+
) {
|
|
80
|
+
manager.configureHealthConnectClient(
|
|
81
|
+
logsEnabled = logsEnabled,
|
|
82
|
+
syncOnAppStart = syncOnAppStart,
|
|
83
|
+
numberOfDaysToBackFill = numberOfDaysToBackFill,
|
|
84
|
+
connectionPolicy = connectionPolicy,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Suppress("UNCHECKED_CAST")
|
|
89
|
+
override fun createPermissionRequestContract(
|
|
90
|
+
readResources: Set<VitalResource>,
|
|
91
|
+
writeResources: Set<WritableVitalResource>,
|
|
92
|
+
): ActivityResultContract<Unit, Deferred<*>> {
|
|
93
|
+
return manager.createPermissionRequestContract(
|
|
94
|
+
readResources = readResources,
|
|
95
|
+
writeResources = writeResources,
|
|
96
|
+
) as ActivityResultContract<Unit, Deferred<*>>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Suppress("UNCHECKED_CAST")
|
|
100
|
+
override suspend fun resolvePermissionOutcome(result: Deferred<*>): VitalHealthPermissionOutcome {
|
|
101
|
+
return when ((result as Deferred<HealthConnectPermissionOutcome>).await()) {
|
|
102
|
+
is HealthConnectPermissionOutcome.Success -> VitalHealthPermissionOutcome.Success
|
|
103
|
+
is HealthConnectPermissionOutcome.HealthConnectUnavailable ->
|
|
104
|
+
VitalHealthPermissionOutcome.HealthDataUnavailable
|
|
105
|
+
is HealthConnectPermissionOutcome.Cancelled -> VitalHealthPermissionOutcome.Cancelled
|
|
106
|
+
is HealthConnectPermissionOutcome.NotPrompted -> VitalHealthPermissionOutcome.NotPrompted
|
|
107
|
+
is HealthConnectPermissionOutcome.UnknownError -> VitalHealthPermissionOutcome.UnknownError
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
override fun hasAskedForPermission(resource: VitalResource): Boolean {
|
|
112
|
+
return manager.hasAskedForPermission(resource)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override suspend fun syncData(resources: Set<VitalResource>?) {
|
|
116
|
+
manager.syncData(resources)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
override suspend fun writeRecord(
|
|
120
|
+
resource: WritableVitalResource,
|
|
121
|
+
startDate: Instant,
|
|
122
|
+
endDate: Instant,
|
|
123
|
+
value: Double,
|
|
124
|
+
) {
|
|
125
|
+
manager.writeRecord(resource, startDate, endDate, value)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override fun enableBackgroundSyncContract(): ActivityResultContract<Unit, Boolean> {
|
|
129
|
+
return manager.enableBackgroundSyncContract()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override fun disableBackgroundSync() {
|
|
133
|
+
manager.disableBackgroundSync()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
override fun setSyncNotificationContent(
|
|
137
|
+
context: Context,
|
|
138
|
+
content: VitalHealthSyncNotificationContent,
|
|
139
|
+
): Boolean {
|
|
140
|
+
val builder = VitalHealthConnectManager.syncNotificationBuilder(context)
|
|
141
|
+
as? DefaultSyncNotificationBuilder
|
|
142
|
+
?: return false
|
|
143
|
+
|
|
144
|
+
builder.setContentOverride(
|
|
145
|
+
DefaultSyncNotificationContent(
|
|
146
|
+
notificationTitle = content.notificationTitle,
|
|
147
|
+
notificationContent = content.notificationContent,
|
|
148
|
+
channelName = content.channelName,
|
|
149
|
+
channelDescription = content.channelDescription,
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
return true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
override suspend fun connect() {
|
|
156
|
+
manager.connect()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
override suspend fun disconnect() {
|
|
160
|
+
manager.disconnect()
|
|
161
|
+
}
|
|
162
|
+
}
|
package/android/src/main/java/com/vitalhealthreactnative/UnavailableVitalHealthProviderDefinition.kt
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.vitalhealthreactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import io.tryvital.vitalhealthcore.model.ProviderAvailability
|
|
6
|
+
import io.tryvital.vitalhealthcore.model.WritableVitalResource
|
|
7
|
+
|
|
8
|
+
fun raiseUnimplemented(): Nothing {
|
|
9
|
+
throw IllegalArgumentException("the requested provider is unavailable on this device")
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@Suppress("unused")
|
|
13
|
+
internal object UnavailableVitalHealthProviderDefinition : VitalHealthProviderDefinition {
|
|
14
|
+
|
|
15
|
+
override val provider: AndroidProvider
|
|
16
|
+
get() { raiseUnimplemented() }
|
|
17
|
+
override val displayName: String
|
|
18
|
+
get() { raiseUnimplemented() }
|
|
19
|
+
override val supportedWriteResources: Set<WritableVitalResource>
|
|
20
|
+
get() { raiseUnimplemented() }
|
|
21
|
+
override val syncStatusEvent: VitalHealthEvent
|
|
22
|
+
get() { raiseUnimplemented() }
|
|
23
|
+
override val connectionStatusEvent: VitalHealthEvent
|
|
24
|
+
get() { raiseUnimplemented() }
|
|
25
|
+
|
|
26
|
+
override fun isAvailable(context: Context): ProviderAvailability {
|
|
27
|
+
return ProviderAvailability.NotInstalled
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override fun openPlatformHealthAppIntent(context: Context): Intent? {
|
|
31
|
+
raiseUnimplemented()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun getOrCreateManager(context: Context): VitalHealthManagerBridge {
|
|
35
|
+
raiseUnimplemented()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
package com.vitalhealthreactnative
|
|
2
2
|
|
|
3
3
|
sealed class VitalHealthEvent(val value: String) {
|
|
4
|
-
object
|
|
5
|
-
object
|
|
4
|
+
object HealthConnectSyncStatus : VitalHealthEvent("Status")
|
|
5
|
+
object HealthConnectConnectionStatus : VitalHealthEvent("HealthConnectConnectionStatus")
|
|
6
|
+
object SamsungHealthSyncStatus : VitalHealthEvent("SamsungHealthSyncStatus")
|
|
7
|
+
object SamsungHealthConnectionStatus : VitalHealthEvent("SamsungHealthConnectionStatus")
|
|
6
8
|
|
|
7
9
|
companion object {
|
|
8
10
|
fun values(): Array<VitalHealthEvent> {
|
|
9
|
-
return arrayOf(
|
|
11
|
+
return arrayOf(HealthConnectSyncStatus, HealthConnectConnectionStatus, SamsungHealthSyncStatus, SamsungHealthConnectionStatus)
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
fun valueOf(value: String): VitalHealthEvent {
|
|
13
|
-
return when (value) {
|
|
14
|
-
"Status" -> Status
|
|
15
|
-
"VitalHealthConnectionStatus" -> ConnectionStatus
|
|
16
|
-
else -> throw IllegalArgumentException("Invalid VitalHealthEvent value: $value")
|
|
17
|
-
}
|
|
18
|
-
}
|
|
14
|
+
fun valueOf(value: String): VitalHealthEvent = values().first { it.value == value }
|
|
19
15
|
}
|
|
20
|
-
|
|
21
16
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
package com.vitalhealthreactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import androidx.activity.result.contract.ActivityResultContract
|
|
6
|
+
import io.tryvital.vitalhealthcore.model.ConnectionPolicy
|
|
7
|
+
import io.tryvital.vitalhealthcore.model.ConnectionStatus
|
|
8
|
+
import io.tryvital.vitalhealthcore.model.ProviderAvailability
|
|
9
|
+
import io.tryvital.vitalhealthcore.model.SyncStatus
|
|
10
|
+
import io.tryvital.vitalhealthcore.model.VitalResource
|
|
11
|
+
import io.tryvital.vitalhealthcore.model.WritableVitalResource
|
|
12
|
+
import kotlinx.coroutines.Deferred
|
|
13
|
+
import kotlinx.coroutines.flow.Flow
|
|
14
|
+
import kotlinx.coroutines.flow.StateFlow
|
|
15
|
+
import java.time.Instant
|
|
16
|
+
import kotlin.time.Duration
|
|
17
|
+
|
|
18
|
+
internal enum class AndroidProvider {
|
|
19
|
+
HealthConnect, SamsungHealth;
|
|
20
|
+
|
|
21
|
+
companion object {
|
|
22
|
+
fun of(rawValue: String) = when (rawValue) {
|
|
23
|
+
"health_connect" -> HealthConnect
|
|
24
|
+
"samsung_health" -> SamsungHealth
|
|
25
|
+
else -> throw IllegalArgumentException("unrecognized AndroidProvider: ${rawValue}")
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
internal interface VitalHealthProviderDefinition {
|
|
31
|
+
val provider: AndroidProvider
|
|
32
|
+
val displayName: String
|
|
33
|
+
val supportedWriteResources: Set<WritableVitalResource>
|
|
34
|
+
val syncStatusEvent: VitalHealthEvent
|
|
35
|
+
val connectionStatusEvent: VitalHealthEvent
|
|
36
|
+
|
|
37
|
+
fun isAvailable(context: Context): ProviderAvailability
|
|
38
|
+
fun openPlatformHealthAppIntent(context: Context): Intent?
|
|
39
|
+
fun getOrCreateManager(context: Context): VitalHealthManagerBridge
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
internal interface VitalHealthManagerBridge {
|
|
43
|
+
val status: Flow<SyncStatus>
|
|
44
|
+
val connectionStatus: StateFlow<ConnectionStatus>
|
|
45
|
+
var pauseSynchronization: Boolean
|
|
46
|
+
val isBackgroundSyncEnabled: Boolean
|
|
47
|
+
var autoSyncThrottle: Duration
|
|
48
|
+
var backgroundSyncMinimumInterval: Duration
|
|
49
|
+
|
|
50
|
+
fun configure(
|
|
51
|
+
syncOnAppStart: Boolean,
|
|
52
|
+
numberOfDaysToBackFill: Int,
|
|
53
|
+
logsEnabled: Boolean,
|
|
54
|
+
connectionPolicy: ConnectionPolicy,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
fun createPermissionRequestContract(
|
|
58
|
+
readResources: Set<VitalResource>,
|
|
59
|
+
writeResources: Set<WritableVitalResource>,
|
|
60
|
+
): ActivityResultContract<Unit, Deferred<*>>
|
|
61
|
+
|
|
62
|
+
suspend fun resolvePermissionOutcome(result: Deferred<*>): VitalHealthPermissionOutcome
|
|
63
|
+
|
|
64
|
+
fun hasAskedForPermission(resource: VitalResource): Boolean
|
|
65
|
+
|
|
66
|
+
suspend fun syncData(resources: Set<VitalResource>? = null)
|
|
67
|
+
|
|
68
|
+
suspend fun writeRecord(
|
|
69
|
+
resource: WritableVitalResource,
|
|
70
|
+
startDate: Instant,
|
|
71
|
+
endDate: Instant,
|
|
72
|
+
value: Double,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
fun enableBackgroundSyncContract(): ActivityResultContract<Unit, Boolean>
|
|
76
|
+
fun disableBackgroundSync()
|
|
77
|
+
|
|
78
|
+
fun setSyncNotificationContent(
|
|
79
|
+
context: Context,
|
|
80
|
+
content: VitalHealthSyncNotificationContent,
|
|
81
|
+
): Boolean
|
|
82
|
+
|
|
83
|
+
suspend fun connect()
|
|
84
|
+
suspend fun disconnect()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
internal data class VitalHealthSyncNotificationContent(
|
|
88
|
+
val notificationTitle: String,
|
|
89
|
+
val notificationContent: String,
|
|
90
|
+
val channelName: String,
|
|
91
|
+
val channelDescription: String,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
internal enum class VitalHealthPermissionOutcome(val jsValue: String) {
|
|
95
|
+
Success("success"),
|
|
96
|
+
HealthDataUnavailable("healthDataUnavailable"),
|
|
97
|
+
Cancelled("cancelled"),
|
|
98
|
+
NotPrompted("notPrompted"),
|
|
99
|
+
UnknownError("unknownError"),
|
|
100
|
+
}
|