3
6
keyBoard/Assets.xcassets/Test/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
22
keyBoard/Assets.xcassets/Test/key_123.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_123@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_123@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_123.imageset/key_123@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_123.imageset/key_123@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
22
keyBoard/Assets.xcassets/Test/key_a.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_a@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_a@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_a.imageset/key_a@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_a.imageset/key_a@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
22
keyBoard/Assets.xcassets/Test/key_ai.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_ai@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_ai@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_ai.imageset/key_ai@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_ai.imageset/key_ai@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 49 KiB |
22
keyBoard/Assets.xcassets/Test/key_b.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_b@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_b@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_b.imageset/key_b@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_b.imageset/key_b@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
22
keyBoard/Assets.xcassets/Test/key_c.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_c@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_c@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_c.imageset/key_c@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_c.imageset/key_c@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
22
keyBoard/Assets.xcassets/Test/key_d.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_d@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_d@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_d.imageset/key_d@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_d.imageset/key_d@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
22
keyBoard/Assets.xcassets/Test/key_del.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_del@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_del@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_del.imageset/key_del@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_del.imageset/key_del@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 45 KiB |
22
keyBoard/Assets.xcassets/Test/key_e.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_e@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_e@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_e.imageset/key_e@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_e.imageset/key_e@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
22
keyBoard/Assets.xcassets/Test/key_f.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_f@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_f@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_f.imageset/key_f@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_f.imageset/key_f@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
22
keyBoard/Assets.xcassets/Test/key_g.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_g@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_g@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_g.imageset/key_g@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_g.imageset/key_g@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
22
keyBoard/Assets.xcassets/Test/key_h.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_h@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_h@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_h.imageset/key_h@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_h.imageset/key_h@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 21 KiB |
22
keyBoard/Assets.xcassets/Test/key_i.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_i@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_i@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_i.imageset/key_i@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_i.imageset/key_i@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 33 KiB |
22
keyBoard/Assets.xcassets/Test/key_j.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_j@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_j@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_j.imageset/key_j@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_j.imageset/key_j@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 34 KiB |
22
keyBoard/Assets.xcassets/Test/key_k.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_k@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_k@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_k.imageset/key_k@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_k.imageset/key_k@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
22
keyBoard/Assets.xcassets/Test/key_l.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_l@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_l@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_l.imageset/key_l@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_l.imageset/key_l@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
22
keyBoard/Assets.xcassets/Test/key_m.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_m@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_m@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_m.imageset/key_m@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_m.imageset/key_m@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
22
keyBoard/Assets.xcassets/Test/key_n.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_n@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_n@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_n.imageset/key_n@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_n.imageset/key_n@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
22
keyBoard/Assets.xcassets/Test/key_o.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_o@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_o@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_o.imageset/key_o@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_o.imageset/key_o@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 34 KiB |
22
keyBoard/Assets.xcassets/Test/key_p.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_p@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_p@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_p.imageset/key_p@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_p.imageset/key_p@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 33 KiB |
22
keyBoard/Assets.xcassets/Test/key_q.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_q@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_q@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_q.imageset/key_q@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_q.imageset/key_q@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
22
keyBoard/Assets.xcassets/Test/key_r.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_r@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_r@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_r.imageset/key_r@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_r.imageset/key_r@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
22
keyBoard/Assets.xcassets/Test/key_s.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_s@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_s@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_s.imageset/key_s@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_s.imageset/key_s@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
22
keyBoard/Assets.xcassets/Test/key_send.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_send@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_send@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_send.imageset/key_send@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_send.imageset/key_send@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 78 KiB |
22
keyBoard/Assets.xcassets/Test/key_space.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_space@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_space@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_space.imageset/key_space@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_space.imageset/key_space@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 108 KiB |
22
keyBoard/Assets.xcassets/Test/key_t.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_t@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_t@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_t.imageset/key_t@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_t.imageset/key_t@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 34 KiB |
22
keyBoard/Assets.xcassets/Test/key_u.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_u@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_u@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_u.imageset/key_u@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_u.imageset/key_u@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
22
keyBoard/Assets.xcassets/Test/key_up.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_up@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_up@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_up.imageset/key_up@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_up.imageset/key_up@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 51 KiB |
22
keyBoard/Assets.xcassets/Test/key_v.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_v@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_v@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_v.imageset/key_v@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_v.imageset/key_v@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
22
keyBoard/Assets.xcassets/Test/key_w.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_w@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_w@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_w.imageset/key_w@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_w.imageset/key_w@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 33 KiB |
22
keyBoard/Assets.xcassets/Test/key_x.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_x@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_x@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_x.imageset/key_x@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_x.imageset/key_x@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 31 KiB |
22
keyBoard/Assets.xcassets/Test/key_y.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_y@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_y@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_y.imageset/key_y@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_y.imageset/key_y@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
22
keyBoard/Assets.xcassets/Test/key_z.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_z@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_z@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/Test/key_z.imageset/key_z@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
keyBoard/Assets.xcassets/Test/key_z.imageset/key_z@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
@@ -7,6 +7,8 @@
|
||||
#import "KBNetworkManager.h"
|
||||
#import "KBSkinManager.h"
|
||||
#import "KBHUD.h"
|
||||
#import "KBConfig.h"
|
||||
#import "KBSkinService.h"
|
||||
|
||||
@interface KBSkinCell : UITableViewCell
|
||||
@property (nonatomic, strong) UIButton *applyBtn;
|
||||
@@ -34,7 +36,7 @@
|
||||
|
||||
@interface KBSkinCenterVC () <UITableViewDelegate, UITableViewDataSource>
|
||||
@property (nonatomic, strong) UITableView *tableView;
|
||||
@property (nonatomic, copy) NSArray<NSDictionary *> *skins; // id, name, img(url relative to KB_BASE_URL)
|
||||
@property (nonatomic, copy) NSArray<NSDictionary *> *skins; // 每个元素即一套皮肤的 JSON(与后端约定格式一致)
|
||||
@end
|
||||
|
||||
@implementation KBSkinCenterVC
|
||||
@@ -43,12 +45,103 @@
|
||||
[super viewDidLoad];
|
||||
// self.title = KBLocalized(@"皮肤中心");
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
// 绝对 URL 的测试皮肤图片(无需 KB_BASE_URL)。
|
||||
// 调整为较小尺寸,避免下载与存储过大(手机上预览已足够清晰)。
|
||||
|
||||
// 本地测试用的皮肤数据:结构与后端最终返回的 JSON 一致。
|
||||
// 说明:
|
||||
// - background_image:背景大图 URL(此处仍使用 picsum 测试)
|
||||
// - 颜色字段:background/key_bg/key_text/key_highlight/accent 与 KBSkinManager.applyThemeFromJSON 一致
|
||||
// - hidden_keys:需要隐藏文字的按键 identifier
|
||||
// - key_icons:
|
||||
// * 当 KB_SKIN_ICON_USE_REMOTE==0 时,value 写本地 Assets 名称(如 "kb_space_melon")
|
||||
// * 当 KB_SKIN_ICON_USE_REMOTE==1 时,value 写远程图标 URL(如 "https://.../icons/space.png")
|
||||
self.skins = @[
|
||||
@{ @"id": @"aurora", @"name": KBLocalized(@"极光"), @"img": @"https://picsum.photos/id/1018/800/450.jpg" },
|
||||
@{ @"id": @"alps", @"name": KBLocalized(@"雪山"), @"img": @"https://picsum.photos/id/1016/800/450.jpg" },
|
||||
@{ @"id": @"lake", @"name": KBLocalized(@"湖面"), @"img": @"https://picsum.photos/id/1039/800/450.jpg" },
|
||||
@{
|
||||
@"id": @"melon",
|
||||
@"name": KBLocalized(@"蜜瓜主题"),
|
||||
@"background_image": @"https://picsum.photos/id/1018/800/450.jpg",
|
||||
@"background": @"#F5FFE8",
|
||||
@"key_bg": @"#FFFFFF",
|
||||
@"key_text": @"#4A4A4A",
|
||||
@"key_highlight": @"#D9F4C4",
|
||||
@"accent": @"#A4D68A",
|
||||
@"hidden_keys": @[
|
||||
@"letter_q",@"letter_w",@"letter_e",@"letter_r",@"letter_t",
|
||||
@"letter_y",@"letter_u",@"letter_i",@"letter_o",@"letter_p",
|
||||
@"letter_a",@"letter_s",@"letter_d",@"letter_f",@"letter_g",
|
||||
@"letter_h",@"letter_j",@"letter_k",@"letter_l",
|
||||
@"letter_z",@"letter_x",@"letter_c",@"letter_v",
|
||||
@"letter_b",@"letter_n",@"letter_m",
|
||||
@"space"
|
||||
],
|
||||
// 默认假设本地测试:这里的值写 Assets 名称。
|
||||
// 如果开启远程图标模式,可改为实际的图标 URL。
|
||||
@"key_icons": @{
|
||||
// 字母键:大小写共用一套本地图(演示用)
|
||||
// 若后续需要不同图,只需改为 *_lower / *_upper 对应不同资源名即可。
|
||||
@"letter_q_lower": @"key_q",
|
||||
@"letter_q_upper": @"key_q",
|
||||
@"letter_w_lower": @"key_w",
|
||||
@"letter_w_upper": @"key_w",
|
||||
@"letter_e_lower": @"key_e",
|
||||
@"letter_e_upper": @"key_e",
|
||||
@"letter_r_lower": @"key_r",
|
||||
@"letter_r_upper": @"key_r",
|
||||
@"letter_t_lower": @"key_t",
|
||||
@"letter_t_upper": @"key_t",
|
||||
@"letter_y_lower": @"key_y",
|
||||
@"letter_y_upper": @"key_y",
|
||||
@"letter_u_lower": @"key_u",
|
||||
@"letter_u_upper": @"key_u",
|
||||
@"letter_i_lower": @"key_i",
|
||||
@"letter_i_upper": @"key_i",
|
||||
@"letter_o_lower": @"key_o",
|
||||
@"letter_o_upper": @"key_o",
|
||||
@"letter_p_lower": @"key_p",
|
||||
@"letter_p_upper": @"key_p",
|
||||
|
||||
@"letter_a_lower": @"key_a",
|
||||
@"letter_a_upper": @"key_a",
|
||||
@"letter_s_lower": @"key_s",
|
||||
@"letter_s_upper": @"key_s",
|
||||
@"letter_d_lower": @"key_d",
|
||||
@"letter_d_upper": @"key_d",
|
||||
@"letter_f_lower": @"key_f",
|
||||
@"letter_f_upper": @"key_f",
|
||||
@"letter_g_lower": @"key_g",
|
||||
@"letter_g_upper": @"key_g",
|
||||
@"letter_h_lower": @"key_h",
|
||||
@"letter_h_upper": @"key_h",
|
||||
@"letter_j_lower": @"key_j",
|
||||
@"letter_j_upper": @"key_j",
|
||||
@"letter_k_lower": @"key_k",
|
||||
@"letter_k_upper": @"key_k",
|
||||
@"letter_l_lower": @"key_l",
|
||||
@"letter_l_upper": @"key_l",
|
||||
|
||||
@"letter_z_lower": @"key_z",
|
||||
@"letter_z_upper": @"key_z",
|
||||
@"letter_x_lower": @"key_x",
|
||||
@"letter_x_upper": @"key_x",
|
||||
@"letter_c_lower": @"key_c",
|
||||
@"letter_c_upper": @"key_c",
|
||||
@"letter_v_lower": @"key_v",
|
||||
@"letter_v_upper": @"key_v",
|
||||
@"letter_b_lower": @"key_b",
|
||||
@"letter_b_upper": @"key_b",
|
||||
@"letter_n_lower": @"key_n",
|
||||
@"letter_n_upper": @"key_n",
|
||||
@"letter_m_lower": @"key_m",
|
||||
@"letter_m_upper": @"key_m",
|
||||
|
||||
// 功能键(无大小写变体)
|
||||
@"space": @"key_space", // 空格键
|
||||
@"backspace": @"key_del", // 删除键
|
||||
@"shift": @"key_up", // Shift(上箭头)
|
||||
@"mode_123": @"key_123", // 字母面板左下角 "123"
|
||||
@"ai": @"key_ai", // 自定义 AI 键
|
||||
@"return": @"key_send" // 发送/换行键
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, KB_NAV_TOTAL_HEIGHT, KB_SCREEN_WIDTH, KB_SCREEN_HEIGHT - KB_NAV_TOTAL_HEIGHT) style:UITableViewStyleInsetGrouped];
|
||||
@@ -78,55 +171,8 @@
|
||||
NSInteger idx = sender.tag;
|
||||
if (idx < 0 || idx >= self.skins.count) return;
|
||||
NSDictionary *skin = self.skins[idx];
|
||||
NSString *path = skin[@"img"] ?: @""; // 相对 KB_BASE_URL
|
||||
|
||||
// 下载图片数据(非 JSON 将以 NSData 返回)
|
||||
[[KBNetworkManager shared] GET:path parameters:nil headers:nil completion:^(id jsonOrData, NSURLResponse *response, NSError *error) {
|
||||
NSData *data = ([jsonOrData isKindOfClass:NSData.class] ? (NSData *)jsonOrData : nil);
|
||||
// 尝试压缩尺寸,避免 Keychain 过大:将宽度限制到 1500px
|
||||
if (data && data.length > 0) {
|
||||
UIImage *img = [UIImage imageWithData:data];
|
||||
if (img) {
|
||||
CGFloat maxW = 1500.0;
|
||||
if (img.size.width > maxW) {
|
||||
CGFloat scale = maxW / img.size.width;
|
||||
CGSize newSize = CGSizeMake(maxW, floor(img.size.height * scale));
|
||||
UIGraphicsBeginImageContextWithOptions(newSize, YES, 1.0);
|
||||
[img drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
|
||||
UIImage *resized = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
img = resized ?: img;
|
||||
}
|
||||
data = UIImageJPEGRepresentation(img, 0.85) ?: data; // 压成 JPEG
|
||||
}
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSData *payload = data;
|
||||
if (payload.length == 0) {
|
||||
// 兜底:生成一张简单渐变图片
|
||||
CGSize size = CGSizeMake(1200, 600);
|
||||
UIGraphicsBeginImageContextWithOptions(size, YES, 1.0);
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
UIColor *c1 = [UIColor colorWithRed:0.76 green:0.91 blue:0.86 alpha:1];
|
||||
UIColor *c2 = [UIColor colorWithRed:0.93 green:0.97 blue:0.91 alpha:1];
|
||||
if ([skin[@"id"] hasPrefix:@"dark"]) {
|
||||
c1 = [UIColor colorWithRed:0.1 green:0.12 blue:0.16 alpha:1];
|
||||
c2 = [UIColor colorWithRed:0.22 green:0.24 blue:0.28 alpha:1];
|
||||
}
|
||||
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
|
||||
NSArray *colors = @[(__bridge id)c1.CGColor, (__bridge id)c2.CGColor];
|
||||
CGFloat locs[] = {0,1};
|
||||
CGGradientRef grad = CGGradientCreateWithColors(space, (__bridge CFArrayRef)colors, locs);
|
||||
CGContextDrawLinearGradient(ctx, grad, CGPointZero, CGPointMake(size.width, size.height), 0);
|
||||
CGGradientRelease(grad); CGColorSpaceRelease(space);
|
||||
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
payload = UIImageJPEGRepresentation(img, 0.9);
|
||||
}
|
||||
BOOL ok = (payload.length > 0) ? [[KBSkinManager shared] applyImageSkinWithData:payload skinId:skin[@"id"] name:skin[@"name"]] : NO;
|
||||
[KBHUD showInfo:(ok ? KBLocalized(@"已应用,切到键盘查看") : KBLocalized(@"应用失败"))];
|
||||
});
|
||||
}];
|
||||
if (!skin) return;
|
||||
[[KBSkinService shared] applySkinWithJSON:skin fromViewController:self completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
34
keyBoard/Class/Manager/KBSkinService.h
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// KBSkinService.h
|
||||
// keyBoard
|
||||
//
|
||||
// 统一的键盘皮肤下载/应用服务。
|
||||
// - App 端调用,一处封装本地图标导出 / 网络图标下载 / 背景图下载逻辑
|
||||
// - 内部会检查键盘是否开启完全访问,并在不足时给出提示或引导
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void(^KBSkinApplyCompletion)(BOOL success);
|
||||
|
||||
/// 皮肤下载与应用服务(仅主 App 使用)
|
||||
@interface KBSkinService : NSObject
|
||||
|
||||
+ (instancetype)shared;
|
||||
|
||||
/// 根据后端返回的皮肤 JSON 下载并应用皮肤。
|
||||
///
|
||||
/// @param skinJSON 与后端约定的皮肤结构(包含 id/name/background_image/hidden_keys/key_icons 等)
|
||||
/// @param presenting 用于弹出“键盘权限引导页”的控制器,可为 nil
|
||||
/// @param completion 应用完成回调(下载/写入全部结束后调用,success 表示是否成功)
|
||||
- (void)applySkinWithJSON:(NSDictionary *)skinJSON
|
||||
fromViewController:(nullable UIViewController *)presenting
|
||||
completion:(nullable KBSkinApplyCompletion)completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
237
keyBoard/Class/Manager/KBSkinService.m
Normal file
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// KBSkinService.m
|
||||
// keyBoard
|
||||
//
|
||||
|
||||
#import "KBSkinService.h"
|
||||
|
||||
#import "KBSkinManager.h"
|
||||
#import "KBConfig.h"
|
||||
#import "KBKeyboardPermissionManager.h"
|
||||
#import "KBNetworkManager.h"
|
||||
#import "KBHUD.h"
|
||||
|
||||
@implementation KBSkinService
|
||||
|
||||
+ (instancetype)shared {
|
||||
static KBSkinService *s; static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{ s = [KBSkinService new]; });
|
||||
return s;
|
||||
}
|
||||
|
||||
- (void)applySkinWithJSON:(NSDictionary *)skinJSON
|
||||
fromViewController:(UIViewController *)presenting
|
||||
completion:(KBSkinApplyCompletion)completion {
|
||||
if (skinJSON.count == 0) {
|
||||
if (completion) completion(NO);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 点击应用皮肤时,检查键盘启用 & 完全访问状态,并尽量给出友好提示。
|
||||
KBKeyboardPermissionManager *perm = [KBKeyboardPermissionManager shared];
|
||||
BOOL enabled = [perm isKeyboardEnabled];
|
||||
KBFARecord fa = [perm lastKnownFullAccess];
|
||||
BOOL hasFullAccess = (fa == KBFARecordGranted);
|
||||
|
||||
if (!enabled || !hasFullAccess) {
|
||||
// 引导页(内部有自己的展示策略,避免过度打扰)
|
||||
[perm presentPermissionIfNeededFrom:presenting];
|
||||
|
||||
// 简单提示:皮肤可以应用,但未开启完全访问时扩展无法读取 App Group 中的图片。
|
||||
[KBHUD showInfo:KBLocalized(@"皮肤已应用,键盘需开启“允许完全访问”后才能显示图片")];
|
||||
}
|
||||
|
||||
#if KB_SKIN_ICON_USE_REMOTE
|
||||
[self kb_applySkinUsingRemoteIcons:skinJSON completion:completion];
|
||||
#else
|
||||
[self kb_applySkinUsingLocalIcons:skinJSON completion:completion];
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - Internal helpers
|
||||
|
||||
/// 本地模式:key_icons 的 value 为 Assets 名称(主 App bundle 内),导出到 App Group 后再写入主题。
|
||||
- (void)kb_applySkinUsingLocalIcons:(NSDictionary *)skin completion:(KBSkinApplyCompletion)completion {
|
||||
NSString *skinId = skin[@"id"] ?: @"local";
|
||||
NSString *name = skin[@"name"] ?: skinId;
|
||||
|
||||
NSDictionary *iconNames = [skin[@"key_icons"] isKindOfClass:NSDictionary.class] ? skin[@"key_icons"] : @{};
|
||||
NSMutableDictionary<NSString *, NSString *> *iconPathMap = [NSMutableDictionary dictionary];
|
||||
|
||||
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
|
||||
if (containerURL && iconNames.count > 0) {
|
||||
NSString *skinsRoot = [containerURL.path stringByAppendingPathComponent:@"Skins"];
|
||||
NSString *skinRoot = [skinsRoot stringByAppendingPathComponent:skinId];
|
||||
NSString *iconsDir = [skinRoot stringByAppendingPathComponent:@"icons"];
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:iconsDir
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:NULL];
|
||||
|
||||
[iconNames enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSString *imageName, BOOL *stop) {
|
||||
if (![imageName isKindOfClass:NSString.class] || imageName.length == 0) return;
|
||||
UIImage *img = [UIImage imageNamed:imageName];
|
||||
if (!img) return;
|
||||
NSData *data = UIImagePNGRepresentation(img);
|
||||
if (data.length == 0) return;
|
||||
NSString *fileName = [NSString stringWithFormat:@"%@.png", identifier];
|
||||
NSString *fullPath = [iconsDir stringByAppendingPathComponent:fileName];
|
||||
if ([data writeToFile:fullPath atomically:YES]) {
|
||||
NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@", skinId, fileName];
|
||||
iconPathMap[identifier] = relative;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
NSMutableDictionary *themeJSON = [skin mutableCopy];
|
||||
if (iconPathMap.count > 0) {
|
||||
themeJSON[@"key_icons"] = iconPathMap.copy; // value 改为 App Group 相对路径
|
||||
}
|
||||
|
||||
// 先应用颜色 / hidden_keys / key_icons(此时为相对路径,扩展从 App Group 读取)
|
||||
BOOL themeOK = [[KBSkinManager shared] applyThemeFromJSON:themeJSON];
|
||||
|
||||
// 再处理背景图片(background_image),与原逻辑一致
|
||||
NSString *bgURL = skin[@"background_image"] ?: @"";
|
||||
if (bgURL.length == 0) {
|
||||
if (completion) completion(themeOK);
|
||||
if (themeOK) {
|
||||
[KBHUD showInfo:KBLocalized(@"已应用皮肤(无背景图)")];
|
||||
} else {
|
||||
[KBHUD showInfo:KBLocalized(@"应用皮肤失败")];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[[KBNetworkManager shared] GET:bgURL parameters:nil headers:nil completion:^(id jsonOrData, NSURLResponse *response, NSError *error) {
|
||||
NSData *data = ([jsonOrData isKindOfClass:NSData.class] ? (NSData *)jsonOrData : nil);
|
||||
// 压缩尺寸,避免 Keychain 过大
|
||||
if (data && data.length > 0) {
|
||||
UIImage *img = [UIImage imageWithData:data];
|
||||
if (img) {
|
||||
CGFloat maxW = 1500.0;
|
||||
if (img.size.width > maxW) {
|
||||
CGFloat scale = maxW / img.size.width;
|
||||
CGSize newSize = CGSizeMake(maxW, floor(img.size.height * scale));
|
||||
UIGraphicsBeginImageContextWithOptions(newSize, YES, 1.0);
|
||||
[img drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
|
||||
UIImage *resized = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
img = resized ?: img;
|
||||
}
|
||||
data = UIImageJPEGRepresentation(img, 0.85) ?: data;
|
||||
}
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSData *payload = data;
|
||||
if (payload.length == 0) {
|
||||
// 兜底:生成一张简单渐变图片
|
||||
CGSize size = CGSizeMake(1200, 600);
|
||||
UIGraphicsBeginImageContextWithOptions(size, YES, 1.0);
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
UIColor *c1 = [UIColor colorWithRed:0.76 green:0.91 blue:0.86 alpha:1];
|
||||
UIColor *c2 = [UIColor colorWithRed:0.93 green:0.97 blue:0.91 alpha:1];
|
||||
if ([skin[@"id"] hasPrefix:@"dark"]) {
|
||||
c1 = [UIColor colorWithRed:0.1 green:0.12 blue:0.16 alpha:1];
|
||||
c2 = [UIColor colorWithRed:0.22 green:0.24 blue:0.28 alpha:1];
|
||||
}
|
||||
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
|
||||
NSArray *colors = @[(__bridge id)c1.CGColor, (__bridge id)c2.CGColor];
|
||||
CGFloat locs[] = {0,1};
|
||||
CGGradientRef grad = CGGradientCreateWithColors(space, (__bridge CFArrayRef)colors, locs);
|
||||
CGContextDrawLinearGradient(ctx, grad, CGPointZero, CGPointMake(size.width, size.height), 0);
|
||||
CGGradientRelease(grad); CGColorSpaceRelease(space);
|
||||
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
payload = UIImageJPEGRepresentation(img, 0.9);
|
||||
}
|
||||
BOOL ok = (payload.length > 0)
|
||||
? [[KBSkinManager shared] applyImageSkinWithData:payload skinId:skinId name:name]
|
||||
: themeOK;
|
||||
if (completion) completion(ok);
|
||||
[KBHUD showInfo:(ok ? KBLocalized(@"已应用,切到键盘查看") : KBLocalized(@"应用皮肤失败"))];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
/// 远程模式:key_icons 的 value 为 URL,需要下载到 App Group,写入主题后再应用。
|
||||
- (void)kb_applySkinUsingRemoteIcons:(NSDictionary *)skin completion:(KBSkinApplyCompletion)completion {
|
||||
NSString *skinId = skin[@"id"] ?: @"remote";
|
||||
NSString *name = skin[@"name"] ?: skinId;
|
||||
NSString *bgURL = skin[@"background_image"] ?: @"";
|
||||
NSDictionary *iconURLs = [skin[@"key_icons"] isKindOfClass:NSDictionary.class] ? skin[@"key_icons"] : @{};
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
__block NSData *bgData = nil;
|
||||
__block NSMutableDictionary<NSString *, NSString *> *iconPathMap = [NSMutableDictionary dictionary];
|
||||
|
||||
// 背景图
|
||||
if (bgURL.length > 0) {
|
||||
dispatch_group_enter(group);
|
||||
[[KBNetworkManager shared] GET:bgURL parameters:nil headers:nil completion:^(id jsonOrData, NSURLResponse *response, NSError *error) {
|
||||
NSData *data = ([jsonOrData isKindOfClass:NSData.class] ? (NSData *)jsonOrData : nil);
|
||||
if (data.length > 0) {
|
||||
UIImage *img = [UIImage imageWithData:data];
|
||||
if (img) {
|
||||
CGFloat maxW = 1500.0;
|
||||
if (img.size.width > maxW) {
|
||||
CGFloat scale = maxW / img.size.width;
|
||||
CGSize newSize = CGSizeMake(maxW, floor(img.size.height * scale));
|
||||
UIGraphicsBeginImageContextWithOptions(newSize, YES, 1.0);
|
||||
[img drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
|
||||
UIImage *resized = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
img = resized ?: img;
|
||||
}
|
||||
data = UIImageJPEGRepresentation(img, 0.85) ?: data;
|
||||
}
|
||||
}
|
||||
bgData = data;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
}
|
||||
|
||||
// 图标下载到 App Group:Skins/<skinId>/icons/<identifier>.png
|
||||
[iconURLs enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSString *urlString, BOOL *stop) {
|
||||
if (![urlString isKindOfClass:NSString.class] || urlString.length == 0) return;
|
||||
dispatch_group_enter(group);
|
||||
[[KBNetworkManager shared] GET:urlString parameters:nil headers:nil completion:^(id jsonOrData, NSURLResponse *response, NSError *error) {
|
||||
NSData *data = ([jsonOrData isKindOfClass:NSData.class] ? (NSData *)jsonOrData : nil);
|
||||
if (data.length > 0) {
|
||||
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
|
||||
if (containerURL) {
|
||||
NSString *skinsRoot = [containerURL.path stringByAppendingPathComponent:@"Skins"];
|
||||
NSString *skinRoot = [skinsRoot stringByAppendingPathComponent:skinId];
|
||||
NSString *iconsDir = [skinRoot stringByAppendingPathComponent:@"icons"];
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:iconsDir
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:NULL];
|
||||
NSString *fileName = [NSString stringWithFormat:@"%@.png", identifier];
|
||||
NSString *fullPath = [iconsDir stringByAppendingPathComponent:fileName];
|
||||
if ([data writeToFile:fullPath atomically:YES]) {
|
||||
NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@", skinId, fileName];
|
||||
iconPathMap[identifier] = relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
}];
|
||||
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
NSMutableDictionary *themeJSON = [skin mutableCopy];
|
||||
themeJSON[@"key_icons"] = iconPathMap.copy; // value 改为 App Group 相对路径
|
||||
|
||||
BOOL themeOK = [[KBSkinManager shared] applyThemeFromJSON:themeJSON];
|
||||
BOOL ok = themeOK;
|
||||
if (bgData.length > 0) {
|
||||
ok = [[KBSkinManager shared] applyImageSkinWithData:bgData skinId:skinId name:name];
|
||||
}
|
||||
if (completion) completion(ok);
|
||||
[KBHUD showInfo:(ok ? KBLocalized(@"已应用,切到键盘查看") : KBLocalized(@"应用皮肤失败"))];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||