Fix build failures from rebrand: restore pbxproj exceptions, fix Kotlin casing, move missed source dirs
- Restore 6 missing PBXFileSystemSynchronizedBuildFileExceptionSet entries and exceptions arrays on 5 root groups (lost during sed rename) - Rename extension WidgetIconView.swift to avoid stringsdata collision (original had different names: MyCribIconView vs CaseraIconView) - Rename CaseraExtension.entitlements → HoneyDueExtension.entitlements - Fix Kotlin object casing: honeyDueShareCodec → HoneyDueShareCodec, honeyDuePackageType → HoneyDuePackageType - Move missed Kotlin source dirs (jsMain, webMain, androidMain/com/casera) to com/tt/honeyDue - Rename remaining Casera widget files to HoneyDue - Rename CaseraTests.swift → HoneyDueTests.swift All 4 projects (Go API, iOS, Android, Web) now compile clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,8 +35,8 @@ import com.tt.honeyDue.network.APILayer
|
|||||||
import com.tt.honeyDue.sharing.ContractorSharingManager
|
import com.tt.honeyDue.sharing.ContractorSharingManager
|
||||||
import com.tt.honeyDue.data.DataManager
|
import com.tt.honeyDue.data.DataManager
|
||||||
import com.tt.honeyDue.data.PersistenceManager
|
import com.tt.honeyDue.data.PersistenceManager
|
||||||
import com.tt.honeyDue.models.honeyDuePackageType
|
import com.tt.honeyDue.models.HoneyDuePackageType
|
||||||
import com.tt.honeyDue.models.detecthoneyDuePackageType
|
import com.tt.honeyDue.models.detectHoneyDuePackageType
|
||||||
import com.tt.honeyDue.analytics.PostHogAnalytics
|
import com.tt.honeyDue.analytics.PostHogAnalytics
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -273,11 +273,11 @@ class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
|
|||||||
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
||||||
inputStream.close()
|
inputStream.close()
|
||||||
|
|
||||||
val packageType = detecthoneyDuePackageType(jsonString)
|
val packageType = detectHoneyDuePackageType(jsonString)
|
||||||
Log.d("MainActivity", "Detected package type: $packageType")
|
Log.d("MainActivity", "Detected package type: $packageType")
|
||||||
|
|
||||||
when (packageType) {
|
when (packageType) {
|
||||||
honeyDuePackageType.RESIDENCE -> {
|
HoneyDuePackageType.RESIDENCE -> {
|
||||||
Log.d("MainActivity", "Routing to residence import")
|
Log.d("MainActivity", "Routing to residence import")
|
||||||
pendingResidenceImportUri = uri
|
pendingResidenceImportUri = uri
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.tt.honeyDue.data.DataManager
|
import com.tt.honeyDue.data.DataManager
|
||||||
import com.tt.honeyDue.models.honeyDueShareCodec
|
import com.tt.honeyDue.models.HoneyDueShareCodec
|
||||||
import com.tt.honeyDue.models.Contractor
|
import com.tt.honeyDue.models.Contractor
|
||||||
import com.tt.honeyDue.network.APILayer
|
import com.tt.honeyDue.network.APILayer
|
||||||
import com.tt.honeyDue.network.ApiResult
|
import com.tt.honeyDue.network.ApiResult
|
||||||
@@ -29,8 +29,8 @@ object ContractorSharingManager {
|
|||||||
fun createShareIntent(context: Context, contractor: Contractor): Intent? {
|
fun createShareIntent(context: Context, contractor: Contractor): Intent? {
|
||||||
return try {
|
return try {
|
||||||
val currentUsername = DataManager.currentUser.value?.username ?: "Unknown"
|
val currentUsername = DataManager.currentUser.value?.username ?: "Unknown"
|
||||||
val jsonString = honeyDueShareCodec.encodeContractorPackage(contractor, currentUsername)
|
val jsonString = HoneyDueShareCodec.encodeContractorPackage(contractor, currentUsername)
|
||||||
val fileName = honeyDueShareCodec.safeShareFileName(contractor.name)
|
val fileName = HoneyDueShareCodec.safeShareFileName(contractor.name)
|
||||||
|
|
||||||
// Create shared directory
|
// Create shared directory
|
||||||
val shareDir = File(context.cacheDir, "shared")
|
val shareDir = File(context.cacheDir, "shared")
|
||||||
@@ -79,7 +79,7 @@ object ContractorSharingManager {
|
|||||||
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
||||||
inputStream.close()
|
inputStream.close()
|
||||||
|
|
||||||
val createRequest = honeyDueShareCodec.createContractorImportRequestOrNull(
|
val createRequest = HoneyDueShareCodec.createContractorImportRequestOrNull(
|
||||||
jsonContent = jsonString,
|
jsonContent = jsonString,
|
||||||
availableSpecialties = DataManager.contractorSpecialties.value
|
availableSpecialties = DataManager.contractorSpecialties.value
|
||||||
) ?: return@withContext ApiResult.Error("Invalid contractor share package")
|
) ?: return@withContext ApiResult.Error("Invalid contractor share package")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.tt.honeyDue.data.DataManager
|
import com.tt.honeyDue.data.DataManager
|
||||||
import com.tt.honeyDue.models.honeyDueShareCodec
|
import com.tt.honeyDue.models.HoneyDueShareCodec
|
||||||
import com.tt.honeyDue.models.JoinResidenceResponse
|
import com.tt.honeyDue.models.JoinResidenceResponse
|
||||||
import com.tt.honeyDue.models.Residence
|
import com.tt.honeyDue.models.Residence
|
||||||
import com.tt.honeyDue.network.APILayer
|
import com.tt.honeyDue.network.APILayer
|
||||||
@@ -38,8 +38,8 @@ object ResidenceSharingManager {
|
|||||||
when (result) {
|
when (result) {
|
||||||
is ApiResult.Success -> {
|
is ApiResult.Success -> {
|
||||||
val sharedResidence = result.data
|
val sharedResidence = result.data
|
||||||
val jsonString = honeyDueShareCodec.encodeSharedResidence(sharedResidence)
|
val jsonString = HoneyDueShareCodec.encodeSharedResidence(sharedResidence)
|
||||||
val fileName = honeyDueShareCodec.safeShareFileName(residence.name)
|
val fileName = HoneyDueShareCodec.safeShareFileName(residence.name)
|
||||||
|
|
||||||
// Create shared directory
|
// Create shared directory
|
||||||
val shareDir = File(context.cacheDir, "shared")
|
val shareDir = File(context.cacheDir, "shared")
|
||||||
@@ -95,7 +95,7 @@ object ResidenceSharingManager {
|
|||||||
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
||||||
inputStream.close()
|
inputStream.close()
|
||||||
|
|
||||||
val shareCode = honeyDueShareCodec.extractResidenceShareCodeOrNull(jsonString)
|
val shareCode = HoneyDueShareCodec.extractResidenceShareCodeOrNull(jsonString)
|
||||||
?: return@withContext ApiResult.Error("Invalid residence share package")
|
?: return@withContext ApiResult.Error("Invalid residence share package")
|
||||||
|
|
||||||
// Call API with share code
|
// Call API with share code
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import kotlinx.serialization.json.Json
|
|||||||
* This keeps package JSON shape in one place while each platform owns
|
* This keeps package JSON shape in one place while each platform owns
|
||||||
* native share-sheet presentation details.
|
* native share-sheet presentation details.
|
||||||
*/
|
*/
|
||||||
object honeyDueShareCodec {
|
object HoneyDueShareCodec {
|
||||||
private val json = Json {
|
private val json = Json {
|
||||||
prettyPrint = true
|
prettyPrint = true
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
@@ -11,7 +11,7 @@ import kotlinx.serialization.json.jsonPrimitive
|
|||||||
/**
|
/**
|
||||||
* Package type identifiers for .honeydue files
|
* Package type identifiers for .honeydue files
|
||||||
*/
|
*/
|
||||||
object honeyDuePackageType {
|
object HoneyDuePackageType {
|
||||||
const val CONTRACTOR = "contractor"
|
const val CONTRACTOR = "contractor"
|
||||||
const val RESIDENCE = "residence"
|
const val RESIDENCE = "residence"
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ data class SharedContractor(
|
|||||||
val version: Int = 1,
|
val version: Int = 1,
|
||||||
|
|
||||||
/** Package type discriminator */
|
/** Package type discriminator */
|
||||||
val type: String = honeyDuePackageType.CONTRACTOR,
|
val type: String = HoneyDuePackageType.CONTRACTOR,
|
||||||
|
|
||||||
val name: String,
|
val name: String,
|
||||||
val company: String? = null,
|
val company: String? = null,
|
||||||
@@ -70,7 +70,7 @@ data class SharedResidence(
|
|||||||
val version: Int = 1,
|
val version: Int = 1,
|
||||||
|
|
||||||
/** Package type discriminator */
|
/** Package type discriminator */
|
||||||
val type: String = honeyDuePackageType.RESIDENCE,
|
val type: String = HoneyDuePackageType.RESIDENCE,
|
||||||
|
|
||||||
/** The share code for joining the residence */
|
/** The share code for joining the residence */
|
||||||
@SerialName("share_code")
|
@SerialName("share_code")
|
||||||
@@ -101,11 +101,11 @@ data class SharedResidence(
|
|||||||
* Detect the type of a .honeydue package from its JSON content.
|
* Detect the type of a .honeydue package from its JSON content.
|
||||||
* Returns null if the type cannot be determined.
|
* Returns null if the type cannot be determined.
|
||||||
*/
|
*/
|
||||||
fun detecthoneyDuePackageType(jsonContent: String): String? {
|
fun detectHoneyDuePackageType(jsonContent: String): String? {
|
||||||
return try {
|
return try {
|
||||||
val json = Json { ignoreUnknownKeys = true }
|
val json = Json { ignoreUnknownKeys = true }
|
||||||
val jsonObject = json.decodeFromString<JsonObject>(jsonContent)
|
val jsonObject = json.decodeFromString<JsonObject>(jsonContent)
|
||||||
jsonObject["type"]?.jsonPrimitive?.content ?: honeyDuePackageType.CONTRACTOR // Default for backward compatibility
|
jsonObject["type"]?.jsonPrimitive?.content ?: HoneyDuePackageType.CONTRACTOR // Default for backward compatibility
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ fun detecthoneyDuePackageType(jsonContent: String): String? {
|
|||||||
fun Contractor.toSharedContractor(exportedBy: String? = null): SharedContractor {
|
fun Contractor.toSharedContractor(exportedBy: String? = null): SharedContractor {
|
||||||
return SharedContractor(
|
return SharedContractor(
|
||||||
version = 1,
|
version = 1,
|
||||||
type = honeyDuePackageType.CONTRACTOR,
|
type = HoneyDuePackageType.CONTRACTOR,
|
||||||
name = name,
|
name = name,
|
||||||
company = company,
|
company = company,
|
||||||
phone = phone,
|
phone = phone,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.tt.honeyDue.platform
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.interop.LocalUIViewController
|
import androidx.compose.ui.interop.LocalUIViewController
|
||||||
import com.tt.honeyDue.data.DataManager
|
import com.tt.honeyDue.data.DataManager
|
||||||
import com.tt.honeyDue.models.honeyDueShareCodec
|
import com.tt.honeyDue.models.HoneyDueShareCodec
|
||||||
import com.tt.honeyDue.models.Contractor
|
import com.tt.honeyDue.models.Contractor
|
||||||
import kotlinx.cinterop.ExperimentalForeignApi
|
import kotlinx.cinterop.ExperimentalForeignApi
|
||||||
import kotlinx.cinterop.addressOf
|
import kotlinx.cinterop.addressOf
|
||||||
@@ -18,7 +18,7 @@ actual fun rememberShareContractor(): (Contractor) -> Unit {
|
|||||||
|
|
||||||
return share@{ contractor: Contractor ->
|
return share@{ contractor: Contractor ->
|
||||||
val currentUsername = DataManager.currentUser.value?.username ?: "Unknown"
|
val currentUsername = DataManager.currentUser.value?.username ?: "Unknown"
|
||||||
val jsonContent = honeyDueShareCodec.encodeContractorPackage(contractor, currentUsername)
|
val jsonContent = HoneyDueShareCodec.encodeContractorPackage(contractor, currentUsername)
|
||||||
val fileUrl = writeShareFile(jsonContent, contractor.name) ?: return@share
|
val fileUrl = writeShareFile(jsonContent, contractor.name) ?: return@share
|
||||||
presentShareSheet(viewController, fileUrl)
|
presentShareSheet(viewController, fileUrl)
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ actual fun rememberShareContractor(): (Contractor) -> Unit {
|
|||||||
|
|
||||||
@OptIn(ExperimentalForeignApi::class)
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
private fun writeShareFile(jsonContent: String, displayName: String): NSURL? {
|
private fun writeShareFile(jsonContent: String, displayName: String): NSURL? {
|
||||||
val fileName = honeyDueShareCodec.safeShareFileName(displayName)
|
val fileName = HoneyDueShareCodec.safeShareFileName(displayName)
|
||||||
val filePath = NSTemporaryDirectory().plus(fileName)
|
val filePath = NSTemporaryDirectory().plus(fileName)
|
||||||
|
|
||||||
val bytes = jsonContent.encodeToByteArray()
|
val bytes = jsonContent.encodeToByteArray()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.interop.LocalUIViewController
|
import androidx.compose.ui.interop.LocalUIViewController
|
||||||
import com.tt.honeyDue.models.honeyDueShareCodec
|
import com.tt.honeyDue.models.HoneyDueShareCodec
|
||||||
import com.tt.honeyDue.models.Residence
|
import com.tt.honeyDue.models.Residence
|
||||||
import com.tt.honeyDue.network.APILayer
|
import com.tt.honeyDue.network.APILayer
|
||||||
import com.tt.honeyDue.network.ApiResult
|
import com.tt.honeyDue.network.ApiResult
|
||||||
@@ -31,7 +31,7 @@ actual fun rememberShareResidence(): Pair<ResidenceSharingState, (Residence) ->
|
|||||||
|
|
||||||
when (val result = APILayer.generateSharePackage(residence.id)) {
|
when (val result = APILayer.generateSharePackage(residence.id)) {
|
||||||
is ApiResult.Success -> {
|
is ApiResult.Success -> {
|
||||||
val jsonContent = honeyDueShareCodec.encodeSharedResidence(result.data)
|
val jsonContent = HoneyDueShareCodec.encodeSharedResidence(result.data)
|
||||||
val fileUrl = writeShareFile(jsonContent, residence.name)
|
val fileUrl = writeShareFile(jsonContent, residence.name)
|
||||||
if (fileUrl == null) {
|
if (fileUrl == null) {
|
||||||
state = ResidenceSharingState(isLoading = false, error = "Failed to create share package")
|
state = ResidenceSharingState(isLoading = false, error = "Failed to create share package")
|
||||||
@@ -59,7 +59,7 @@ actual fun rememberShareResidence(): Pair<ResidenceSharingState, (Residence) ->
|
|||||||
|
|
||||||
@OptIn(ExperimentalForeignApi::class)
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
private fun writeShareFile(jsonContent: String, displayName: String): NSURL? {
|
private fun writeShareFile(jsonContent: String, displayName: String): NSURL? {
|
||||||
val fileName = honeyDueShareCodec.safeShareFileName(displayName)
|
val fileName = HoneyDueShareCodec.safeShareFileName(displayName)
|
||||||
val filePath = NSTemporaryDirectory().plus(fileName)
|
val filePath = NSTemporaryDirectory().plus(fileName)
|
||||||
|
|
||||||
val bytes = jsonContent.encodeToByteArray()
|
val bytes = jsonContent.encodeToByteArray()
|
||||||
|
|||||||
@@ -1,316 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
// MARK: - Centered Icon View
|
|
||||||
|
|
||||||
struct HoneyDueIconView: View {
|
|
||||||
var houseProgress: CGFloat = 1.0
|
|
||||||
var windowScale: CGFloat = 1.0
|
|
||||||
var checkmarkScale: CGFloat = 1.0
|
|
||||||
var foregroundColor: Color = Color(red: 1.0, green: 0.96, blue: 0.92)
|
|
||||||
var backgroundColor: Color? = nil // nil uses default gradient, otherwise uses theme color
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
GeometryReader { geo in
|
|
||||||
let size = min(geo.size.width, geo.size.height)
|
|
||||||
let center = CGPoint(x: geo.size.width / 2, y: geo.size.height / 2)
|
|
||||||
|
|
||||||
ZStack {
|
|
||||||
// Background - use provided color or default gradient
|
|
||||||
if let bgColor = backgroundColor {
|
|
||||||
RoundedRectangle(cornerRadius: size * 0.195)
|
|
||||||
.fill(
|
|
||||||
LinearGradient(
|
|
||||||
colors: [bgColor, bgColor.opacity(0.85)],
|
|
||||||
startPoint: .top,
|
|
||||||
endPoint: .bottom
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.frame(width: size * 0.906, height: size * 0.906)
|
|
||||||
.position(center)
|
|
||||||
} else {
|
|
||||||
RoundedRectangle(cornerRadius: size * 0.195)
|
|
||||||
.fill(
|
|
||||||
LinearGradient(
|
|
||||||
colors: [
|
|
||||||
Color(red: 1.0, green: 0.64, blue: 0.28),
|
|
||||||
Color(red: 0.96, green: 0.51, blue: 0.20)
|
|
||||||
],
|
|
||||||
startPoint: .top,
|
|
||||||
endPoint: .bottom
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.frame(width: size * 0.906, height: size * 0.906)
|
|
||||||
.position(center)
|
|
||||||
}
|
|
||||||
|
|
||||||
// House outline
|
|
||||||
HousePath(progress: houseProgress)
|
|
||||||
.stroke(foregroundColor, style: StrokeStyle(
|
|
||||||
lineWidth: size * 0.055,
|
|
||||||
lineCap: .round,
|
|
||||||
lineJoin: .round
|
|
||||||
))
|
|
||||||
.frame(width: size, height: size)
|
|
||||||
.position(center)
|
|
||||||
|
|
||||||
// Window
|
|
||||||
RoundedRectangle(cornerRadius: size * 0.023)
|
|
||||||
.fill(foregroundColor)
|
|
||||||
.frame(width: size * 0.102, height: size * 0.102)
|
|
||||||
.scaleEffect(windowScale)
|
|
||||||
.position(x: center.x, y: center.y - size * 0.09)
|
|
||||||
|
|
||||||
// Checkmark
|
|
||||||
CheckmarkShape()
|
|
||||||
.stroke(foregroundColor, style: StrokeStyle(
|
|
||||||
lineWidth: size * 0.0625,
|
|
||||||
lineCap: .round,
|
|
||||||
lineJoin: .round
|
|
||||||
))
|
|
||||||
.frame(width: size, height: size)
|
|
||||||
.scaleEffect(checkmarkScale)
|
|
||||||
.position(center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aspectRatio(1, contentMode: .fit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - House Path (both sides)
|
|
||||||
|
|
||||||
struct HousePath: Shape {
|
|
||||||
var progress: CGFloat = 1.0
|
|
||||||
|
|
||||||
var animatableData: CGFloat {
|
|
||||||
get { progress }
|
|
||||||
set { progress = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
func path(in rect: CGRect) -> Path {
|
|
||||||
let w = rect.width
|
|
||||||
let h = rect.height
|
|
||||||
let cx = rect.midX
|
|
||||||
let cy = rect.midY
|
|
||||||
|
|
||||||
var path = Path()
|
|
||||||
|
|
||||||
// Left side: roof peak -> down left wall -> foot
|
|
||||||
path.move(to: CGPoint(x: cx, y: cy - h * 0.27))
|
|
||||||
path.addLine(to: CGPoint(x: cx - w * 0.232, y: cy - h * 0.09))
|
|
||||||
path.addQuadCurve(
|
|
||||||
to: CGPoint(x: cx - w * 0.266, y: cy + h * 0.02),
|
|
||||||
control: CGPoint(x: cx - w * 0.266, y: cy - h * 0.055)
|
|
||||||
)
|
|
||||||
path.addLine(to: CGPoint(x: cx - w * 0.266, y: cy + h * 0.195))
|
|
||||||
path.addQuadCurve(
|
|
||||||
to: CGPoint(x: cx - w * 0.207, y: cy + h * 0.254),
|
|
||||||
control: CGPoint(x: cx - w * 0.266, y: cy + h * 0.254)
|
|
||||||
)
|
|
||||||
path.addLine(to: CGPoint(x: cx - w * 0.154, y: cy + h * 0.254))
|
|
||||||
|
|
||||||
// Right side: roof peak -> down right wall -> foot
|
|
||||||
path.move(to: CGPoint(x: cx, y: cy - h * 0.27))
|
|
||||||
path.addLine(to: CGPoint(x: cx + w * 0.232, y: cy - h * 0.09))
|
|
||||||
path.addQuadCurve(
|
|
||||||
to: CGPoint(x: cx + w * 0.266, y: cy + h * 0.02),
|
|
||||||
control: CGPoint(x: cx + w * 0.266, y: cy - h * 0.055)
|
|
||||||
)
|
|
||||||
path.addLine(to: CGPoint(x: cx + w * 0.266, y: cy + h * 0.195))
|
|
||||||
path.addQuadCurve(
|
|
||||||
to: CGPoint(x: cx + w * 0.207, y: cy + h * 0.254),
|
|
||||||
control: CGPoint(x: cx + w * 0.266, y: cy + h * 0.254)
|
|
||||||
)
|
|
||||||
path.addLine(to: CGPoint(x: cx + w * 0.154, y: cy + h * 0.254))
|
|
||||||
|
|
||||||
return path.trimmedPath(from: 0, to: progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Checkmark Shape
|
|
||||||
|
|
||||||
struct CheckmarkShape: Shape {
|
|
||||||
var progress: CGFloat = 1.0
|
|
||||||
|
|
||||||
var animatableData: CGFloat {
|
|
||||||
get { progress }
|
|
||||||
set { progress = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
func path(in rect: CGRect) -> Path {
|
|
||||||
let w = rect.width
|
|
||||||
let h = rect.height
|
|
||||||
let cx = rect.midX
|
|
||||||
let cy = rect.midY
|
|
||||||
|
|
||||||
var path = Path()
|
|
||||||
// Checkmark: starts bottom-left, goes to bottom-center, then up-right
|
|
||||||
path.move(to: CGPoint(x: cx - w * 0.158, y: cy + h * 0.145))
|
|
||||||
path.addLine(to: CGPoint(x: cx - w * 0.041, y: cy + h * 0.263))
|
|
||||||
path.addLine(to: CGPoint(x: cx + w * 0.193, y: cy + h * 0.01))
|
|
||||||
|
|
||||||
return path.trimmedPath(from: 0, to: progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Animations
|
|
||||||
|
|
||||||
struct FullIntroAnimationView: View {
|
|
||||||
@State private var houseProgress: CGFloat = 0
|
|
||||||
@State private var windowScale: CGFloat = 0
|
|
||||||
@State private var checkScale: CGFloat = 0
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HoneyDueIconView(
|
|
||||||
houseProgress: houseProgress,
|
|
||||||
windowScale: windowScale,
|
|
||||||
checkmarkScale: checkScale
|
|
||||||
)
|
|
||||||
.onAppear { animate() }
|
|
||||||
}
|
|
||||||
|
|
||||||
func animate() {
|
|
||||||
withAnimation(.easeOut(duration: 0.6)) {
|
|
||||||
houseProgress = 1.0
|
|
||||||
}
|
|
||||||
withAnimation(.spring(response: 0.4, dampingFraction: 0.6).delay(0.5)) {
|
|
||||||
windowScale = 1.0
|
|
||||||
}
|
|
||||||
withAnimation(.easeOut(duration: 0.25).delay(0.9)) {
|
|
||||||
checkScale = 1.2
|
|
||||||
}
|
|
||||||
withAnimation(.easeInOut(duration: 0.15).delay(1.15)) {
|
|
||||||
checkScale = 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PulsatingCheckmarkView: View {
|
|
||||||
@State private var checkScale: CGFloat = 1.0
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HoneyDueIconView(checkmarkScale: checkScale)
|
|
||||||
.onAppear {
|
|
||||||
withAnimation(.easeInOut(duration: 0.5).repeatForever(autoreverses: true)) {
|
|
||||||
checkScale = 1.3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PulsingIconView: View {
|
|
||||||
@State private var scale: CGFloat = 1.0
|
|
||||||
var backgroundColor: Color? = nil
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HoneyDueIconView(backgroundColor: backgroundColor)
|
|
||||||
.scaleEffect(scale)
|
|
||||||
.onAppear {
|
|
||||||
withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) {
|
|
||||||
scale = 1.08
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BouncyIconView: View {
|
|
||||||
@State private var offset: CGFloat = -300
|
|
||||||
@State private var scale: CGFloat = 0.5
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HoneyDueIconView()
|
|
||||||
.scaleEffect(scale)
|
|
||||||
.offset(y: offset)
|
|
||||||
.onAppear {
|
|
||||||
withAnimation(.spring(response: 0.6, dampingFraction: 0.5)) {
|
|
||||||
offset = 0
|
|
||||||
scale = 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WigglingIconView: View {
|
|
||||||
@State private var angle: Double = 0
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HoneyDueIconView()
|
|
||||||
.rotationEffect(.degrees(angle))
|
|
||||||
.onAppear {
|
|
||||||
withAnimation(.easeInOut(duration: 0.1).repeatForever(autoreverses: true)) {
|
|
||||||
angle = 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Playground UI
|
|
||||||
|
|
||||||
struct PlaygroundContentView: View {
|
|
||||||
@State private var selectedAnimation = 0
|
|
||||||
@State private var animationKey = UUID()
|
|
||||||
|
|
||||||
let animations = ["Full Intro", "Pulsating", "Pulse", "Bounce", "Wiggle"]
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 20) {
|
|
||||||
Text("HoneyDue Icon Animations")
|
|
||||||
.font(.title)
|
|
||||||
.fontWeight(.bold)
|
|
||||||
|
|
||||||
ZStack {
|
|
||||||
RoundedRectangle(cornerRadius: 20)
|
|
||||||
.fill(Color(.systemGray6))
|
|
||||||
.frame(height: 250)
|
|
||||||
|
|
||||||
currentAnimation
|
|
||||||
.frame(width: 150, height: 150)
|
|
||||||
.id(animationKey)
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
HStack(spacing: 10) {
|
|
||||||
ForEach(0..<animations.count, id: \.self) { index in
|
|
||||||
Button {
|
|
||||||
selectedAnimation = index
|
|
||||||
animationKey = UUID()
|
|
||||||
} label: {
|
|
||||||
Text(animations[index])
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(selectedAnimation == index ? .bold : .regular)
|
|
||||||
.padding(.horizontal, 12)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
.background(
|
|
||||||
Capsule().fill(selectedAnimation == index ? Color.orange : Color(.systemGray5))
|
|
||||||
)
|
|
||||||
.foregroundColor(selectedAnimation == index ? .white : .primary)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button("Replay") { animationKey = UUID() }
|
|
||||||
.buttonStyle(.borderedProminent)
|
|
||||||
.tint(.orange)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.top, 30)
|
|
||||||
.frame(width: 450, height: 500)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
var currentAnimation: some View {
|
|
||||||
switch selectedAnimation {
|
|
||||||
case 0: FullIntroAnimationView()
|
|
||||||
case 1: PulsatingCheckmarkView()
|
|
||||||
case 2: PulsingIconView()
|
|
||||||
case 3: BouncyIconView()
|
|
||||||
case 4: WigglingIconView()
|
|
||||||
default: HoneyDueIconView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
iosApp/HoneyDue/WidgetIconView.swift
Normal file
0
iosApp/HoneyDue/WidgetIconView.swift
Normal file
@@ -89,6 +89,13 @@
|
|||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
1C0789572EBC218D00392B46 /* Exceptions for "HoneyDue" folder in "HoneyDueExtension" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = 1C07893C2EBC218B00392B46 /* HoneyDueExtension */;
|
||||||
|
};
|
||||||
1C77EDA12ECE784100A53003 /* Exceptions for "iosApp" folder in "HoneyDueUITests" target */ = {
|
1C77EDA12ECE784100A53003 /* Exceptions for "iosApp" folder in "HoneyDueUITests" target */ = {
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
@@ -96,6 +103,34 @@
|
|||||||
);
|
);
|
||||||
target = 1CBF1BEC2ECD9768001BF56C /* HoneyDueUITests */;
|
target = 1CBF1BEC2ECD9768001BF56C /* HoneyDueUITests */;
|
||||||
};
|
};
|
||||||
|
1C77EDA22ECE797700A53003 /* Exceptions for "HoneyDueUITests" folder in "HoneyDueUITests" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
AccessibilityIdentifiers.swift,
|
||||||
|
);
|
||||||
|
target = 1CBF1BEC2ECD9768001BF56C /* HoneyDueUITests */;
|
||||||
|
};
|
||||||
|
1C81F27B2EE416EF000739EA /* Exceptions for "HoneyDueQLPreview" folder in "HoneyDueQLPreview" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = 1C81F2682EE416EE000739EA /* HoneyDueQLPreview */;
|
||||||
|
};
|
||||||
|
1C81F28D2EE41BB6000739EA /* Exceptions for "HoneyDueQLThumbnail" folder in "HoneyDueQLThumbnail" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = 1C81F27F2EE41BB6000739EA /* HoneyDueQLThumbnail */;
|
||||||
|
};
|
||||||
|
1C81F38A2EE5E430000739EA /* Exceptions for "HoneyDue" folder in "HoneyDue" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
WidgetIconView.swift,
|
||||||
|
);
|
||||||
|
target = D4ADB376A7A4CFB73469E173 /* HoneyDue */;
|
||||||
|
};
|
||||||
1C87A67A2EDCC3100081E450 /* Exceptions for "iosApp" folder in "HoneyDueExtension" target */ = {
|
1C87A67A2EDCC3100081E450 /* Exceptions for "iosApp" folder in "HoneyDueExtension" target */ = {
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
@@ -106,6 +141,13 @@
|
|||||||
);
|
);
|
||||||
target = 1C07893C2EBC218B00392B46 /* HoneyDueExtension */;
|
target = 1C07893C2EBC218B00392B46 /* HoneyDueExtension */;
|
||||||
};
|
};
|
||||||
|
1CBF1C072ECD97AC001BF56C /* Exceptions for "HoneyDueTests" folder in "HoneyDueTests" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
HoneyDueTests.swift,
|
||||||
|
);
|
||||||
|
target = 1C685CD12EC5539000A9669B /* HoneyDueTests */;
|
||||||
|
};
|
||||||
84D9B4B86A80D013B8CBB951 /* Exceptions for "iosApp" folder in "HoneyDue" target */ = {
|
84D9B4B86A80D013B8CBB951 /* Exceptions for "iosApp" folder in "HoneyDue" target */ = {
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
@@ -118,26 +160,42 @@
|
|||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
1C0789432EBC218B00392B46 /* HoneyDue */ = {
|
1C0789432EBC218B00392B46 /* HoneyDue */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
1C81F38A2EE5E430000739EA /* Exceptions for "HoneyDue" folder in "HoneyDue" target */,
|
||||||
|
1C0789572EBC218D00392B46 /* Exceptions for "HoneyDue" folder in "HoneyDueExtension" target */,
|
||||||
|
);
|
||||||
path = HoneyDue;
|
path = HoneyDue;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
1C685CD32EC5539000A9669B /* HoneyDueTests */ = {
|
1C685CD32EC5539000A9669B /* HoneyDueTests */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
1CBF1C072ECD97AC001BF56C /* Exceptions for "HoneyDueTests" folder in "HoneyDueTests" target */,
|
||||||
|
);
|
||||||
path = HoneyDueTests;
|
path = HoneyDueTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
1C81F26C2EE416EE000739EA /* HoneyDueQLPreview */ = {
|
1C81F26C2EE416EE000739EA /* HoneyDueQLPreview */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
1C81F27B2EE416EF000739EA /* Exceptions for "HoneyDueQLPreview" folder in "HoneyDueQLPreview" target */,
|
||||||
|
);
|
||||||
path = HoneyDueQLPreview;
|
path = HoneyDueQLPreview;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
1C81F2832EE41BB6000739EA /* HoneyDueQLThumbnail */ = {
|
1C81F2832EE41BB6000739EA /* HoneyDueQLThumbnail */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
1C81F28D2EE41BB6000739EA /* Exceptions for "HoneyDueQLThumbnail" folder in "HoneyDueQLThumbnail" target */,
|
||||||
|
);
|
||||||
path = HoneyDueQLThumbnail;
|
path = HoneyDueQLThumbnail;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
1CBF1BEE2ECD9768001BF56C /* HoneyDueUITests */ = {
|
1CBF1BEE2ECD9768001BF56C /* HoneyDueUITests */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
1C77EDA22ECE797700A53003 /* Exceptions for "HoneyDueUITests" folder in "HoneyDueUITests" target */,
|
||||||
|
);
|
||||||
path = HoneyDueUITests;
|
path = HoneyDueUITests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user