Skip to content

Commit 4622815

Browse files
author
Jeff Yanta
committed
Merge branch 'develop'
2 parents 3692fd2 + 5ce3abe commit 4622815

15 files changed

Lines changed: 436 additions & 261 deletions

File tree

api/src/main/java/com/getcode/model/Kin.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.getcode.model
33
import java.math.BigDecimal
44
import java.math.RoundingMode
55
import kotlin.math.floor
6+
import kotlin.math.min
67

78
data class Kin(val quarks: Long): Value {
89
init {
@@ -27,6 +28,7 @@ data class Kin(val quarks: Long): Value {
2728
if (this <= other) return fromKin(0)
2829
return Kin(this.quarks - other.quarks)
2930
}
31+
3032
operator fun div(other: Kin): Kin = Kin(this.quarks / other.quarks)
3133
operator fun div(other: Int): Kin = this / fromQuarks(other.toLong())
3234
operator fun times(other: Kin): Kin = Kin(this.quarks * other.quarks)
@@ -59,4 +61,12 @@ data class Kin(val quarks: Long): Value {
5961
fun fromQuarks(quarks: Long) = Kin(quarks)
6062
fun fromFiat(fiat: Double, fx: Double) = fromKin(fiat / fx)
6163
}
64+
}
65+
66+
fun min(a: Kin, b: Kin): Kin {
67+
if (a.quarks > b.quarks) {
68+
return b
69+
}
70+
71+
return a
6272
}

api/src/main/java/com/getcode/network/repository/PaymentRepository.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ import dagger.hilt.android.qualifiers.ApplicationContext
2323
import io.reactivex.rxjava3.core.Completable
2424
import kotlinx.coroutines.CoroutineScope
2525
import kotlinx.coroutines.Dispatchers
26+
import kotlinx.coroutines.delay
27+
import kotlinx.coroutines.flow.update
2628
import kotlinx.coroutines.launch
2729
import kotlinx.coroutines.suspendCancellableCoroutine
2830
import timber.log.Timber
2931
import javax.inject.Inject
3032
import kotlin.coroutines.resume
3133
import kotlin.coroutines.resumeWithException
34+
import kotlin.time.Duration.Companion.milliseconds
3235

3336

3437
class PaymentRepository @Inject constructor(
@@ -185,6 +188,48 @@ class PaymentRepository @Inject constructor(
185188
suspend fun rejectPayment(payload: CodePayload) {
186189
messagingRepository.rejectPayment(payload.rendezvous)
187190
}
191+
192+
suspend fun completeTipPayment(metadata: TwitterUser, amount: KinAmount) {
193+
return suspendCancellableCoroutine { cont ->
194+
val organizer = SessionManager.getOrganizer() ?: throw PaymentError.OrganizerNotFound()
195+
196+
// Generally, we would use the rendezvous key that
197+
// was generated from the scan code payload, however,
198+
// tip codes are inherently deterministic and won't
199+
// change so we need a unique rendezvous for every tx.
200+
val rendezvous = PublicKey.generate()
201+
202+
runCatching {
203+
val transferResult = client.transferWithResult(
204+
amount = amount,
205+
organizer = organizer,
206+
fee = Kin.fromKin(0),
207+
additionalFees = emptyList(),
208+
rendezvousKey = rendezvous,
209+
destination = metadata.tipAddress,
210+
isWithdrawal = true,
211+
tippedUsername = metadata.username
212+
)
213+
214+
if (transferResult.isSuccess) {
215+
Completable.concatArray(
216+
balanceController.fetchBalance(),
217+
client.fetchLimits(isForce = true)
218+
).doOnComplete {
219+
analytics.transferForTip(amount = amount, successful = true)
220+
cont.resume(Unit)
221+
}.subscribe()
222+
} else {
223+
// pass exception down to onFailure for isolated handling
224+
throw transferResult.exceptionOrNull()
225+
?: Throwable("Unable to complete payment")
226+
}
227+
}.onFailure { error ->
228+
ErrorUtils.handleError(error)
229+
cont.resumeWithException(error)
230+
}
231+
}
232+
}
188233
}
189234

190235
sealed interface PaymentError {

api/src/main/java/com/getcode/network/repository/TransactionRepository.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ class TransactionRepository @Inject constructor(
8787
return limits?.sendLimitFor(currencyCode)
8888
}
8989

90+
fun hasAvailableTransactionLimit(amount: KinAmount): Boolean {
91+
return (sendLimitFor(amount.rate.currency)?.nextTransaction ?: 0.0) >= amount.fiat
92+
}
93+
94+
fun hasAvailableDailyLimit(): Boolean {
95+
return (sendLimitFor(currencyCode = CurrencyCode.USD)?.nextTransaction ?: 0.0) > 0
96+
}
97+
9098
private fun setLimits(limits: Limits) {
9199
this.limits = limits
92100
}

app/src/main/java/com/getcode/ui/components/Scaffold.kt renamed to app/src/main/java/com/getcode/ui/components/CodeScaffold.kt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.ColumnScope
44
import androidx.compose.foundation.layout.PaddingValues
55
import androidx.compose.material.DrawerDefaults
66
import androidx.compose.material.FabPosition
7-
import androidx.compose.material.MaterialTheme
87
import androidx.compose.material.Scaffold
98
import androidx.compose.material.ScaffoldState
109
import androidx.compose.material.SnackbarHost
@@ -18,18 +17,22 @@ import androidx.compose.ui.unit.Dp
1817
import com.getcode.theme.CodeTheme
1918
import com.getcode.theme.extraLarge
2019

21-
2220
@Composable
2321
fun CodeScaffold(
2422
modifier: Modifier = Modifier,
2523
scaffoldState: ScaffoldState = rememberScaffoldState(),
26-
topBar: @Composable (() -> Unit) = {},
27-
bottomBar: @Composable (() -> Unit) = {},
28-
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
29-
floatingActionButton: @Composable (() -> Unit) = {},
24+
topBar: @Composable () -> Unit = {},
25+
bottomBar: @Composable () -> Unit = {},
26+
snackbarHost: @Composable (SnackbarHostState) -> Unit = {
27+
SnackbarHost(it) { data ->
28+
CodeSnackbar(snackbarData = data)
29+
}
30+
},
31+
floatingActionButton: @Composable () -> Unit = {},
3032
floatingActionButtonPosition: FabPosition = FabPosition.End,
3133
isFloatingActionButtonDocked: Boolean = false,
3234
drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
35+
drawerGesturesEnabled: Boolean = true,
3336
drawerShape: Shape = CodeTheme.shapes.extraLarge,
3437
drawerElevation: Dp = DrawerDefaults.Elevation,
3538
drawerBackgroundColor: Color = CodeTheme.colors.background,
@@ -49,6 +52,7 @@ fun CodeScaffold(
4952
floatingActionButtonPosition = floatingActionButtonPosition,
5053
isFloatingActionButtonDocked = isFloatingActionButtonDocked,
5154
drawerContent = drawerContent,
55+
drawerGesturesEnabled = drawerGesturesEnabled,
5256
drawerShape = drawerShape,
5357
drawerElevation = drawerElevation,
5458
drawerBackgroundColor = drawerBackgroundColor,
@@ -58,4 +62,4 @@ fun CodeScaffold(
5862
contentColor = contentColor,
5963
content = content
6064
)
61-
}
65+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.getcode.ui.components
2+
3+
import androidx.compose.material.Snackbar
4+
import androidx.compose.material.SnackbarData
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.Modifier
7+
import androidx.compose.ui.graphics.Color
8+
import androidx.compose.ui.graphics.Shape
9+
import androidx.compose.ui.unit.Dp
10+
import androidx.compose.ui.unit.dp
11+
import com.getcode.theme.BrandMuted
12+
import com.getcode.theme.CodeTheme
13+
import com.getcode.theme.green
14+
15+
@Composable
16+
fun CodeSnackbar(
17+
snackbarData: SnackbarData,
18+
modifier: Modifier = Modifier,
19+
actionOnNewLine: Boolean = false,
20+
shape: Shape = CodeTheme.shapes.small,
21+
backgroundColor: Color = BrandMuted,
22+
contentColor: Color = CodeTheme.colors.onBackground,
23+
actionColor: Color = green,
24+
elevation: Dp = 6.dp
25+
) {
26+
Snackbar(
27+
snackbarData = snackbarData,
28+
modifier = modifier,
29+
actionOnNewLine = actionOnNewLine,
30+
shape = shape,
31+
backgroundColor = backgroundColor,
32+
contentColor = contentColor,
33+
actionColor = actionColor,
34+
elevation = elevation
35+
)
36+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.getcode.ui.components
2+
3+
import androidx.compose.material.SnackbarDuration
4+
import androidx.compose.material.SnackbarHostState
5+
import androidx.compose.material.SnackbarResult
6+
7+
data class SnackData(
8+
val message: String,
9+
val actionLabel: String? = null,
10+
val duration: SnackbarDuration = SnackbarDuration.Short
11+
)
12+
13+
suspend fun SnackbarHostState.showSnackbar(data: SnackData): SnackbarResult {
14+
return showSnackbar(
15+
message = data.message,
16+
actionLabel = data.actionLabel,
17+
duration = data.duration
18+
)
19+
}

0 commit comments

Comments
 (0)