diff --git a/.gitignore b/.gitignore index 204301f2..e6bdf9c4 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ BUILD_SUMMARY.md SCRIPTS.md .learnings/ +serviceAccount.json +closer-app-22014-firebase-adminsdk-fbsvc-ed20bf6003.json + # Build artifacts *.apk *.aab @@ -60,3 +63,5 @@ closer_partner_proof_reveal_privacy.md app/google-services.json.bk app/GoogleService-Info.plist docs/SUBSCRIPTION_GO_LIVE.md +ios_encrypt.md +closer-app-22014-firebase-adminsdk-fbsvc-ed20bf6003.json diff --git a/app/src/main/java/app/closer/data/remote/FirebaseStorageDataSource.kt b/app/src/main/java/app/closer/data/remote/FirebaseStorageDataSource.kt index 3b4d4be2..4d548d00 100644 --- a/app/src/main/java/app/closer/data/remote/FirebaseStorageDataSource.kt +++ b/app/src/main/java/app/closer/data/remote/FirebaseStorageDataSource.kt @@ -1,7 +1,10 @@ package app.closer.data.remote +import android.content.Context import android.net.Uri import com.google.firebase.storage.FirebaseStorage +import com.google.firebase.storage.StorageMetadata +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.suspendCancellableCoroutine import javax.inject.Inject import javax.inject.Singleton @@ -9,14 +12,22 @@ import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @Singleton -class FirebaseStorageDataSource @Inject constructor() { +class FirebaseStorageDataSource @Inject constructor( + @ApplicationContext private val context: Context +) { private val storage = FirebaseStorage.getInstance() suspend fun uploadProfilePhoto(uid: String, uri: Uri): String = suspendCancellableCoroutine { cont -> val ref = storage.reference.child("users/$uid/profile.jpg") - ref.putFile(uri) + val mimeType = context.contentResolver.getType(uri) + ?.takeIf { it.startsWith("image/") } + ?: "image/jpeg" + val metadata = StorageMetadata.Builder() + .setContentType(mimeType) + .build() + ref.putFile(uri, metadata) .continueWithTask { ref.downloadUrl } .addOnSuccessListener { cont.resume(it.toString()) } .addOnFailureListener { cont.resumeWithException(it) } diff --git a/app/src/main/java/app/closer/ui/play/PlayHubScreen.kt b/app/src/main/java/app/closer/ui/play/PlayHubScreen.kt index 8d4a9dd4..6bbf4b92 100644 --- a/app/src/main/java/app/closer/ui/play/PlayHubScreen.kt +++ b/app/src/main/java/app/closer/ui/play/PlayHubScreen.kt @@ -1,5 +1,6 @@ package app.closer.ui.play +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -20,7 +21,6 @@ import androidx.compose.material.icons.filled.Done import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Lock -import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Star import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -31,10 +31,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import app.closer.R import app.closer.core.navigation.AppRoute import app.closer.ui.components.CategoryGlyph import app.closer.ui.components.CloserActionButton @@ -45,7 +48,6 @@ import app.closer.ui.components.CloserPill import app.closer.ui.components.CloserRadii import app.closer.ui.theme.CloserPalette import app.closer.ui.theme.closerBackgroundBrush -import app.closer.ui.theme.closerBrandGlyphBrush import app.closer.ui.theme.closerPlayCardBrush @Composable @@ -685,17 +687,17 @@ private fun WheelGlyph( Surface( modifier = modifier, shape = RoundedCornerShape(26.dp), - color = CloserPalette.PurpleDeep + color = MaterialTheme.colorScheme.surface.copy(alpha = 0.62f) ) { Box( contentAlignment = Alignment.Center, - modifier = Modifier.background(closerBrandGlyphBrush()) + modifier = Modifier.padding(5.dp) ) { - Icon( - imageVector = Icons.Filled.PlayArrow, + Image( + painter = painterResource(R.drawable.illustration_spin_wheel), contentDescription = null, - tint = MaterialTheme.colorScheme.surface, - modifier = Modifier.size(34.dp) + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize() ) } } diff --git a/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt b/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt index e539c338..3ca90cf7 100644 --- a/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt +++ b/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Canvas +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -43,15 +44,17 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import app.closer.R import app.closer.ui.theme.CloserPalette import app.closer.ui.theme.closerBackgroundBrush -import app.closer.ui.theme.closerWheelSegmentColors @Composable fun SpinWheelScreen( @@ -231,7 +234,6 @@ private fun WheelSpinner( spunAndReady: Boolean, rotation: Float ) { - val segmentColors = closerWheelSegmentColors() val idleTransition = rememberInfiniteTransition(label = "wheel_idle") val idlePulse by idleTransition.animateFloat( initialValue = 0.98f, @@ -260,24 +262,18 @@ private fun WheelSpinner( .scale(if (isSpinning) 1f else idlePulse), contentAlignment = Alignment.Center ) { - Canvas( + Image( + painter = painterResource(R.drawable.illustration_spin_wheel), + contentDescription = null, + contentScale = ContentScale.Fit, modifier = Modifier .size(236.dp) .rotate(if (isSpinning) rotation else 0f) + ) + + Canvas( + modifier = Modifier.size(236.dp) ) { - val sweep = 360f / segmentColors.size - segmentColors.forEachIndexed { index, color -> - drawArc( - color = color, - startAngle = -90f + (index * sweep), - sweepAngle = sweep, - useCenter = true - ) - } - drawCircle( - color = Color.White.copy(alpha = 0.78f), - radius = size.minDimension * 0.24f - ) drawCircle( color = wheelRingColor, style = Stroke(width = 7.dp.toPx()) diff --git a/app/src/main/res/drawable-nodpi/illustration_spin_wheel.png b/app/src/main/res/drawable-nodpi/illustration_spin_wheel.png new file mode 100644 index 00000000..f66274ea Binary files /dev/null and b/app/src/main/res/drawable-nodpi/illustration_spin_wheel.png differ diff --git a/iphone/Closer/Play/PlayViews.swift b/iphone/Closer/Play/PlayViews.swift index 9b0b5e0f..c99c3da2 100644 --- a/iphone/Closer/Play/PlayViews.swift +++ b/iphone/Closer/Play/PlayViews.swift @@ -90,9 +90,16 @@ struct GameCard: View { RoundedRectangle(cornerRadius: CloserRadius.medium) .fill(color.opacity(0.15)) .frame(width: 60, height: 60) - Image(systemName: icon) - .font(.title2) - .foregroundColor(color) + if gameType == .wheel { + Image("illustration-spin-wheel") + .resizable() + .scaledToFit() + .frame(width: 52, height: 52) + } else { + Image(systemName: icon) + .font(.title2) + .foregroundColor(color) + } } VStack(alignment: .leading, spacing: 4) { @@ -716,4 +723,4 @@ extension String { func replacing(_ target: String, with replacement: String) -> String { replacingOccurrences(of: target, with: replacement) } -} \ No newline at end of file +} diff --git a/iphone/Closer/Resources/illustration-spin-wheel.png b/iphone/Closer/Resources/illustration-spin-wheel.png new file mode 100644 index 00000000..f66274ea Binary files /dev/null and b/iphone/Closer/Resources/illustration-spin-wheel.png differ diff --git a/iphone/Closer/Wheel/WheelViews.swift b/iphone/Closer/Wheel/WheelViews.swift index 58b0c7fd..16990195 100644 --- a/iphone/Closer/Wheel/WheelViews.swift +++ b/iphone/Closer/Wheel/WheelViews.swift @@ -87,29 +87,29 @@ struct SpinWheelView: View { // Wheel ZStack { - ForEach(slices.indices, id: \.self) { index in - WheelSlice( - label: slices[index].label, - color: slices[index].color, - startAngle: Double(index) * (360.0 / Double(slices.count)), - endAngle: Double(index + 1) * (360.0 / Double(slices.count)) - ) - } + Image("illustration-spin-wheel") + .resizable() + .scaledToFit() + .frame(width: 320, height: 320) + .rotationEffect(.degrees(rotation)) // Center circle Circle() .fill(Color.closerBackground) .frame(width: 60, height: 60) .closerShadow(level: .medium) - + + Circle() + .stroke(Color.closerSurface.opacity(0.9), lineWidth: 8) + .frame(width: 320, height: 320) + // Pointer (top) Image(systemName: "arrowtriangle.down.fill") .font(.title) - .foregroundColor(.closerDanger) + .foregroundColor(.closerPrimary) .offset(y: -160) } .frame(width: 320, height: 320) - .rotationEffect(.degrees(rotation)) .animation(isSpinning ? .spring(response: 1.5, dampingFraction: 0.6) : .default, value: rotation) // Result @@ -180,47 +180,6 @@ struct SpinWheelView: View { } } -// MARK: - Wheel Slice - -struct WheelSlice: View { - let label: String - let color: Color - let startAngle: Double - let endAngle: Double - - var body: some View { - GeometryReader { geo in - let center = CGPoint(x: geo.size.width / 2, y: geo.size.height / 2) - let radius = min(geo.size.width, geo.size.height) / 2 - - Path { path in - path.move(to: center) - path.addArc( - center: center, - radius: radius, - startAngle: .degrees(startAngle - 90), - endAngle: .degrees(endAngle - 90), - clockwise: false - ) - path.closeSubpath() - } - .fill(color.opacity(0.3)) - - // Label - let midAngle = (startAngle + endAngle) / 2 - 90 - let labelRadius = radius * 0.7 - let x = center.x + labelRadius * cos(CGFloat(midAngle) * .pi / 180) - let y = center.y + labelRadius * sin(CGFloat(midAngle) * .pi / 180) - - Text(label) - .font(.system(size: 10, weight: .semibold)) - .foregroundColor(.closerText) - .position(x: x, y: y) - .rotationEffect(.degrees(midAngle + 90)) - } - } -} - // MARK: - Wheel Session struct WheelSessionView: View { @@ -375,4 +334,3 @@ struct WheelHistoryView: View { } } } -