Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
97d8b648e4
commit
9233d779a3
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.250",
|
||||||
|
"green" : "0.750",
|
||||||
|
"red" : "0.950"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.140",
|
||||||
|
"green" : "0.120",
|
||||||
|
"red" : "0.120"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.550",
|
||||||
|
"green" : "0.650",
|
||||||
|
"red" : "0.200"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.280",
|
||||||
|
"green" : "0.330",
|
||||||
|
"red" : "0.950"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.400",
|
||||||
|
"green" : "0.330",
|
||||||
|
"red" : "0.290"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.900",
|
||||||
|
"green" : "0.890",
|
||||||
|
"red" : "0.890"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.340",
|
||||||
|
"green" : "0.820",
|
||||||
|
"red" : "0.980"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.278",
|
||||||
|
"green" : "0.329",
|
||||||
|
"red" : "0.949"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.150",
|
||||||
|
"green" : "0.180",
|
||||||
|
"red" : "0.650"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.280",
|
||||||
|
"green" : "0.750",
|
||||||
|
"red" : "0.980"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.330",
|
||||||
|
"green" : "0.350",
|
||||||
|
"red" : "0.950"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.310",
|
||||||
|
"green" : "0.370",
|
||||||
|
"red" : "0.130"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.340",
|
||||||
|
"green" : "0.820",
|
||||||
|
"red" : "0.730"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.220",
|
||||||
|
"green" : "0.160",
|
||||||
|
"red" : "0.120"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.560",
|
||||||
|
"green" : "0.450",
|
||||||
|
"red" : "0.080"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.520",
|
||||||
|
"green" : "0.270",
|
||||||
|
"red" : "0.560"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.730",
|
||||||
|
"green" : "0.680",
|
||||||
|
"red" : "0.950"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.680",
|
||||||
|
"green" : "0.830",
|
||||||
|
"red" : "0.930"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.500",
|
||||||
|
"green" : "0.440",
|
||||||
|
"red" : "0.380"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.620",
|
||||||
|
"green" : "0.360",
|
||||||
|
"red" : "0.420"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.350",
|
||||||
|
"green" : "0.820",
|
||||||
|
"red" : "0.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.700",
|
||||||
|
"green" : "0.400",
|
||||||
|
"red" : "0.260"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.130",
|
||||||
|
"green" : "0.130",
|
||||||
|
"red" : "0.130"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.420",
|
||||||
|
"green" : "0.190",
|
||||||
|
"red" : "0.880"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.710",
|
||||||
|
"green" : "0.470",
|
||||||
|
"red" : "0.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.890",
|
||||||
|
"green" : "0.630",
|
||||||
|
"red" : "0.160"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.000",
|
||||||
|
"green" : "0.000",
|
||||||
|
"red" : "0.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.000",
|
||||||
|
"green" : "0.000",
|
||||||
|
"red" : "0.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.950",
|
||||||
|
"green" : "0.630",
|
||||||
|
"red" : "0.110"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.790",
|
||||||
|
"green" : "0.530",
|
||||||
|
"red" : "0.220"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.380",
|
||||||
|
"green" : "0.680",
|
||||||
|
"red" : "0.150"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,13 +15,13 @@ extension Color {
|
|||||||
enum Branding {
|
enum Branding {
|
||||||
/// Primary gradient color (warm coral/red).
|
/// Primary gradient color (warm coral/red).
|
||||||
/// Must match LaunchBackground.colorset exactly to prevent flash.
|
/// Must match LaunchBackground.colorset exactly to prevent flash.
|
||||||
static let primary = Color(red: 0.949, green: 0.329, blue: 0.278)
|
static let primary = Color("BrandingPrimary")
|
||||||
|
|
||||||
/// Secondary gradient color (darker red).
|
/// Secondary gradient color (darker red).
|
||||||
static let secondary = Color(red: 0.65, green: 0.18, blue: 0.15)
|
static let secondary = Color("BrandingSecondary")
|
||||||
|
|
||||||
/// Accent color for icons and highlights.
|
/// Accent color for icons and highlights.
|
||||||
static let accent = Color.white
|
static let accent = Color("BrandingAccent")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ extension LaunchScreenConfig {
|
|||||||
primaryColor: Color.Branding.primary,
|
primaryColor: Color.Branding.primary,
|
||||||
secondaryColor: Color.Branding.secondary,
|
secondaryColor: Color.Branding.secondary,
|
||||||
accentColor: Color.Branding.accent,
|
accentColor: Color.Branding.accent,
|
||||||
titleColor: .white,
|
titleColor: Color.Branding.accent,
|
||||||
iconSize: 52,
|
iconSize: 52,
|
||||||
titleSize: 32,
|
titleSize: 32,
|
||||||
iconSpacing: 12,
|
iconSpacing: 12,
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import SwiftUI
|
|||||||
import Bedrock
|
import Bedrock
|
||||||
|
|
||||||
public enum BusinessCardAccentColors: AccentColorProvider {
|
public enum BusinessCardAccentColors: AccentColorProvider {
|
||||||
public static let primary = Color(red: 0.95, green: 0.33, blue: 0.28)
|
public static let primary = Color.Accent.red
|
||||||
public static let light = Color(red: 0.98, green: 0.50, blue: 0.45)
|
public static let light = Color("CardPaletteRose")
|
||||||
public static let dark = Color(red: 0.75, green: 0.25, blue: 0.22)
|
public static let dark = Color("BrandingSecondary")
|
||||||
public static let secondary = Color(red: 0.12, green: 0.12, blue: 0.14)
|
public static let secondary = Color.Accent.ink
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import SwiftUI
|
|||||||
import Bedrock
|
import Bedrock
|
||||||
|
|
||||||
public enum BusinessCardButtonColors: ButtonColorProvider {
|
public enum BusinessCardButtonColors: ButtonColorProvider {
|
||||||
public static let primaryLight = Color(red: 0.98, green: 0.45, blue: 0.40)
|
public static let primaryLight = Color("CardPaletteRose")
|
||||||
public static let primaryDark = Color(red: 0.85, green: 0.28, blue: 0.24)
|
public static let primaryDark = Color("BrandingSecondary")
|
||||||
public static let secondary = Color(red: 0.14, green: 0.14, blue: 0.17).opacity(Design.Opacity.subtle)
|
public static let secondary = Color.AppBackground.accent.opacity(Design.Opacity.subtle)
|
||||||
public static let destructive = Color.red.opacity(Design.Opacity.heavy)
|
public static let destructive = Color.Accent.red.opacity(Design.Opacity.heavy)
|
||||||
public static let cancelText = Color(red: 0.32, green: 0.34, blue: 0.40)
|
public static let cancelText = Color.Text.secondary
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import Bedrock
|
|||||||
|
|
||||||
public enum BusinessCardInteractiveColors: InteractiveColorProvider {
|
public enum BusinessCardInteractiveColors: InteractiveColorProvider {
|
||||||
public static let selected = BusinessCardAccentColors.primary.opacity(Design.Opacity.selection)
|
public static let selected = BusinessCardAccentColors.primary.opacity(Design.Opacity.selection)
|
||||||
public static let hover = Color(red: 0.14, green: 0.14, blue: 0.17).opacity(Design.Opacity.subtle)
|
public static let hover = Color.AppBackground.accent.opacity(Design.Opacity.subtle)
|
||||||
public static let pressed = Color(red: 0.14, green: 0.14, blue: 0.17).opacity(Design.Opacity.hint)
|
public static let pressed = Color.AppBackground.accent.opacity(Design.Opacity.hint)
|
||||||
public static let focus = BusinessCardAccentColors.light
|
public static let focus = BusinessCardAccentColors.light
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import SwiftUI
|
|||||||
import Bedrock
|
import Bedrock
|
||||||
|
|
||||||
public enum BusinessCardStatusColors: StatusColorProvider {
|
public enum BusinessCardStatusColors: StatusColorProvider {
|
||||||
public static let success = Color(red: 0.2, green: 0.75, blue: 0.4)
|
public static let success = Color.Accent.mint
|
||||||
public static let warning = Color(red: 0.95, green: 0.75, blue: 0.25)
|
public static let warning = Color.Accent.gold
|
||||||
public static let error = Color(red: 0.9, green: 0.3, blue: 0.3)
|
public static let error = Color.Accent.red
|
||||||
public static let info = Color(red: 0.3, green: 0.6, blue: 0.9)
|
public static let info = Color("SocialTwitter")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,6 +91,42 @@ extension Design {
|
|||||||
DeviceSizeTier.isTablet ? 500 : nil
|
DeviceSizeTier.isTablet ? 500 : nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contacts feature constants (list behavior, row layout, note UI).
|
||||||
|
enum Contacts {
|
||||||
|
static let detailFieldIconWidth: CGFloat = 28
|
||||||
|
static let detailHeaderAvatarSize: CGFloat = 92
|
||||||
|
static let sectionIndexMinContacts = 20
|
||||||
|
static let sectionIndexMinSections = 6
|
||||||
|
static let sectionIndexLetterSpacing: CGFloat = 2
|
||||||
|
static let sectionIndexLetterWidth: CGFloat = 16
|
||||||
|
static let sectionIndexLetterHeight: CGFloat = 12
|
||||||
|
static let sectionIndexScrollAnimationDuration: Double = 0.2
|
||||||
|
static let notePreviewLineLimit = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Image color extraction constants.
|
||||||
|
enum ColorExtraction {
|
||||||
|
static let sampleSize = 50
|
||||||
|
static let rgbaBytesPerPixel = 4
|
||||||
|
static let alphaVisibilityThreshold: UInt8 = 128
|
||||||
|
static let minBrightness = 30
|
||||||
|
static let maxBrightness = 225
|
||||||
|
static let bucketQuantizationStep = 32
|
||||||
|
static let rgbDenominator = 255.0
|
||||||
|
static let colorSimilarityThreshold = 0.15
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Card theme math constants.
|
||||||
|
enum ThemeMath {
|
||||||
|
static let luminanceRedWeight = 0.299
|
||||||
|
static let luminanceGreenWeight = 0.587
|
||||||
|
static let luminanceBlueWeight = 0.114
|
||||||
|
static let requiresDarkTextThreshold = 0.5
|
||||||
|
static let darkThemeLuminanceThreshold = 0.3
|
||||||
|
static let customLightenAmount = 0.15
|
||||||
|
static let customDarkenAmount = 0.12
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Shadow Extensions
|
// MARK: - Shadow Extensions
|
||||||
@ -118,27 +154,27 @@ extension Color {
|
|||||||
// MARK: - Card Theme Palette
|
// MARK: - Card Theme Palette
|
||||||
|
|
||||||
enum CardPalette {
|
enum CardPalette {
|
||||||
static let coral = Color(red: 0.95, green: 0.35, blue: 0.33)
|
static let coral = Color("CardPaletteCoral")
|
||||||
static let midnight = Color(red: 0.12, green: 0.16, blue: 0.22)
|
static let midnight = Color("CardPaletteMidnight")
|
||||||
static let ocean = Color(red: 0.08, green: 0.45, blue: 0.56)
|
static let ocean = Color("CardPaletteOcean")
|
||||||
static let lime = Color(red: 0.73, green: 0.82, blue: 0.34)
|
static let lime = Color("CardPaletteLime")
|
||||||
static let violet = Color(red: 0.42, green: 0.36, blue: 0.62)
|
static let violet = Color("CardPaletteViolet")
|
||||||
static let forest = Color(red: 0.13, green: 0.37, blue: 0.31)
|
static let forest = Color("CardPaletteForest")
|
||||||
static let rose = Color(red: 0.95, green: 0.68, blue: 0.73)
|
static let rose = Color("CardPaletteRose")
|
||||||
static let slate = Color(red: 0.38, green: 0.44, blue: 0.50)
|
static let slate = Color("CardPaletteSlate")
|
||||||
static let amber = Color(red: 0.98, green: 0.75, blue: 0.28)
|
static let amber = Color("CardPaletteAmber")
|
||||||
static let plum = Color(red: 0.56, green: 0.27, blue: 0.52)
|
static let plum = Color("CardPalettePlum")
|
||||||
static let sand = Color(red: 0.93, green: 0.83, blue: 0.68)
|
static let sand = Color("CardPaletteSand")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - App Accent Colors
|
// MARK: - App Accent Colors
|
||||||
|
|
||||||
enum AppAccent {
|
enum AppAccent {
|
||||||
static let red = Color(red: 0.95, green: 0.33, blue: 0.28)
|
static let red = Color("AppAccentRed")
|
||||||
static let gold = Color(red: 0.95, green: 0.75, blue: 0.25)
|
static let gold = Color("AppAccentGold")
|
||||||
static let mint = Color(red: 0.2, green: 0.65, blue: 0.55)
|
static let mint = Color("AppAccentMint")
|
||||||
static let ink = Color(red: 0.12, green: 0.12, blue: 0.14)
|
static let ink = Color("AppAccentInk")
|
||||||
static let slate = Color(red: 0.29, green: 0.33, blue: 0.4)
|
static let slate = Color("AppAccentSlate")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - App Text Colors
|
// MARK: - App Text Colors
|
||||||
@ -153,8 +189,8 @@ extension Color {
|
|||||||
// MARK: - Badge Colors
|
// MARK: - Badge Colors
|
||||||
|
|
||||||
enum Badge {
|
enum Badge {
|
||||||
static let star = Color(red: 0.98, green: 0.82, blue: 0.34)
|
static let star = Color("BadgeStar")
|
||||||
static let neutral = Color(red: 0.89, green: 0.89, blue: 0.9)
|
static let neutral = Color("BadgeNeutral")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Share Sheet Theme
|
// MARK: - Share Sheet Theme
|
||||||
@ -170,17 +206,17 @@ extension Color {
|
|||||||
// MARK: - Social Media Brand Colors
|
// MARK: - Social Media Brand Colors
|
||||||
|
|
||||||
enum Social {
|
enum Social {
|
||||||
static let linkedIn = Color(red: 0.0, green: 0.47, blue: 0.71)
|
static let linkedIn = Color("SocialLinkedIn")
|
||||||
static let twitter = Color(red: 0.11, green: 0.63, blue: 0.95)
|
static let twitter = Color("SocialTwitter")
|
||||||
static let instagram = Color(red: 0.88, green: 0.19, blue: 0.42)
|
static let instagram = Color("SocialInstagram")
|
||||||
static let facebook = Color(red: 0.26, green: 0.40, blue: 0.70)
|
static let facebook = Color("SocialFacebook")
|
||||||
static let tiktok = Color(red: 0.0, green: 0.0, blue: 0.0)
|
static let tiktok = Color("SocialTikTok")
|
||||||
static let github = Color(red: 0.13, green: 0.13, blue: 0.13)
|
static let github = Color("SocialGitHub")
|
||||||
static let threads = Color(red: 0.0, green: 0.0, blue: 0.0)
|
static let threads = Color("SocialThreads")
|
||||||
static let telegram = Color(red: 0.16, green: 0.63, blue: 0.89)
|
static let telegram = Color("SocialTelegram")
|
||||||
static let whatsapp = Color(red: 0.15, green: 0.68, blue: 0.38)
|
static let whatsapp = Color("SocialWhatsApp")
|
||||||
static let venmo = Color(red: 0.22, green: 0.53, blue: 0.79)
|
static let venmo = Color("SocialVenmo")
|
||||||
static let cashApp = Color(red: 0.0, green: 0.82, blue: 0.35)
|
static let cashApp = Color("SocialCashApp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Bedrock
|
||||||
|
|
||||||
/// Card theme configuration supporting both preset themes and custom colors.
|
/// Card theme configuration supporting both preset themes and custom colors.
|
||||||
///
|
///
|
||||||
@ -88,8 +89,11 @@ struct CardTheme: Identifiable, Hashable, Sendable {
|
|||||||
var requiresDarkText: Bool {
|
var requiresDarkText: Bool {
|
||||||
if let rgb = customRGB {
|
if let rgb = customRGB {
|
||||||
// Calculate perceived luminance for custom colors
|
// Calculate perceived luminance for custom colors
|
||||||
let luminance = 0.299 * rgb.0 + 0.587 * rgb.1 + 0.114 * rgb.2
|
let luminance =
|
||||||
return luminance > 0.5
|
Design.ThemeMath.luminanceRedWeight * rgb.0
|
||||||
|
+ Design.ThemeMath.luminanceGreenWeight * rgb.1
|
||||||
|
+ Design.ThemeMath.luminanceBlueWeight * rgb.2
|
||||||
|
return luminance > Design.ThemeMath.requiresDarkTextThreshold
|
||||||
}
|
}
|
||||||
guard let preset else { return false }
|
guard let preset else { return false }
|
||||||
switch preset {
|
switch preset {
|
||||||
@ -100,106 +104,82 @@ struct CardTheme: Identifiable, Hashable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - RGB Values
|
|
||||||
|
|
||||||
private var primaryRGB: (Double, Double, Double) {
|
|
||||||
if let rgb = customRGB { return rgb }
|
|
||||||
guard let preset else { return (0.95, 0.35, 0.33) }
|
|
||||||
switch preset {
|
|
||||||
case .coral: return (0.95, 0.35, 0.33)
|
|
||||||
case .midnight: return (0.12, 0.16, 0.22)
|
|
||||||
case .ocean: return (0.08, 0.45, 0.56)
|
|
||||||
case .lime: return (0.73, 0.82, 0.34)
|
|
||||||
case .violet: return (0.42, 0.36, 0.62)
|
|
||||||
case .forest: return (0.13, 0.37, 0.31)
|
|
||||||
case .rose: return (0.95, 0.68, 0.73)
|
|
||||||
case .slate: return (0.38, 0.44, 0.50)
|
|
||||||
case .amber: return (0.98, 0.75, 0.28)
|
|
||||||
case .plum: return (0.56, 0.27, 0.52)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var secondaryRGB: (Double, Double, Double) {
|
|
||||||
if let rgb = customRGB {
|
|
||||||
// Calculate luminance to determine if we should lighten or darken
|
|
||||||
let luminance = 0.299 * rgb.0 + 0.587 * rgb.1 + 0.114 * rgb.2
|
|
||||||
|
|
||||||
if luminance < 0.3 {
|
|
||||||
// Dark color: lighten uniformly to avoid color shifts
|
|
||||||
let lightenAmount = 0.15
|
|
||||||
return (
|
|
||||||
min(1.0, rgb.0 + lightenAmount),
|
|
||||||
min(1.0, rgb.1 + lightenAmount),
|
|
||||||
min(1.0, rgb.2 + lightenAmount)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Light color: darken uniformly
|
|
||||||
let darkenAmount = 0.12
|
|
||||||
return (
|
|
||||||
max(0.0, rgb.0 - darkenAmount),
|
|
||||||
max(0.0, rgb.1 - darkenAmount),
|
|
||||||
max(0.0, rgb.2 - darkenAmount)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard let preset else { return (0.93, 0.83, 0.68) }
|
|
||||||
switch preset {
|
|
||||||
case .coral: return (0.93, 0.83, 0.68)
|
|
||||||
case .midnight: return (0.29, 0.33, 0.4)
|
|
||||||
case .ocean: return (0.2, 0.65, 0.55)
|
|
||||||
case .lime: return (0.93, 0.83, 0.68)
|
|
||||||
case .violet: return (0.29, 0.33, 0.4)
|
|
||||||
case .forest: return (0.22, 0.52, 0.42)
|
|
||||||
case .rose: return (0.98, 0.85, 0.88)
|
|
||||||
case .slate: return (0.52, 0.58, 0.64)
|
|
||||||
case .amber: return (0.99, 0.88, 0.55)
|
|
||||||
case .plum: return (0.72, 0.45, 0.68)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var accentRGB: (Double, Double, Double) {
|
|
||||||
if customRGB != nil {
|
|
||||||
// For custom colors, use a contrasting accent (gold or dark)
|
|
||||||
return requiresDarkText ? (0.12, 0.12, 0.14) : (0.95, 0.75, 0.25)
|
|
||||||
}
|
|
||||||
guard let preset else { return (0.95, 0.33, 0.28) }
|
|
||||||
switch preset {
|
|
||||||
case .coral: return (0.95, 0.33, 0.28)
|
|
||||||
case .midnight: return (0.95, 0.75, 0.25)
|
|
||||||
case .ocean: return (0.95, 0.75, 0.25)
|
|
||||||
case .lime: return (0.12, 0.12, 0.14)
|
|
||||||
case .violet: return (0.95, 0.75, 0.25)
|
|
||||||
case .forest: return (0.95, 0.75, 0.25)
|
|
||||||
case .rose: return (0.75, 0.25, 0.35)
|
|
||||||
case .slate: return (0.95, 0.75, 0.25)
|
|
||||||
case .amber: return (0.12, 0.12, 0.14)
|
|
||||||
case .plum: return (0.95, 0.75, 0.25)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var textRGB: (Double, Double, Double) {
|
|
||||||
requiresDarkText
|
|
||||||
? (0.14, 0.14, 0.17) // Dark text for light backgrounds
|
|
||||||
: (0.98, 0.98, 0.98) // Light text for dark backgrounds
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Colors (MainActor)
|
// MARK: - Colors (MainActor)
|
||||||
|
|
||||||
@MainActor var primaryColor: Color {
|
@MainActor var primaryColor: Color {
|
||||||
Color(red: primaryRGB.0, green: primaryRGB.1, blue: primaryRGB.2)
|
if let rgb = customRGB {
|
||||||
|
return Color(red: rgb.0, green: rgb.1, blue: rgb.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let preset else { return Color.CardPalette.coral }
|
||||||
|
switch preset {
|
||||||
|
case .coral: return Color.CardPalette.coral
|
||||||
|
case .midnight: return Color.CardPalette.midnight
|
||||||
|
case .ocean: return Color.CardPalette.ocean
|
||||||
|
case .lime: return Color.CardPalette.lime
|
||||||
|
case .violet: return Color.CardPalette.violet
|
||||||
|
case .forest: return Color.CardPalette.forest
|
||||||
|
case .rose: return Color.CardPalette.rose
|
||||||
|
case .slate: return Color.CardPalette.slate
|
||||||
|
case .amber: return Color.CardPalette.amber
|
||||||
|
case .plum: return Color.CardPalette.plum
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor var secondaryColor: Color {
|
@MainActor var secondaryColor: Color {
|
||||||
Color(red: secondaryRGB.0, green: secondaryRGB.1, blue: secondaryRGB.2)
|
if let rgb = customRGB {
|
||||||
|
// Calculate luminance to determine if we should lighten or darken.
|
||||||
|
let luminance =
|
||||||
|
Design.ThemeMath.luminanceRedWeight * rgb.0
|
||||||
|
+ Design.ThemeMath.luminanceGreenWeight * rgb.1
|
||||||
|
+ Design.ThemeMath.luminanceBlueWeight * rgb.2
|
||||||
|
if luminance < Design.ThemeMath.darkThemeLuminanceThreshold {
|
||||||
|
return Color(
|
||||||
|
red: min(1.0, rgb.0 + Design.ThemeMath.customLightenAmount),
|
||||||
|
green: min(1.0, rgb.1 + Design.ThemeMath.customLightenAmount),
|
||||||
|
blue: min(1.0, rgb.2 + Design.ThemeMath.customLightenAmount)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Color(
|
||||||
|
red: max(0.0, rgb.0 - Design.ThemeMath.customDarkenAmount),
|
||||||
|
green: max(0.0, rgb.1 - Design.ThemeMath.customDarkenAmount),
|
||||||
|
blue: max(0.0, rgb.2 - Design.ThemeMath.customDarkenAmount)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let preset else { return Color.CardPalette.sand }
|
||||||
|
switch preset {
|
||||||
|
case .coral, .lime: return Color.CardPalette.sand
|
||||||
|
case .midnight, .violet: return Color.Accent.slate
|
||||||
|
case .ocean: return Color.Accent.mint
|
||||||
|
case .forest: return Color("SocialWhatsApp")
|
||||||
|
case .rose: return Color("CardPaletteRose")
|
||||||
|
case .slate: return Color("CardPaletteSlate")
|
||||||
|
case .amber: return Color("CardPaletteSand")
|
||||||
|
case .plum: return Color("CardPalettePlum")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor var accentColor: Color {
|
@MainActor var accentColor: Color {
|
||||||
Color(red: accentRGB.0, green: accentRGB.1, blue: accentRGB.2)
|
if customRGB != nil {
|
||||||
|
return requiresDarkText ? Color.Accent.ink : Color.Accent.gold
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let preset else { return Color.Accent.red }
|
||||||
|
switch preset {
|
||||||
|
case .lime, .amber:
|
||||||
|
return Color.Accent.ink
|
||||||
|
case .rose:
|
||||||
|
return Color("BrandingSecondary")
|
||||||
|
case .coral, .midnight, .ocean, .violet, .forest, .slate, .plum:
|
||||||
|
return Color.Accent.gold
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The appropriate text color for content displayed on this theme's background.
|
/// The appropriate text color for content displayed on this theme's background.
|
||||||
@MainActor var textColor: Color {
|
@MainActor var textColor: Color {
|
||||||
Color(red: textRGB.0, green: textRGB.1, blue: textRGB.2)
|
requiresDarkText ? Color.Text.primary : Color.AppText.inverted
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Hashable & Equatable
|
// MARK: - Hashable & Equatable
|
||||||
|
|||||||
@ -117,7 +117,7 @@ extension ContactFieldType {
|
|||||||
id: "phone",
|
id: "phone",
|
||||||
displayName: String(localized: "Phone Number"),
|
displayName: String(localized: "Phone Number"),
|
||||||
systemImage: "phone.fill",
|
systemImage: "phone.fill",
|
||||||
iconColor: Color(red: 0.2, green: 0.2, blue: 0.2),
|
iconColor: Color.Text.secondary,
|
||||||
category: .contact,
|
category: .contact,
|
||||||
valueLabel: String(localized: "Phone Number"),
|
valueLabel: String(localized: "Phone Number"),
|
||||||
valuePlaceholder: "+1 (555) 123-4567",
|
valuePlaceholder: "+1 (555) 123-4567",
|
||||||
@ -134,7 +134,7 @@ extension ContactFieldType {
|
|||||||
id: "email",
|
id: "email",
|
||||||
displayName: String(localized: "Email"),
|
displayName: String(localized: "Email"),
|
||||||
systemImage: "envelope.fill",
|
systemImage: "envelope.fill",
|
||||||
iconColor: Color(red: 0.2, green: 0.2, blue: 0.2),
|
iconColor: Color.Text.secondary,
|
||||||
category: .contact,
|
category: .contact,
|
||||||
valueLabel: String(localized: "Email"),
|
valueLabel: String(localized: "Email"),
|
||||||
valuePlaceholder: "you@example.com",
|
valuePlaceholder: "you@example.com",
|
||||||
@ -147,7 +147,7 @@ extension ContactFieldType {
|
|||||||
id: "website",
|
id: "website",
|
||||||
displayName: String(localized: "Company Website"),
|
displayName: String(localized: "Company Website"),
|
||||||
systemImage: "globe",
|
systemImage: "globe",
|
||||||
iconColor: Color(red: 0.2, green: 0.2, blue: 0.2),
|
iconColor: Color.Text.secondary,
|
||||||
category: .contact,
|
category: .contact,
|
||||||
valueLabel: String(localized: "Website URL"),
|
valueLabel: String(localized: "Website URL"),
|
||||||
valuePlaceholder: "https://company.com",
|
valuePlaceholder: "https://company.com",
|
||||||
@ -160,7 +160,7 @@ extension ContactFieldType {
|
|||||||
id: "address",
|
id: "address",
|
||||||
displayName: String(localized: "Address"),
|
displayName: String(localized: "Address"),
|
||||||
systemImage: "location.fill",
|
systemImage: "location.fill",
|
||||||
iconColor: Color(red: 0.2, green: 0.2, blue: 0.2),
|
iconColor: Color.Text.secondary,
|
||||||
category: .contact,
|
category: .contact,
|
||||||
valueLabel: String(localized: "Address"),
|
valueLabel: String(localized: "Address"),
|
||||||
valuePlaceholder: "123 Main St, City, State",
|
valuePlaceholder: "123 Main St, City, State",
|
||||||
@ -187,7 +187,7 @@ extension ContactFieldType {
|
|||||||
displayName: "LinkedIn",
|
displayName: "LinkedIn",
|
||||||
systemImage: "linkedin",
|
systemImage: "linkedin",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "linkedin.com/in/username",
|
valuePlaceholder: "linkedin.com/in/username",
|
||||||
@ -201,7 +201,7 @@ extension ContactFieldType {
|
|||||||
displayName: "X",
|
displayName: "X",
|
||||||
systemImage: "x-twitter",
|
systemImage: "x-twitter",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "x.com/username",
|
valuePlaceholder: "x.com/username",
|
||||||
@ -215,7 +215,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Instagram",
|
displayName: "Instagram",
|
||||||
systemImage: "instagram",
|
systemImage: "instagram",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "instagram.com/username",
|
valuePlaceholder: "instagram.com/username",
|
||||||
@ -229,7 +229,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Facebook",
|
displayName: "Facebook",
|
||||||
systemImage: "facebook",
|
systemImage: "facebook",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "facebook.com/username",
|
valuePlaceholder: "facebook.com/username",
|
||||||
@ -243,7 +243,7 @@ extension ContactFieldType {
|
|||||||
displayName: "TikTok",
|
displayName: "TikTok",
|
||||||
systemImage: "tiktok",
|
systemImage: "tiktok",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "tiktok.com/@username",
|
valuePlaceholder: "tiktok.com/@username",
|
||||||
@ -257,7 +257,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Threads",
|
displayName: "Threads",
|
||||||
systemImage: "threads",
|
systemImage: "threads",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "threads.net/@username",
|
valuePlaceholder: "threads.net/@username",
|
||||||
@ -271,7 +271,7 @@ extension ContactFieldType {
|
|||||||
displayName: "YouTube",
|
displayName: "YouTube",
|
||||||
systemImage: "youtube",
|
systemImage: "youtube",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "youtube.com/@channel",
|
valuePlaceholder: "youtube.com/@channel",
|
||||||
@ -284,7 +284,7 @@ extension ContactFieldType {
|
|||||||
id: "snapchat",
|
id: "snapchat",
|
||||||
displayName: "Snapchat",
|
displayName: "Snapchat",
|
||||||
systemImage: "camera.fill",
|
systemImage: "camera.fill",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "snapchat.com/add/username",
|
valuePlaceholder: "snapchat.com/add/username",
|
||||||
@ -297,7 +297,7 @@ extension ContactFieldType {
|
|||||||
id: "pinterest",
|
id: "pinterest",
|
||||||
displayName: "Pinterest",
|
displayName: "Pinterest",
|
||||||
systemImage: "pin.fill",
|
systemImage: "pin.fill",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "pinterest.com/username",
|
valuePlaceholder: "pinterest.com/username",
|
||||||
@ -311,7 +311,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Twitch",
|
displayName: "Twitch",
|
||||||
systemImage: "twitch",
|
systemImage: "twitch",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "twitch.tv/username",
|
valuePlaceholder: "twitch.tv/username",
|
||||||
@ -325,7 +325,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Bluesky",
|
displayName: "Bluesky",
|
||||||
systemImage: "bluesky",
|
systemImage: "bluesky",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "bsky.app/profile/username",
|
valuePlaceholder: "bsky.app/profile/username",
|
||||||
@ -339,7 +339,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Mastodon",
|
displayName: "Mastodon",
|
||||||
systemImage: "mastodon",
|
systemImage: "mastodon",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "mastodon.social/@username",
|
valuePlaceholder: "mastodon.social/@username",
|
||||||
@ -362,7 +362,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Reddit",
|
displayName: "Reddit",
|
||||||
systemImage: "reddit",
|
systemImage: "reddit",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .social,
|
category: .social,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "reddit.com/user/username",
|
valuePlaceholder: "reddit.com/user/username",
|
||||||
@ -378,7 +378,7 @@ extension ContactFieldType {
|
|||||||
displayName: "GitHub",
|
displayName: "GitHub",
|
||||||
systemImage: "github.fill",
|
systemImage: "github.fill",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .developer,
|
category: .developer,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "github.com/username",
|
valuePlaceholder: "github.com/username",
|
||||||
@ -391,7 +391,7 @@ extension ContactFieldType {
|
|||||||
id: "gitlab",
|
id: "gitlab",
|
||||||
displayName: "GitLab",
|
displayName: "GitLab",
|
||||||
systemImage: "chevron.left.forwardslash.chevron.right",
|
systemImage: "chevron.left.forwardslash.chevron.right",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .developer,
|
category: .developer,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "gitlab.com/username",
|
valuePlaceholder: "gitlab.com/username",
|
||||||
@ -404,7 +404,7 @@ extension ContactFieldType {
|
|||||||
id: "stackoverflow",
|
id: "stackoverflow",
|
||||||
displayName: "Stack Overflow",
|
displayName: "Stack Overflow",
|
||||||
systemImage: "text.bubble.fill",
|
systemImage: "text.bubble.fill",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .developer,
|
category: .developer,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "stackoverflow.com/users/id",
|
valuePlaceholder: "stackoverflow.com/users/id",
|
||||||
@ -420,7 +420,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Telegram",
|
displayName: "Telegram",
|
||||||
systemImage: "telegram",
|
systemImage: "telegram",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .messaging,
|
category: .messaging,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "t.me/username",
|
valuePlaceholder: "t.me/username",
|
||||||
@ -433,7 +433,7 @@ extension ContactFieldType {
|
|||||||
id: "whatsapp",
|
id: "whatsapp",
|
||||||
displayName: "WhatsApp",
|
displayName: "WhatsApp",
|
||||||
systemImage: "message.fill",
|
systemImage: "message.fill",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .messaging,
|
category: .messaging,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "+1 555 123 4567",
|
valuePlaceholder: "+1 555 123 4567",
|
||||||
@ -449,7 +449,7 @@ extension ContactFieldType {
|
|||||||
id: "signal",
|
id: "signal",
|
||||||
displayName: "Signal",
|
displayName: "Signal",
|
||||||
systemImage: "bubble.left.fill",
|
systemImage: "bubble.left.fill",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .messaging,
|
category: .messaging,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "+1 555 123 4567",
|
valuePlaceholder: "+1 555 123 4567",
|
||||||
@ -466,7 +466,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Discord",
|
displayName: "Discord",
|
||||||
systemImage: "discord",
|
systemImage: "discord",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .messaging,
|
category: .messaging,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "discord.gg/invite",
|
valuePlaceholder: "discord.gg/invite",
|
||||||
@ -480,7 +480,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Slack",
|
displayName: "Slack",
|
||||||
systemImage: "slack",
|
systemImage: "slack",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .messaging,
|
category: .messaging,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "yourworkspace.slack.com",
|
valuePlaceholder: "yourworkspace.slack.com",
|
||||||
@ -494,7 +494,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Matrix",
|
displayName: "Matrix",
|
||||||
systemImage: "matrix",
|
systemImage: "matrix",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .messaging,
|
category: .messaging,
|
||||||
valueLabel: String(localized: "Username/Link"),
|
valueLabel: String(localized: "Username/Link"),
|
||||||
valuePlaceholder: "@username:matrix.org",
|
valuePlaceholder: "@username:matrix.org",
|
||||||
@ -515,7 +515,7 @@ extension ContactFieldType {
|
|||||||
id: "venmo",
|
id: "venmo",
|
||||||
displayName: "Venmo",
|
displayName: "Venmo",
|
||||||
systemImage: "dollarsign.circle.fill",
|
systemImage: "dollarsign.circle.fill",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .payment,
|
category: .payment,
|
||||||
valueLabel: String(localized: "Username"),
|
valueLabel: String(localized: "Username"),
|
||||||
valuePlaceholder: "@username",
|
valuePlaceholder: "@username",
|
||||||
@ -528,7 +528,7 @@ extension ContactFieldType {
|
|||||||
id: "cashApp",
|
id: "cashApp",
|
||||||
displayName: "Cash App",
|
displayName: "Cash App",
|
||||||
systemImage: "dollarsign.square.fill",
|
systemImage: "dollarsign.square.fill",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .payment,
|
category: .payment,
|
||||||
valueLabel: String(localized: "Username"),
|
valueLabel: String(localized: "Username"),
|
||||||
valuePlaceholder: "$cashtag",
|
valuePlaceholder: "$cashtag",
|
||||||
@ -541,7 +541,7 @@ extension ContactFieldType {
|
|||||||
id: "paypal",
|
id: "paypal",
|
||||||
displayName: "PayPal",
|
displayName: "PayPal",
|
||||||
systemImage: "creditcard.fill",
|
systemImage: "creditcard.fill",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .payment,
|
category: .payment,
|
||||||
valueLabel: String(localized: "Email or Username"),
|
valueLabel: String(localized: "Email or Username"),
|
||||||
valuePlaceholder: "paypal.me/username",
|
valuePlaceholder: "paypal.me/username",
|
||||||
@ -554,7 +554,7 @@ extension ContactFieldType {
|
|||||||
id: "zelle",
|
id: "zelle",
|
||||||
displayName: "Zelle",
|
displayName: "Zelle",
|
||||||
systemImage: "dollarsign.arrow.circlepath",
|
systemImage: "dollarsign.arrow.circlepath",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .payment,
|
category: .payment,
|
||||||
valueLabel: String(localized: "Phone or Email"),
|
valueLabel: String(localized: "Phone or Email"),
|
||||||
valuePlaceholder: "email@example.com",
|
valuePlaceholder: "email@example.com",
|
||||||
@ -570,7 +570,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Patreon",
|
displayName: "Patreon",
|
||||||
systemImage: "patreon.fill",
|
systemImage: "patreon.fill",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .creator,
|
category: .creator,
|
||||||
valueLabel: String(localized: "Profile Link"),
|
valueLabel: String(localized: "Profile Link"),
|
||||||
valuePlaceholder: "patreon.com/username",
|
valuePlaceholder: "patreon.com/username",
|
||||||
@ -584,7 +584,7 @@ extension ContactFieldType {
|
|||||||
displayName: "Ko-fi",
|
displayName: "Ko-fi",
|
||||||
systemImage: "ko-fi",
|
systemImage: "ko-fi",
|
||||||
isCustomSymbol: true,
|
isCustomSymbol: true,
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .creator,
|
category: .creator,
|
||||||
valueLabel: String(localized: "Profile Link"),
|
valueLabel: String(localized: "Profile Link"),
|
||||||
valuePlaceholder: "ko-fi.com/username",
|
valuePlaceholder: "ko-fi.com/username",
|
||||||
@ -599,7 +599,7 @@ extension ContactFieldType {
|
|||||||
id: "calendly",
|
id: "calendly",
|
||||||
displayName: "Calendly",
|
displayName: "Calendly",
|
||||||
systemImage: "calendar",
|
systemImage: "calendar",
|
||||||
iconColor: Color(red: 0.0, green: 0.0, blue: 0.0),
|
iconColor: Color.Text.primary,
|
||||||
category: .scheduling,
|
category: .scheduling,
|
||||||
valueLabel: String(localized: "Calendly Link"),
|
valueLabel: String(localized: "Calendly Link"),
|
||||||
valuePlaceholder: "calendly.com/username",
|
valuePlaceholder: "calendly.com/username",
|
||||||
@ -614,7 +614,7 @@ extension ContactFieldType {
|
|||||||
id: "customLink",
|
id: "customLink",
|
||||||
displayName: String(localized: "Link"),
|
displayName: String(localized: "Link"),
|
||||||
systemImage: "link",
|
systemImage: "link",
|
||||||
iconColor: Color(red: 0.2, green: 0.2, blue: 0.2),
|
iconColor: Color.Text.secondary,
|
||||||
category: .other,
|
category: .other,
|
||||||
valueLabel: String(localized: "URL"),
|
valueLabel: String(localized: "URL"),
|
||||||
valuePlaceholder: "https://example.com",
|
valuePlaceholder: "https://example.com",
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Bedrock
|
||||||
|
|
||||||
extension UIImage {
|
extension UIImage {
|
||||||
/// Extracts dominant colors from the image using pixel sampling.
|
/// Extracts dominant colors from the image using pixel sampling.
|
||||||
/// - Parameter count: Number of colors to extract (default 3)
|
/// - Parameter count: Number of colors to extract (default 3)
|
||||||
/// - Returns: Array of SwiftUI Colors, always includes white and black as fallbacks
|
/// - Returns: Array of SwiftUI Colors, always includes app theme fallback colors
|
||||||
func dominantColors(count: Int = 3) -> [Color] {
|
func dominantColors(count: Int = 3) -> [Color] {
|
||||||
guard let cgImage = self.cgImage else {
|
guard let cgImage = self.cgImage else {
|
||||||
return defaultColors(count: count)
|
return defaultColors(count: count)
|
||||||
@ -18,14 +19,14 @@ extension UIImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a smaller sample size for performance
|
// Create a smaller sample size for performance
|
||||||
let sampleSize = 50
|
let sampleSize = Design.ColorExtraction.sampleSize
|
||||||
|
|
||||||
guard let context = CGContext(
|
guard let context = CGContext(
|
||||||
data: nil,
|
data: nil,
|
||||||
width: sampleSize,
|
width: sampleSize,
|
||||||
height: sampleSize,
|
height: sampleSize,
|
||||||
bitsPerComponent: 8,
|
bitsPerComponent: 8,
|
||||||
bytesPerRow: sampleSize * 4,
|
bytesPerRow: sampleSize * Design.ColorExtraction.rgbaBytesPerPixel,
|
||||||
space: CGColorSpaceCreateDeviceRGB(),
|
space: CGColorSpaceCreateDeviceRGB(),
|
||||||
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
|
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
|
||||||
) else {
|
) else {
|
||||||
@ -38,7 +39,10 @@ extension UIImage {
|
|||||||
return defaultColors(count: count)
|
return defaultColors(count: count)
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = pixelData.bindMemory(to: UInt8.self, capacity: sampleSize * sampleSize * 4)
|
let data = pixelData.bindMemory(
|
||||||
|
to: UInt8.self,
|
||||||
|
capacity: sampleSize * sampleSize * Design.ColorExtraction.rgbaBytesPerPixel
|
||||||
|
)
|
||||||
|
|
||||||
// Collect colors with their frequencies
|
// Collect colors with their frequencies
|
||||||
var colorCounts: [ColorBucket: Int] = [:]
|
var colorCounts: [ColorBucket: Int] = [:]
|
||||||
@ -52,17 +56,18 @@ extension UIImage {
|
|||||||
let a = data[offset + 3]
|
let a = data[offset + 3]
|
||||||
|
|
||||||
// Skip transparent or near-transparent pixels
|
// Skip transparent or near-transparent pixels
|
||||||
guard a > 128 else { continue }
|
guard a > Design.ColorExtraction.alphaVisibilityThreshold else { continue }
|
||||||
|
|
||||||
// Skip near-white and near-black (we'll add those as defaults)
|
// Skip near-white and near-black (we'll add those as defaults)
|
||||||
let brightness = (Int(r) + Int(g) + Int(b)) / 3
|
let brightness = (Int(r) + Int(g) + Int(b)) / 3
|
||||||
guard brightness > 30 && brightness < 225 else { continue }
|
guard brightness > Design.ColorExtraction.minBrightness
|
||||||
|
&& brightness < Design.ColorExtraction.maxBrightness else { continue }
|
||||||
|
|
||||||
// Bucket colors to reduce noise (group similar colors)
|
// Bucket colors to reduce noise (group similar colors)
|
||||||
let bucket = ColorBucket(
|
let bucket = ColorBucket(
|
||||||
r: UInt8((Int(r) / 32) * 32),
|
r: UInt8((Int(r) / Design.ColorExtraction.bucketQuantizationStep) * Design.ColorExtraction.bucketQuantizationStep),
|
||||||
g: UInt8((Int(g) / 32) * 32),
|
g: UInt8((Int(g) / Design.ColorExtraction.bucketQuantizationStep) * Design.ColorExtraction.bucketQuantizationStep),
|
||||||
b: UInt8((Int(b) / 32) * 32)
|
b: UInt8((Int(b) / Design.ColorExtraction.bucketQuantizationStep) * Design.ColorExtraction.bucketQuantizationStep)
|
||||||
)
|
)
|
||||||
|
|
||||||
colorCounts[bucket, default: 0] += 1
|
colorCounts[bucket, default: 0] += 1
|
||||||
@ -77,9 +82,9 @@ extension UIImage {
|
|||||||
// Add top colors, ensuring they're distinct
|
// Add top colors, ensuring they're distinct
|
||||||
for (bucket, _) in sortedColors {
|
for (bucket, _) in sortedColors {
|
||||||
let color = Color(
|
let color = Color(
|
||||||
red: Double(bucket.r) / 255.0,
|
red: Double(bucket.r) / Design.ColorExtraction.rgbDenominator,
|
||||||
green: Double(bucket.g) / 255.0,
|
green: Double(bucket.g) / Design.ColorExtraction.rgbDenominator,
|
||||||
blue: Double(bucket.b) / 255.0
|
blue: Double(bucket.b) / Design.ColorExtraction.rgbDenominator
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if this color is distinct enough from existing ones
|
// Check if this color is distinct enough from existing ones
|
||||||
@ -92,20 +97,20 @@ extension UIImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always include white as first option
|
// Always include high-contrast app fallback colors first.
|
||||||
var result: [Color] = [.white]
|
var result: [Color] = [Color.AppText.inverted]
|
||||||
result.append(contentsOf: extractedColors)
|
result.append(contentsOf: extractedColors)
|
||||||
|
|
||||||
// Add black if we have room
|
// Add primary text color if we have room.
|
||||||
if result.count < count {
|
if result.count < count {
|
||||||
result.append(.black)
|
result.append(Color.Text.primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array(result.prefix(count))
|
return Array(result.prefix(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func defaultColors(count: Int) -> [Color] {
|
private func defaultColors(count: Int) -> [Color] {
|
||||||
let defaults: [Color] = [.white, .black, Color(red: 0.2, green: 0.2, blue: 0.2)]
|
let defaults: [Color] = [Color.AppText.inverted, Color.Text.primary, Color.Text.secondary]
|
||||||
return Array(defaults.prefix(count))
|
return Array(defaults.prefix(count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +127,7 @@ private struct ColorBucket: Hashable {
|
|||||||
|
|
||||||
private extension Color {
|
private extension Color {
|
||||||
/// Checks if two colors are visually similar.
|
/// Checks if two colors are visually similar.
|
||||||
func isClose(to other: Color, threshold: Double = 0.15) -> Bool {
|
func isClose(to other: Color, threshold: Double = Design.ColorExtraction.colorSimilarityThreshold) -> Bool {
|
||||||
guard let selfComponents = self.cgColor?.components,
|
guard let selfComponents = self.cgColor?.components,
|
||||||
let otherComponents = other.cgColor?.components,
|
let otherComponents = other.cgColor?.components,
|
||||||
selfComponents.count >= 3,
|
selfComponents.count >= 3,
|
||||||
@ -137,4 +142,3 @@ private extension Color {
|
|||||||
return rDiff < threshold && gDiff < threshold && bDiff < threshold
|
return rDiff < threshold && gDiff < threshold && bDiff < threshold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ struct FloatingShareButton: View {
|
|||||||
Image(systemName: "qrcode")
|
Image(systemName: "qrcode")
|
||||||
.typography(.title2)
|
.typography(.title2)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
.frame(width: Design.CardSize.floatingButtonSize, height: Design.CardSize.floatingButtonSize)
|
.frame(width: Design.CardSize.floatingButtonSize, height: Design.CardSize.floatingButtonSize)
|
||||||
.background(
|
.background(
|
||||||
Circle()
|
Circle()
|
||||||
|
|||||||
@ -32,6 +32,8 @@ struct CardsHomeView: View {
|
|||||||
}
|
}
|
||||||
.navigationTitle(String.localized("My Cards"))
|
.navigationTitle(String.localized("My Cards"))
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbarBackground(.ultraThinMaterial, for: .navigationBar)
|
||||||
|
.toolbarBackground(.visible, for: .navigationBar)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Button(String.localized("Add Card"), systemImage: "plus") {
|
Button(String.localized("Add Card"), systemImage: "plus") {
|
||||||
|
|||||||
@ -573,7 +573,7 @@ private struct ContactFieldRowView: View {
|
|||||||
.background(Color.AppBackground.base.opacity(0.82))
|
.background(Color.AppBackground.base.opacity(0.82))
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||||
.stroke(Color.white.opacity(0.08), lineWidth: 1)
|
.stroke(Color.Text.tertiary.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin)
|
||||||
)
|
)
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
||||||
.contentShape(.rect)
|
.contentShape(.rect)
|
||||||
|
|||||||
@ -410,8 +410,8 @@ private struct CustomColorSwatch: View {
|
|||||||
if customColor == nil {
|
if customColor == nil {
|
||||||
Image(systemName: "eyedropper")
|
Image(systemName: "eyedropper")
|
||||||
.typography(.caption)
|
.typography(.caption)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
.shadow(color: .black.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusSmall)
|
.shadow(color: Color.AppBackground.base.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusSmall)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: Design.CardSize.colorSwatchSize, height: Design.CardSize.colorSwatchSize)
|
.frame(width: Design.CardSize.colorSwatchSize, height: Design.CardSize.colorSwatchSize)
|
||||||
@ -945,7 +945,7 @@ private struct ContactFieldRowView: View {
|
|||||||
.overlay(
|
.overlay(
|
||||||
field.fieldType.iconImage()
|
field.fieldType.iconImage()
|
||||||
.typography(.title3)
|
.typography(.title3)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
@ -1064,8 +1064,8 @@ private struct PreviewCardButton: View {
|
|||||||
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
|
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
|
||||||
.stroke(
|
.stroke(
|
||||||
colorScheme == .dark
|
colorScheme == .dark
|
||||||
? Color.white.opacity(Design.Opacity.subtle)
|
? Color.AppText.inverted.opacity(Design.Opacity.subtle)
|
||||||
: Color.black.opacity(Design.Opacity.light),
|
: Color.Text.primary.opacity(Design.Opacity.light),
|
||||||
lineWidth: Design.LineWidth.thin
|
lineWidth: Design.LineWidth.thin
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -26,7 +26,7 @@ struct ContactFieldEditorSheet: View {
|
|||||||
fieldType: ContactFieldType,
|
fieldType: ContactFieldType,
|
||||||
initialValue: String = "",
|
initialValue: String = "",
|
||||||
initialTitle: String = "",
|
initialTitle: String = "",
|
||||||
themeColor: Color = Color(red: 0.2, green: 0.2, blue: 0.2),
|
themeColor: Color = Color.Text.secondary,
|
||||||
onSave: @escaping (String, String) -> Void,
|
onSave: @escaping (String, String) -> Void,
|
||||||
onDelete: (() -> Void)? = nil
|
onDelete: (() -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ struct CropGridLines: View {
|
|||||||
HStack(spacing: cropSize.width / 3 - Design.LineWidth.thin) {
|
HStack(spacing: cropSize.width / 3 - Design.LineWidth.thin) {
|
||||||
ForEach(0..<2, id: \.self) { _ in
|
ForEach(0..<2, id: \.self) { _ in
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(Color.white.opacity(Design.Opacity.light))
|
.fill(Color.AppText.inverted.opacity(Design.Opacity.light))
|
||||||
.frame(width: Design.LineWidth.thin, height: cropSize.height)
|
.frame(width: Design.LineWidth.thin, height: cropSize.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -17,7 +17,7 @@ struct CropGridLines: View {
|
|||||||
VStack(spacing: cropSize.height / 3 - Design.LineWidth.thin) {
|
VStack(spacing: cropSize.height / 3 - Design.LineWidth.thin) {
|
||||||
ForEach(0..<2, id: \.self) { _ in
|
ForEach(0..<2, id: \.self) { _ in
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(Color.white.opacity(Design.Opacity.light))
|
.fill(Color.AppText.inverted.opacity(Design.Opacity.light))
|
||||||
.frame(width: cropSize.width, height: Design.LineWidth.thin)
|
.frame(width: cropSize.width, height: Design.LineWidth.thin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ struct CropOverlay: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(Color.black.opacity(Design.Opacity.accent))
|
.fill(Color.AppBackground.base.opacity(Design.Opacity.accent))
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(Color.clear)
|
.fill(Color.clear)
|
||||||
@ -19,7 +19,7 @@ struct CropOverlay: View {
|
|||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.stroke(Color.white, lineWidth: Design.LineWidth.thin)
|
.stroke(Color.AppText.inverted, lineWidth: Design.LineWidth.thin)
|
||||||
.frame(width: cropSize.width, height: cropSize.height)
|
.frame(width: cropSize.width, height: cropSize.height)
|
||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ struct FieldHeaderView: View {
|
|||||||
.overlay(
|
.overlay(
|
||||||
fieldType.iconImage()
|
fieldType.iconImage()
|
||||||
.typography(.title3)
|
.typography(.title3)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(fieldType.displayName)
|
Text(fieldType.displayName)
|
||||||
|
|||||||
@ -89,7 +89,7 @@ struct PhotoCropperSheet: View {
|
|||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
ZStack {
|
ZStack {
|
||||||
// Dark background
|
// Dark background
|
||||||
Color.black.ignoresSafeArea()
|
Color.AppBackground.base.ignoresSafeArea()
|
||||||
|
|
||||||
// Image with gestures
|
// Image with gestures
|
||||||
if let uiImage {
|
if let uiImage {
|
||||||
@ -129,7 +129,7 @@ struct PhotoCropperSheet: View {
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .principal) {
|
ToolbarItem(placement: .principal) {
|
||||||
HStack(spacing: Design.Spacing.xLarge) {
|
HStack(spacing: Design.Spacing.xLarge) {
|
||||||
@ -138,7 +138,7 @@ struct PhotoCropperSheet: View {
|
|||||||
rotate90Left()
|
rotate90Left()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "rotate.left")
|
Image(systemName: "rotate.left")
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset all transforms
|
// Reset all transforms
|
||||||
@ -146,7 +146,7 @@ struct PhotoCropperSheet: View {
|
|||||||
resetTransform()
|
resetTransform()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrow.counterclockwise")
|
Image(systemName: "arrow.counterclockwise")
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aspect ratio picker (only for logo workflow)
|
// Aspect ratio picker (only for logo workflow)
|
||||||
@ -155,7 +155,7 @@ struct PhotoCropperSheet: View {
|
|||||||
showingAspectRatioPicker = true
|
showingAspectRatioPicker = true
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "aspectratio")
|
Image(systemName: "aspectratio")
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ struct PhotoCropperSheet: View {
|
|||||||
rotate90Right()
|
rotate90Right()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "rotate.right")
|
Image(systemName: "rotate.right")
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,7 +173,7 @@ struct PhotoCropperSheet: View {
|
|||||||
cropAndSave()
|
cropAndSave()
|
||||||
}
|
}
|
||||||
.bold()
|
.bold()
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,10 @@ struct ContactRowView: View {
|
|||||||
return trimmed.isEmpty ? String.localized("Shared") : String.localized(trimmed)
|
return trimmed.isEmpty ? String.localized("Shared") : String.localized(trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var chipBackground: Color { Color.AppBackground.accent }
|
||||||
|
private var chipTextColor: Color { Color.Text.primary }
|
||||||
|
private var chipStroke: Color { Color.Text.tertiary.opacity(Design.Opacity.light) }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: Design.Spacing.medium) {
|
HStack(spacing: Design.Spacing.medium) {
|
||||||
ContactAvatarView(contact: contact)
|
ContactAvatarView(contact: contact)
|
||||||
@ -54,10 +58,14 @@ struct ContactRowView: View {
|
|||||||
ForEach(contact.tagList.prefix(2), id: \.self) { tag in
|
ForEach(contact.tagList.prefix(2), id: \.self) { tag in
|
||||||
Text(tag)
|
Text(tag)
|
||||||
.typography(.caption2)
|
.typography(.caption2)
|
||||||
.foregroundStyle(Color.Text.secondary)
|
.foregroundStyle(chipTextColor)
|
||||||
.padding(.horizontal, Design.Spacing.xSmall)
|
.padding(.horizontal, Design.Spacing.xSmall)
|
||||||
.padding(.vertical, Design.Spacing.xxSmall)
|
.padding(.vertical, Design.Spacing.xxSmall)
|
||||||
.background(Color.AppBackground.accent.opacity(0.5))
|
.background(chipBackground)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: Design.CornerRadius.small)
|
||||||
|
.stroke(chipStroke, lineWidth: Design.LineWidth.thin)
|
||||||
|
)
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.small))
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.small))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,8 @@ struct ContactsListView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var showsJumpIndex: Bool {
|
private var showsJumpIndex: Bool {
|
||||||
sortedContacts.count >= 20 && sectionTitles.count >= 6
|
sortedContacts.count >= Design.Contacts.sectionIndexMinContacts
|
||||||
|
&& sectionTitles.count >= Design.Contacts.sectionIndexMinSections
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -67,17 +68,20 @@ struct ContactsListView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if showsJumpIndex {
|
if showsJumpIndex {
|
||||||
VStack(spacing: 2) {
|
VStack(spacing: Design.Contacts.sectionIndexLetterSpacing) {
|
||||||
ForEach(sectionTitles, id: \.self) { title in
|
ForEach(sectionTitles, id: \.self) { title in
|
||||||
Button {
|
Button {
|
||||||
withAnimation(.easeInOut(duration: 0.2)) {
|
withAnimation(.easeInOut(duration: Design.Contacts.sectionIndexScrollAnimationDuration)) {
|
||||||
proxy.scrollTo(title, anchor: .top)
|
proxy.scrollTo(title, anchor: .top)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Text(title)
|
Text(title)
|
||||||
.typography(.caption2)
|
.typography(.caption2)
|
||||||
.foregroundStyle(Color.Text.secondary)
|
.foregroundStyle(Color.Text.secondary)
|
||||||
.frame(width: 16, height: 12)
|
.frame(
|
||||||
|
width: Design.Contacts.sectionIndexLetterWidth,
|
||||||
|
height: Design.Contacts.sectionIndexLetterHeight
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -334,7 +334,7 @@ private struct ContactBannerView: View {
|
|||||||
.overlay(alignment: .bottomTrailing) {
|
.overlay(alignment: .bottomTrailing) {
|
||||||
Image(systemName: "pencil")
|
Image(systemName: "pencil")
|
||||||
.typography(.caption)
|
.typography(.caption)
|
||||||
.foregroundStyle(Color.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
.padding(Design.Spacing.xSmall)
|
.padding(Design.Spacing.xSmall)
|
||||||
.background(Color.CardPalette.coral)
|
.background(Color.CardPalette.coral)
|
||||||
.clipShape(.circle)
|
.clipShape(.circle)
|
||||||
@ -383,7 +383,7 @@ private struct ContactProfileAvatarView: View {
|
|||||||
.background(Color.CardPalette.coral)
|
.background(Color.CardPalette.coral)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: 92, height: 92)
|
.frame(width: Design.Contacts.detailHeaderAvatarSize, height: Design.Contacts.detailHeaderAvatarSize)
|
||||||
.clipShape(.circle)
|
.clipShape(.circle)
|
||||||
.overlay(Circle().stroke(Color.AppBackground.elevated, lineWidth: Design.LineWidth.thick))
|
.overlay(Circle().stroke(Color.AppBackground.elevated, lineWidth: Design.LineWidth.thick))
|
||||||
.shadow(
|
.shadow(
|
||||||
@ -451,7 +451,7 @@ private struct ContactInfoCard: View {
|
|||||||
|
|
||||||
if index < allContactFields.count - 1 || hasLegacyFields {
|
if index < allContactFields.count - 1 || hasLegacyFields {
|
||||||
Divider()
|
Divider()
|
||||||
.padding(.leading, 28 + Design.Spacing.medium)
|
.padding(.leading, Design.Contacts.detailFieldIconWidth + Design.Spacing.medium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,7 +466,7 @@ private struct ContactInfoCard: View {
|
|||||||
|
|
||||||
if !contact.email.isEmpty && contact.emailAddresses.isEmpty {
|
if !contact.email.isEmpty && contact.emailAddresses.isEmpty {
|
||||||
Divider()
|
Divider()
|
||||||
.padding(.leading, 28 + Design.Spacing.medium)
|
.padding(.leading, Design.Contacts.detailFieldIconWidth + Design.Spacing.medium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,14 +501,14 @@ private struct ContactFieldInfoRow: View {
|
|||||||
field.iconImage()
|
field.iconImage()
|
||||||
.typography(.subheading)
|
.typography(.subheading)
|
||||||
.foregroundStyle(Color.CardPalette.coral)
|
.foregroundStyle(Color.CardPalette.coral)
|
||||||
.frame(width: 28)
|
.frame(width: Design.Contacts.detailFieldIconWidth)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
|
||||||
Text(field.displayValue)
|
Text(field.displayValue)
|
||||||
.typography(.subheading)
|
.typography(.subheading)
|
||||||
.foregroundStyle(Color.Text.primary)
|
.foregroundStyle(Color.Text.primary)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.lineLimit(2)
|
.lineLimit(Design.Contacts.notePreviewLineLimit)
|
||||||
|
|
||||||
Text(field.title.isEmpty ? field.displayName : field.title)
|
Text(field.title.isEmpty ? field.displayName : field.title)
|
||||||
.typography(.caption)
|
.typography(.caption)
|
||||||
@ -539,14 +539,14 @@ private struct ContactInfoRow: View {
|
|||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.typography(.subheading)
|
.typography(.subheading)
|
||||||
.foregroundStyle(Color.CardPalette.coral)
|
.foregroundStyle(Color.CardPalette.coral)
|
||||||
.frame(width: 28)
|
.frame(width: Design.Contacts.detailFieldIconWidth)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
|
||||||
Text(value)
|
Text(value)
|
||||||
.typography(.subheading)
|
.typography(.subheading)
|
||||||
.foregroundStyle(Color.Text.primary)
|
.foregroundStyle(Color.Text.primary)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.lineLimit(2)
|
.lineLimit(Design.Contacts.notePreviewLineLimit)
|
||||||
Text(label)
|
Text(label)
|
||||||
.typography(.caption)
|
.typography(.caption)
|
||||||
.foregroundStyle(Color.Text.secondary)
|
.foregroundStyle(Color.Text.secondary)
|
||||||
|
|||||||
@ -7,7 +7,7 @@ struct ScannerOverlayView: View {
|
|||||||
let size = min(geometry.size.width, geometry.size.height) * 0.7
|
let size = min(geometry.size.width, geometry.size.height) * 0.7
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.black.opacity(Design.Opacity.medium)
|
Color.AppBackground.base.opacity(Design.Opacity.medium)
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
|
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
@ -27,7 +27,7 @@ struct ScannerOverlayView: View {
|
|||||||
.typography(.heading)
|
.typography(.heading)
|
||||||
.foregroundStyle(Color.Text.inverted)
|
.foregroundStyle(Color.Text.inverted)
|
||||||
.padding(Design.Spacing.medium)
|
.padding(Design.Spacing.medium)
|
||||||
.background(Color.black.opacity(Design.Opacity.medium))
|
.background(Color.AppBackground.base.opacity(Design.Opacity.medium))
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
||||||
.padding(.bottom, Design.Spacing.xxxLarge)
|
.padding(.bottom, Design.Spacing.xxxLarge)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,7 +101,7 @@ private struct QRCodeSection: View {
|
|||||||
QRCodeView(payload: card.vCardPayload)
|
QRCodeView(payload: card.vCardPayload)
|
||||||
.frame(width: Design.CardSize.qrSizeLarge, height: Design.CardSize.qrSizeLarge)
|
.frame(width: Design.CardSize.qrSizeLarge, height: Design.CardSize.qrSizeLarge)
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
.background(Color.white)
|
.background(Color.ShareSheet.rowBackground)
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
||||||
|
|
||||||
// Instruction text
|
// Instruction text
|
||||||
@ -146,7 +146,7 @@ private struct AppClipSection: View {
|
|||||||
QRCodeView(payload: result.appClipURL.absoluteString)
|
QRCodeView(payload: result.appClipURL.absoluteString)
|
||||||
.frame(width: Design.CardSize.qrSizeLarge, height: Design.CardSize.qrSizeLarge)
|
.frame(width: Design.CardSize.qrSizeLarge, height: Design.CardSize.qrSizeLarge)
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
.background(Color.white)
|
.background(Color.ShareSheet.rowBackground)
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
||||||
|
|
||||||
// Expiration notice
|
// Expiration notice
|
||||||
@ -190,7 +190,7 @@ private struct AppClipSection: View {
|
|||||||
if let error = appClipState.errorMessage {
|
if let error = appClipState.errorMessage {
|
||||||
Text(error)
|
Text(error)
|
||||||
.typography(.caption)
|
.typography(.caption)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(Color.Accent.red)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import Bedrock
|
|||||||
/// Displays a vertical list of added contact fields with tap to edit and drag to reorder
|
/// Displays a vertical list of added contact fields with tap to edit and drag to reorder
|
||||||
struct AddedContactFieldsView: View {
|
struct AddedContactFieldsView: View {
|
||||||
@Binding var fields: [AddedContactField]
|
@Binding var fields: [AddedContactField]
|
||||||
var themeColor: Color = Color(red: 0.2, green: 0.2, blue: 0.2)
|
var themeColor: Color = Color.Text.secondary
|
||||||
let onEdit: (AddedContactField) -> Void
|
let onEdit: (AddedContactField) -> Void
|
||||||
|
|
||||||
@State private var draggingField: AddedContactField?
|
@State private var draggingField: AddedContactField?
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import Bedrock
|
|||||||
|
|
||||||
/// Grid view for selecting contact field types to add
|
/// Grid view for selecting contact field types to add
|
||||||
struct ContactFieldPickerView: View {
|
struct ContactFieldPickerView: View {
|
||||||
var themeColor: Color = Color(red: 0.2, green: 0.2, blue: 0.2)
|
var themeColor: Color = Color.Text.secondary
|
||||||
let onSelect: (ContactFieldType) -> Void
|
let onSelect: (ContactFieldType) -> Void
|
||||||
|
|
||||||
private let columns = Array(repeating: GridItem(.flexible(), spacing: Design.Spacing.medium), count: 3)
|
private let columns = Array(repeating: GridItem(.flexible(), spacing: Design.Spacing.medium), count: 3)
|
||||||
|
|||||||
@ -21,7 +21,7 @@ struct FieldRow: View {
|
|||||||
.overlay(
|
.overlay(
|
||||||
field.fieldType.iconImage()
|
field.fieldType.iconImage()
|
||||||
.typography(.title3)
|
.typography(.title3)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
)
|
)
|
||||||
|
|
||||||
Button(action: onTap) {
|
Button(action: onTap) {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ struct FieldRowPreview: View {
|
|||||||
.overlay(
|
.overlay(
|
||||||
field.fieldType.iconImage()
|
field.fieldType.iconImage()
|
||||||
.typography(.title3)
|
.typography(.title3)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
)
|
)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ struct FieldTypeButton: View {
|
|||||||
.overlay(
|
.overlay(
|
||||||
fieldType.iconImage()
|
fieldType.iconImage()
|
||||||
.typography(.title3)
|
.typography(.title3)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(fieldType.displayName)
|
Text(fieldType.displayName)
|
||||||
|
|||||||
@ -89,10 +89,10 @@ struct HeaderLayoutPickerView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Text("Confirm layout")
|
Text("Confirm layout")
|
||||||
.typography(.heading)
|
.typography(.heading)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.vertical, Design.Spacing.large)
|
.padding(.vertical, Design.Spacing.large)
|
||||||
.background(.black)
|
.background(Color.Accent.ink)
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
||||||
}
|
}
|
||||||
.padding(.horizontal, Design.Spacing.xLarge)
|
.padding(.horizontal, Design.Spacing.xLarge)
|
||||||
|
|||||||
@ -17,7 +17,7 @@ struct LayoutBadge: View {
|
|||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
.padding(.vertical, Design.Spacing.xSmall)
|
.padding(.vertical, Design.Spacing.xSmall)
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(Color.AppText.inverted)
|
||||||
.clipShape(.capsule)
|
.clipShape(.capsule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user