Compare commits

...

280 commits

Author SHA1 Message Date
fmodf 8a39ebe2ee wip 2024-05-16 01:00:39 +04:00
fmodf 2d6cdeba1d wip 2024-05-15 16:17:51 +04:00
fmodf e1b74520a2 wip 2024-05-09 18:47:46 +04:00
fmodf 02a8caabe7 wip 2024-05-02 15:37:29 +04:00
fmodf 1b0a62db32 wip 2024-05-02 15:10:10 +04:00
fmodf dc993147f5 wip 2024-05-02 14:44:41 +04:00
fmodf f29d52834d wip 2024-05-02 14:30:01 +04:00
fmodf 2881a3a9ab wip 2024-05-02 13:47:28 +04:00
fmodf ba4b5b8ca8 refactoring state 2024-05-02 01:00:30 +04:00
fmodf 405ed6be65 wip 2024-05-01 23:42:15 +04:00
fmodf 0446bd1991 wip 2024-05-01 23:28:19 +04:00
fmodf a97111007c wip 2024-04-29 19:53:42 +04:00
fmodf a28e829c1a wip 2024-04-28 18:43:37 +04:00
fmodf ba7955ab87 wip 2024-04-28 16:41:31 +04:00
fmodf 05ce6de8d7 wip 2024-04-28 16:14:22 +04:00
fmodf 301e6f0f62 wip 2024-04-28 14:52:18 +04:00
fmodf b80d485041 wip 2024-04-28 14:44:03 +04:00
fmodf 225e3916f8 wip 2024-04-27 21:32:41 +04:00
fmodf 080c54bb88 wip 2024-04-27 19:33:02 +04:00
fmodf 2345027150 arch refactoring 2024-04-27 16:57:46 +04:00
fmodf 0f173753db wip 2024-04-26 19:12:17 +04:00
fmodf 4193230a40 wip 2024-04-26 15:15:22 +04:00
fmodf cd84cdb48b wip 2024-04-26 00:48:18 +04:00
fmodf 7aa08313dd wip 2024-04-25 20:58:02 +04:00
fmodf 0c7284cc3e wip 2024-04-25 15:45:18 +04:00
fmodf 7f2ac43722 wip 2024-04-25 15:05:47 +04:00
fmodf 794d870904 wip 2024-04-23 21:42:04 +04:00
fmodf ce2a89ff04 wip 2024-04-23 20:49:03 +04:00
fmodf e0e96bc6d4 wip 2024-04-23 19:07:28 +04:00
fmodf 97d2875675 wip 2024-04-23 18:15:31 +04:00
fmodf d7cfbf9f0f wip 2024-04-19 14:43:04 +04:00
fmodf e93129d94c wip 2024-04-18 02:05:51 +04:00
fmodf 579ed1af33 wip 2024-04-18 00:53:39 +04:00
fmodf 5fc60e3aa4 wip 2024-04-17 23:04:58 +04:00
fmodf 3712026b38 wip 2024-04-17 12:45:47 +04:00
fmodf 8917c87df6 wip 2024-04-17 12:11:35 +04:00
fmodf fbf5a34a0b wip 2024-04-16 15:21:06 +04:00
fmodf a8f17b68df wip 2024-04-15 20:19:57 +04:00
fmodf 2f4ab62b04 wip 2024-04-15 14:27:52 +04:00
fmodf 9d92bc9a98 wip 2024-04-12 22:38:24 +04:00
fmodf 900280377f db-based-state 2024-04-12 20:29:56 +04:00
fmodf 25429eef26 wip 2024-04-12 19:58:24 +04:00
fmodf e620087909 wip 2024-04-12 18:35:44 +04:00
fmodf e04ee03c29 wip 2024-04-12 16:43:49 +04:00
fmodf 9bc64997b7 wip 2024-04-12 16:22:15 +04:00
fmodf f98d22ebfc wip 2024-04-11 21:29:48 +04:00
fmodf e92fcb1c81 wip 2024-04-11 21:19:07 +04:00
fmodf 5ef03b7486 wip 2024-04-11 16:57:25 +04:00
fmodf cde3694d19 wip 2024-04-11 16:40:22 +04:00
fmodf f749a0d089 wip 2024-04-11 15:49:13 +04:00
fmodf fcbaa35c3f wip 2024-04-10 22:20:22 +04:00
fmodf 0d21b50b08 wip 2024-04-10 20:38:46 +04:00
fmodf 6f4eaebb39 wip 2024-04-10 20:28:28 +04:00
fmodf bcd956954b wip 2024-04-10 18:42:04 +04:00
fmodf 71007b819f wip 2024-04-09 19:20:22 +04:00
fmodf 9fbafb0ed5 wip 2024-04-09 18:57:15 +04:00
fmodf a364f11e15 wip 2024-04-09 18:35:36 +04:00
fmodf c801a82ca1 wip 2024-04-09 18:33:19 +04:00
fmodf d363217e21 wip 2024-04-09 17:45:01 +04:00
fmodf 50d67739f8 wip 2024-04-09 16:06:39 +04:00
fmodf dce9438b52 wip 2024-04-08 18:25:31 +04:00
fmodf 68b5ab17b5 wip 2024-04-04 20:42:24 +04:00
fmodf ada11790d4 wip 2024-04-04 19:20:56 +04:00
fmodf 8a1a27850e wip 2024-04-04 17:40:38 +04:00
fmodf b62a05e1cd wip 2024-04-04 17:14:20 +04:00
fmodf 48c39c6b2f wip 2024-04-04 16:59:15 +04:00
fmodf 2a26bee8ca wip 2024-04-03 19:29:41 +04:00
fmodf 6a3b2559e8 wip 2024-04-03 18:17:34 +04:00
fmodf 69e48f02ad wip 2024-04-03 16:58:30 +04:00
fmodf d5639fe452 wip 2024-04-03 16:13:56 +04:00
fmodf 4d35351474 wip 2024-04-02 23:24:17 +04:00
fmodf 3c8f86d509 wip 2024-04-02 23:07:00 +04:00
fmodf 9c5d478aff wip 2024-04-02 22:17:54 +04:00
fmodf 441044ddae wip 2024-04-02 22:09:04 +04:00
fmodf 655e074cf1 wip 2024-04-02 20:33:34 +04:00
fmodf 813cf50a34 wip 2024-04-02 19:56:05 +04:00
fmodf 3aacc311a0 wip 2024-04-02 17:42:59 +04:00
fmodf 0929662b39 wip 2024-04-02 16:37:13 +04:00
fmodf f17e093a6d wip 2024-04-02 16:17:48 +04:00
fmodf 34a550cc64 wip 2024-04-02 15:31:15 +04:00
fmodf 317dee1e26 wip 2024-04-02 14:36:39 +04:00
fmodf 42eac074fa wip 2024-04-01 18:20:43 +04:00
fmodf 554053fdba wip 2024-03-28 20:19:47 +04:00
fmodf a45584eda0 wip 2024-03-28 18:49:10 +04:00
fmodf 51a39cce42 wip 2024-03-28 18:02:15 +04:00
fmodf 196d5f2176 wip 2024-03-28 17:32:27 +04:00
fmodf 5a0b3536bc wip 2024-03-28 14:40:46 +04:00
fmodf f0cf5e18ec wip 2024-03-28 03:57:22 +04:00
fmodf a8a11c6489 wip 2024-03-27 20:05:21 +04:00
fmodf 2f5f289e3f wip 2024-03-11 19:57:50 +04:00
fmodf 1025518f55 cleanup 2024-03-11 19:30:16 +04:00
fmodf dfa9ce3ec5 roster get 2024-03-03 20:07:16 +04:00
fmodf ed82109382 Merge pull request 'feature/calls-maket' (#5) from feature/calls-maket into xmpp-lib
Reviewed-on: #5
2024-01-28 07:30:10 +00:00
fmodf 0f5019252b implement SCRAM-SHA-1 SASL 2024-01-15 19:20:13 +04:00
fmodf 2e16f4a879 wip 2024-01-12 23:14:50 +04:00
fmodf 930a73168d wip 2024-01-12 23:14:41 +04:00
fmodf 2de949c410 wip 2024-01-12 19:26:59 +04:00
fmodf f6a9b6b163 wip 2024-01-09 22:22:07 +04:00
fmodf 8026771490 wip 2024-01-09 22:16:43 +04:00
fmodf c559fab590 wip 2024-01-09 22:16:22 +04:00
fmodf 865b2881ba reg upd 2023-12-05 17:14:07 +04:00
fmodf 4a64621746 wip 2023-12-05 14:53:07 +04:00
fmodf d948a10117 wip 2023-11-28 17:36:57 +04:00
fmodf 9850213f80 wip 2023-11-28 17:36:39 +04:00
fmodf 4c03959aa8 wip 2023-11-21 20:14:02 +04:00
fmodf 93df47b6ae wip 2023-11-21 17:40:13 +04:00
fmodf b55dfe98fd wip 2023-11-21 16:39:39 +04:00
fmodf 089c47c1a4 wip 2023-11-21 16:18:24 +04:00
fmodf 7f3ec8b754 wip 2023-11-21 15:55:34 +04:00
fmodf d9e8b01b2f wip 2023-11-21 13:26:35 +04:00
fmodf bba24a4929 wip 2023-11-21 12:57:44 +04:00
fmodf ba93689d26 wip 2023-11-21 12:36:16 +04:00
fmodf 64d948eea9 wip 2023-11-15 16:01:04 +04:00
fmodf b0ee26bd62 webrtc 2023-11-14 20:09:33 +04:00
fmodf a68a0b781c wip 2023-11-14 18:11:52 +04:00
fmodf c7997f438d prepare call 2023-11-14 17:41:30 +04:00
fmodf 389d9d44a1 discovering external services 2023-11-14 17:08:45 +04:00
fmodf a09087ffe4 resource binding 2023-11-14 16:12:32 +04:00
fmodf 016b3511b6 wip 2023-11-14 15:38:55 +04:00
fmodf 6a6a077b85 quick resource binding 2023-11-14 15:37:50 +04:00
fmodf 46a358e926 fix stream 2023-11-13 21:24:26 +04:00
fmodf 601d2b9152 request ext services 2023-11-13 19:15:39 +04:00
fmodf b1604ab115 Merge pull request 'feature/auth' (#4) from feature/auth into xmpp-lib
Reviewed-on: #4
2023-11-08 17:06:33 +00:00
fmodf 6c911652d5 wip 2023-11-08 21:04:29 +04:00
fmodf 58aa50e730 wip 2023-11-08 16:57:46 +04:00
fmodf 4af286ecee wip 2023-11-08 14:44:26 +04:00
fmodf 8d85a81e70 wip 2023-11-07 00:18:40 +04:00
fmodf 8fadc53470 wip 2023-11-06 23:55:21 +04:00
fmodf e45bbbecd2 wip 2023-11-06 22:26:28 +04:00
fmodf 0b7e1e5b81 fix parser 2023-10-23 14:25:24 +04:00
fmodf df2ea2eefb Merge pull request 'feature/way-around' (#3) from feature/way-around into xmpp-lib
Reviewed-on: #3
2023-10-12 05:33:33 +00:00
fmodf 8f9eced353 fix 2023-10-12 09:32:23 +04:00
fmodf e1d7393139 wip 2023-10-12 09:15:10 +04:00
fmodf afad9d9641 wip 2023-10-12 08:41:13 +04:00
fmodf 5df60e82cd wip 2023-10-12 08:37:26 +04:00
fmodf 697624e2f9 wip 2023-10-12 08:22:15 +04:00
fmodf 9e6a3cc5c7 wip 2023-10-12 07:00:19 +04:00
fmodf e288e44d45 wip 2023-10-12 05:33:30 +04:00
fmodf d5872f47db wip 2023-10-12 05:07:21 +04:00
fmodf 673097831e wip 2023-10-12 04:57:09 +04:00
fmodf 9c4f8b9f4e wip 2023-10-12 04:19:24 +04:00
fmodf 4afea365a2 wip 2023-10-11 23:55:11 +04:00
fmodf 4cd68fa756 wip 2023-10-11 23:44:39 +04:00
fmodf d655e72336 wip 2023-10-11 20:43:14 +04:00
fmodf d5f97dbf24 wip 2023-10-11 09:41:26 +04:00
fmodf aaaa8a3dd8 wip 2023-10-11 09:24:42 +04:00
fmodf 6373ded7c0 wip 2023-10-10 10:59:11 +04:00
fmodf b48686ec9e wip 2023-10-10 05:30:25 +04:00
fmodf 1a397a4643 wip 2023-10-10 05:14:24 +04:00
fmodf d81094c024 wip 2023-10-09 18:23:02 +04:00
fmodf e781e76f43 wip 2023-10-06 17:58:45 +04:00
fmodf df56001998 wip 2023-10-06 17:24:48 +04:00
fmodf 8b4e50de7c wip 2023-10-06 16:56:26 +04:00
fmodf 2b1674a35e wip 2023-10-04 16:16:34 +04:00
fmodf de5f593072 wip 2023-10-04 06:07:30 +04:00
fmodf 50633b2f7a wip 2023-10-04 03:28:53 +04:00
fmodf 4ed380afd1 wip 2023-10-04 00:31:09 +04:00
fmodf 87ca6b0978 wip 2023-10-03 20:36:42 +04:00
fmodf 9c6522839f wip 2023-10-01 17:58:43 +04:00
fmodf aaf2a0a39d wip 2023-09-30 17:19:03 +04:00
fmodf 423c1be232 wip 2023-09-30 16:16:57 +04:00
fmodf 3cf73bfdea wip 2023-09-28 15:51:08 +04:00
fmodf 8845370bf0 wip 2023-09-28 12:16:58 +04:00
fmodf 89765241a3 wip 2023-09-27 19:16:24 +04:00
fmodf 9654667af0 wip 2023-09-27 15:05:35 +04:00
fmodf 354e9df0ef wip 2023-09-27 11:48:47 +04:00
fmodf 0554b1d262 wip 2023-09-27 10:26:18 +04:00
fmodf 70446adbd1 wip 2023-09-27 10:05:14 +04:00
fmodf 5925e07011 wip 2023-09-27 09:59:19 +04:00
fmodf 7da120521b wip 2023-09-27 02:20:40 +04:00
fmodf 446271d7ba wip 2023-09-27 01:35:31 +04:00
fmodf 5c9143b9dc wip 2023-09-27 01:02:03 +04:00
fmodf 768378e05f wip 2023-09-27 00:21:12 +04:00
fmodf ae9843b61c wip 2023-09-26 23:53:46 +04:00
fmodf f38560fb2d wip 2023-09-26 22:39:36 +04:00
fmodf f45fb69997 wip 2023-09-26 22:10:22 +04:00
fmodf 64047eb936 wip 2023-09-26 21:35:36 +04:00
fmodf 57010e7fae wip 2023-09-26 20:21:52 +04:00
fmodf ad94afa92b wip 2023-09-26 19:22:13 +04:00
fmodf 79272bc220 wip 2023-09-26 00:13:49 +04:00
fmodf 51122902a3 wip 2023-09-25 23:58:40 +04:00
fmodf 6ef0e3ec33 refactoring xml parser 2023-09-25 21:49:48 +04:00
fmodf 68ce04fd1f wip 2023-09-25 20:54:35 +04:00
fmodf 6bffcedb0d wip 2023-09-25 19:08:28 +04:00
fmodf 9397f83f95 wip 2023-09-25 18:19:26 +04:00
fmodf 1c0ce668a4 wip 2023-09-25 17:58:35 +04:00
fmodf b0031d1e50 wip 2023-09-25 16:51:07 +04:00
fmodf d7b597ca0d wip 2023-09-25 15:12:20 +04:00
fmodf 5e9e53dbce wip 2023-09-24 19:55:01 +04:00
fmodf a8bca45732 wip 2023-09-24 19:17:40 +04:00
fmodf e1e4095589 wip 2023-09-24 18:02:13 +04:00
fmodf f92929c829 wip 2023-09-24 17:26:08 +04:00
fmodf 0d30dd199f back to in-app udf 2023-09-24 17:14:58 +04:00
fmodf 0fc7f682d8 wip 2023-09-24 16:20:49 +04:00
fmodf 20265255e7 wip 2023-09-24 16:15:32 +04:00
fmodf 2fed518d81 wip 2023-09-24 02:45:40 +04:00
fmodf eb74ad27e3 wip 2023-09-23 20:39:11 +04:00
fmodf c400da7fb8 wip 2023-09-23 19:41:57 +04:00
fmodf 05c6a5698a wip 2023-09-23 19:35:34 +04:00
fmodf 332b903d6d wip 2023-09-23 19:00:24 +04:00
fmodf 27e6096042 wip 2023-09-23 18:59:45 +04:00
fmodf de06b3c323 wip 2023-09-23 18:39:04 +04:00
fmodf a764385379 wip 2023-09-23 18:30:31 +04:00
fmodf a1f8aa0a82 wip 2023-09-23 18:03:17 +04:00
fmodf 4f4e8b0a89 wip 2023-09-23 15:55:09 +04:00
fmodf 752d8ee4af wip 2023-09-23 15:30:44 +04:00
fmodf 5a87fd419d socket connector 2023-09-22 17:40:08 +04:00
fmodf a7928efbf2 wip 2023-09-22 06:15:17 +04:00
fmodf 8f4d574f19 add dns resolver 2023-09-19 23:35:13 +04:00
fmodf ff891869be wip 2023-09-19 18:58:51 +04:00
fmodf be0061921e wip 2023-09-19 05:53:31 +04:00
fmodf 58c612d6a2 migrate to realm, safe checkpoint 2023-09-19 03:13:33 +04:00
fmodf affc5afdc9 wip 2023-09-18 23:40:48 +04:00
fmodf 57dc5e1d81 UI wip 2023-09-18 19:37:53 +04:00
fmodf 7eae6c295c wip 2023-09-18 01:04:55 +04:00
fmodf 73f0f8762f wip 2023-09-17 23:30:27 +04:00
fmodf 16c0208f21 wip 2023-09-17 19:30:32 +04:00
fmodf 26269c6118 wip 2023-09-17 19:25:03 +04:00
fmodf 118c9b6efa UI go brrr 2023-09-17 18:04:00 +04:00
fmodf 4be16a9656 ui 2023-09-16 03:51:01 +04:00
fmodf ec3092f199 fix db 2023-09-16 00:40:16 +04:00
fmodf de7be9ea68 wip 2023-09-15 23:14:49 +04:00
fmodf fbf8df2c9a update db 2023-09-15 20:24:57 +04:00
fmodf 3f4a2d4cde update sqlite database, add migration mechanism 2023-09-15 20:03:55 +04:00
fmodf 3d4ade5346 wip 2023-09-15 18:10:12 +04:00
fmodf c0a19d86fd wip 2023-09-15 00:28:03 +04:00
fmodf a45479d0c6 fix build errors 2023-09-14 20:11:40 +04:00
fmodf 48d7030090 Merge pull request 'feature/new-target' (#2) from feature/new-target into develop
Reviewed-on: #2
2023-09-14 15:00:23 +00:00
fmodf 2d8019217a add local package 2023-09-14 18:58:53 +04:00
fmodf 43dde437c5 add packages 2023-09-14 16:29:48 +04:00
fmodf 20db5d5f2d add colors from dino, add swiftgen 2023-09-12 22:04:56 +04:00
fmodf 7424e9171a app template 2023-09-11 18:40:58 +04:00
fmodf 6906c094ea upd project 2023-09-11 17:32:07 +04:00
fmodf 3ab551e8de wip 2023-09-11 17:06:34 +04:00
fmodf 1c745f2b25 wip 2023-09-11 16:45:34 +04:00
fmodf 4717aa8649 linter update 2023-09-11 16:24:37 +04:00
fmodf f387236d9d wip 2023-09-11 16:22:16 +04:00
fmodf 80c56f1d48 wip 2023-09-11 16:21:44 +04:00
fmodf 43f01272ab wip 2023-09-11 16:13:44 +04:00
fmodf 1a51090b03 wip 2023-09-11 15:41:19 +04:00
fmodf dba6463116 wip 2023-09-11 15:37:44 +04:00
fmodf 1f414b9344 new multiplatform target 2023-09-11 15:11:45 +04:00
fmodf fc4610192b move old sources 2023-09-11 14:45:21 +04:00
fmodf 50feee4f74 Merge pull request 'feature/project-update' (#1) from feature/project-update into develop
Reviewed-on: #1
2023-09-06 09:05:48 +00:00
fmodf 9df49aa422 wip 2023-09-06 13:02:52 +04:00
fmodf 2736e72e9d fix linter warnings 2023-09-06 12:58:09 +04:00
fmodf 4d7e8d2fbd fix linter warnings 2023-09-06 12:43:28 +04:00
fmodf 4c6aaea4d8 fix linter warnings 2023-09-06 12:24:39 +04:00
fmodf cad4d3dcb0 fix linter warnings 2023-09-06 12:03:57 +04:00
fmodf 3e668aa808 fix linter errors 2023-09-05 18:46:24 +04:00
fmodf 54f2b3b825 fix linter warnings 2023-09-05 18:43:42 +04:00
fmodf f33c3de839 fix linter warnings 2023-09-05 18:25:31 +04:00
fmodf c95460b80e fix linter errors 2023-09-05 18:21:07 +04:00
fmodf cc20619e2c fix linter errors 2023-09-05 18:05:11 +04:00
fmodf f1fd81841e fix linter errors 2023-09-05 18:00:40 +04:00
fmodf 7603ace54e fix linter errors 2023-09-05 17:52:31 +04:00
fmodf b75eed2677 fix linter errors 2023-09-05 17:15:14 +04:00
fmodf d62937af4b fix linter errors 2023-09-05 17:00:08 +04:00
fmodf ab08d43225 fix linter errors 2023-09-05 16:46:12 +04:00
fmodf 507bc16846 fix linter errors 2023-09-05 16:40:40 +04:00
fmodf ac6eb97b16 fix linter errors 2023-09-05 16:33:35 +04:00
fmodf f31f0c54c5 fix linter errors 2023-09-05 13:50:58 +04:00
fmodf 8cbd60d30d fix linter errors 2023-09-05 13:41:32 +04:00
fmodf 93ee79ee20 fix linter error 2023-09-05 13:20:11 +04:00
fmodf a7fddc9022 fix linter errors 2023-09-05 12:32:09 +04:00
fmodf 4316eb2ca1 fix linter errors 2023-09-04 17:39:13 +04:00
fmodf 4b4de74eda fix linter error 2023-09-04 17:31:49 +04:00
fmodf 4d02aaea5b fix linter errors 2023-09-04 17:24:32 +04:00
fmodf 97ac8373c9 fix linter errors 2023-09-04 17:20:18 +04:00
fmodf 68a136e4b7 fix linter errors 2023-08-30 17:33:07 +04:00
fmodf aaa5428bfc fix linter errors 2023-08-30 17:28:14 +04:00
fmodf 39b8a4c6bc fix linter errors 2023-08-30 17:20:46 +04:00
fmodf 050a12b1cc fix linter errors 2023-08-30 17:07:51 +04:00
fmodf f9d776da64 fix linter errors 2023-08-30 16:54:00 +04:00
fmodf 129b63fd16 fix linter errors 2023-08-30 16:28:00 +04:00
fmodf 0535e59bb3 fix linter errors 2023-08-30 16:23:07 +04:00
fmodf 05d79d058f fix linter errors 2023-08-30 16:18:21 +04:00
fmodf eefce61e3a fix linter errors 2023-08-30 16:07:36 +04:00
fmodf 44f3c598cd up to iOS 15 2023-08-28 19:17:55 +04:00
fmodf ac15a7e8c5 move WebRTC and OpenSSL to SPM 2023-08-28 17:18:46 +04:00
654 changed files with 8879 additions and 63437 deletions

View file

@ -1,26 +0,0 @@
[update]
tasks = ["interfaces", "code", "normalize"]
[update.interfaces]
paths = ["."]
defaultToBase = true
ignoreEmptyStrings = false
unstripped = false
[update.code]
codePaths = ["."]
localizablePaths = ["."]
defaultToKeys = true
additive = false
unstripped = false
[update.normalize]
paths = ["."]
sourceLocale = "en"
harmonizeWithSource = true
sortByKeys = true
[lint]
paths = ["."]
duplicateKeys = true
emptyValues = true

14
.gitignore vendored
View file

@ -110,4 +110,16 @@ xcuserdata
#
# We're using source-control, so this is a "feature" that we do not want!
*.moved-aside
*.moved-aside
/.idea
/Snikket/.idea
/Snikket.xcodeproj
/Info.plist
/old/.idea
/Engine/Info.plist
/Snikket/Snikket.entitlements
/XMPPSwift/Client/VoIP/rickroll.mp4
/.nvim
/buildServer.json
TODO.txt
PASSWD.txt

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string></string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>SwiftGen_SwiftGenCLI</string>
<key>CFBundleIdentifier</key>
<string>SwiftGen.SwiftGenCLI.resources</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>SwiftGen_SwiftGenCLI</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>13A233</string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>11.3</string>
<key>DTSDKBuild</key>
<string>20E214</string>
<key>DTSDKName</key>
<string>macosx11.3</string>
<key>DTXcode</key>
<string>1300</string>
<key>DTXcodeBuild</key>
<string>13A233</string>
<key>LSMinimumSystemVersion</key>
<string>10.11</string>
</dict>
</plist>

View file

@ -0,0 +1,43 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if palettes %}
{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
#if os(macOS)
import AppKit
{% if enumName != 'NSColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %}
#elseif os(iOS) || os(tvOS) || os(watchOS)
import UIKit
{% if enumName != 'UIColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %}
#endif
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - Colors
// swiftlint:disable identifier_name line_length type_body_length
{{accessModifier}} extension {{enumName}} {
{% macro h2f hex %}{{hex|hexToInt|int255toFloat}}{% endmacro %}
{% macro enumBlock colors accessPrefix %}
{% for color in colors %}
/// 0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}} (r: {{color.red|hexToInt}}, g: {{color.green|hexToInt}}, b: {{color.blue|hexToInt}}, a: {{color.alpha|hexToInt}})
{{accessPrefix}}static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = #colorLiteral(red: {% call h2f color.red %}, green: {% call h2f color.green %}, blue: {% call h2f color.blue %}, alpha: {% call h2f color.alpha %})
{% endfor %}
{% endmacro %}
{% if palettes.count > 1 or param.forceFileNameEnum %}
{% set accessPrefix %}{{accessModifier}} {% endset %}
{% for palette in palettes %}
enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call enumBlock palette.colors accessPrefix %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call enumBlock palettes.first.colors "" %}
{% endif %}
}
// swiftlint:enable identifier_name line_length type_body_length
{% else %}
// No color found
{% endif %}

View file

@ -0,0 +1,43 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if palettes %}
{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
#if os(macOS)
import AppKit
{% if enumName != 'NSColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %}
#elseif os(iOS) || os(tvOS) || os(watchOS)
import UIKit
{% if enumName != 'UIColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %}
#endif
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - Colors
// swiftlint:disable identifier_name line_length type_body_length
{{accessModifier}} extension {{enumName}} {
{% macro h2f hex %}{{hex|hexToInt|int255toFloat}}{% endmacro %}
{% macro enumBlock colors accessPrefix %}
{% for color in colors %}
/// 0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}} (r: {{color.red|hexToInt}}, g: {{color.green|hexToInt}}, b: {{color.blue|hexToInt}}, a: {{color.alpha|hexToInt}})
{{accessPrefix}}static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = #colorLiteral(red: {% call h2f color.red %}, green: {% call h2f color.green %}, blue: {% call h2f color.blue %}, alpha: {% call h2f color.alpha %})
{% endfor %}
{% endmacro %}
{% if palettes.count > 1 or param.forceFileNameEnum %}
{% set accessPrefix %}{{accessModifier}} {% endset %}
{% for palette in palettes %}
enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call enumBlock palette.colors accessPrefix %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call enumBlock palettes.first.colors "" %}
{% endif %}
}
// swiftlint:enable identifier_name line_length type_body_length
{% else %}
// No color found
{% endif %}

View file

@ -0,0 +1,84 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if palettes %}
{% set colorAlias %}{{param.colorAliasName|default:"Color"}}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
#if os(macOS)
import AppKit.NSColor
{{accessModifier}} typealias {{colorAlias}} = NSColor
#elseif os(iOS) || os(tvOS) || os(watchOS)
import UIKit.UIColor
{{accessModifier}} typealias {{colorAlias}} = UIColor
#endif
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Colors
// swiftlint:disable identifier_name line_length type_body_length
{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %}
{{accessModifier}} struct {{enumName}} {
{{accessModifier}} let rgbaValue: UInt32
{{accessModifier}} var color: {{colorAlias}} { return {{colorAlias}}(named: self) }
{% macro rgbaValue color %}0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}{% endmacro %}
{% macro enumBlock colors %}
{% for color in colors %}
/// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#{{color.red}}{{color.green}}{{color.blue}}"></span>
/// Alpha: {{color.alpha|hexToInt|int255toFloat|percent}} <br/> (0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}})
{{accessModifier}} static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}(rgbaValue: {% call rgbaValue color %})
{% endfor %}
{% endmacro %}
{% if palettes.count > 1 or param.forceFileNameEnum %}
{% for palette in palettes %}
{{accessModifier}} enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call enumBlock palette.colors %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call enumBlock palettes.first.colors %}
{% endif %}
}
// swiftlint:enable identifier_name line_length type_body_length
// MARK: - Implementation Details
internal extension {{colorAlias}} {
convenience init(rgbaValue: UInt32) {
let components = RGBAComponents(rgbaValue: rgbaValue).normalized
self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3])
}
}
private struct RGBAComponents {
let rgbaValue: UInt32
private var shifts: [UInt32] {
[
rgbaValue >> 24, // red
rgbaValue >> 16, // green
rgbaValue >> 8, // blue
rgbaValue // alpha
]
}
private var components: [CGFloat] {
shifts.map {
CGFloat($0 & 0xff)
}
}
var normalized: [CGFloat] {
components.map { $0 / 255.0 }
}
}
{{accessModifier}} extension {{colorAlias}} {
convenience init(named color: {{enumName}}) {
self.init(rgbaValue: color.rgbaValue)
}
}
{% else %}
// No color found
{% endif %}

View file

@ -0,0 +1,84 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if palettes %}
{% set colorAlias %}{{param.colorAliasName|default:"Color"}}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
#if os(macOS)
import AppKit.NSColor
{{accessModifier}} typealias {{colorAlias}} = NSColor
#elseif os(iOS) || os(tvOS) || os(watchOS)
import UIKit.UIColor
{{accessModifier}} typealias {{colorAlias}} = UIColor
#endif
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Colors
// swiftlint:disable identifier_name line_length type_body_length
{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %}
{{accessModifier}} struct {{enumName}} {
{{accessModifier}} let rgbaValue: UInt32
{{accessModifier}} var color: {{colorAlias}} { return {{colorAlias}}(named: self) }
{% macro rgbaValue color %}0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}{% endmacro %}
{% macro enumBlock colors %}
{% for color in colors %}
/// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#{{color.red}}{{color.green}}{{color.blue}}"></span>
/// Alpha: {{color.alpha|hexToInt|int255toFloat|percent}} <br/> (0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}})
{{accessModifier}} static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}(rgbaValue: {% call rgbaValue color %})
{% endfor %}
{% endmacro %}
{% if palettes.count > 1 or param.forceFileNameEnum %}
{% for palette in palettes %}
{{accessModifier}} enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call enumBlock palette.colors %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call enumBlock palettes.first.colors %}
{% endif %}
}
// swiftlint:enable identifier_name line_length type_body_length
// MARK: - Implementation Details
internal extension {{colorAlias}} {
convenience init(rgbaValue: UInt32) {
let components = RGBAComponents(rgbaValue: rgbaValue).normalized
self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3])
}
}
private struct RGBAComponents {
let rgbaValue: UInt32
private var shifts: [UInt32] {
[
rgbaValue >> 24, // red
rgbaValue >> 16, // green
rgbaValue >> 8, // blue
rgbaValue // alpha
]
}
private var components: [CGFloat] {
shifts.map {
CGFloat($0 & 0xff)
}
}
var normalized: [CGFloat] {
components.map { $0 / 255.0 }
}
}
{{accessModifier}} extension {{colorAlias}} {
convenience init(named color: {{enumName}}) {
self.init(rgbaValue: color.rgbaValue)
}
}
{% else %}
// No color found
{% endif %}

View file

@ -0,0 +1,211 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
// swiftlint:disable superfluous_disable_command implicit_return
// swiftlint:disable sorted_imports
import CoreData
import Foundation
{% for import in param.extraImports %}
import {{ import }}
{% empty %}
{# If extraImports is a single String instead of an array, `for` considers it empty but we still have to check if there's a single String value #}
{% if param.extraImports %}import {{ param.extraImports }}{% endif %}
{% endfor %}
// swiftlint:disable attributes file_length vertical_whitespace_closing_braces
// swiftlint:disable identifier_name line_length type_body_length
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% for model in models %}
{% for name, entity in model.entities %}
{% set superclass %}{{ model.entities[entity.superEntity].className|default:"NSManagedObject" }}{% endset %}
{% set entityClassName %}{{ entity.className|default:"NSManagedObject" }}{% endset %}
// MARK: - {{ entity.name }}
{% if not entity.shouldGenerateCode %}
// Note: '{{ entity.name }}' has codegen enabled for Xcode, skipping code generation.
{% elif entityClassName|contains:"." %}
// Warning: '{{ entityClassName }}' cannot be a valid type name, skipping code generation.
{% else %}
{% if param.generateObjcName %}
@objc({{ entityClassName }})
{% endif %}
{{ accessModifier }} class {{ entityClassName }}: {{ superclass }} {
{% set override %}{% if superclass != "NSManagedObject" %}override {% endif %}{% endset %}
{{ override }}{{ accessModifier }} class var entityName: String {
return "{{ entity.name }}"
}
{{ override }}{{ accessModifier }} class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext)
}
@available(*, deprecated, renamed: "makeFetchRequest", message: "To avoid collisions with the less concrete method in `NSManagedObject`, please use `makeFetchRequest()` instead.")
@nonobjc {{ accessModifier }} class func fetchRequest() -> NSFetchRequest<{{ entityClassName }}> {
return NSFetchRequest<{{ entityClassName }}>(entityName: entityName)
}
@nonobjc {{ accessModifier }} class func makeFetchRequest() -> NSFetchRequest<{{ entityClassName }}> {
return NSFetchRequest<{{ entityClassName }}>(entityName: entityName)
}
// swiftlint:disable discouraged_optional_boolean discouraged_optional_collection
{% for attribute in entity.attributes %}
{% if attribute.userInfo.RawType %}
{% set rawType attribute.userInfo.RawType %}
{% set unwrapOptional attribute.userInfo.unwrapOptional %}
{{ accessModifier }} var {{ attribute.name }}: {{ rawType }}{% if not unwrapOptional %}?{% endif %} {
get {
let key = "{{ attribute.name }}"
willAccessValue(forKey: key)
defer { didAccessValue(forKey: key) }
{% if unwrapOptional %}
guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue,
let result = {{ rawType }}(rawValue: value) else {
fatalError("Could not convert value for key '\(key)' to type '{{ rawType }}'")
}
return result
{% else %}
guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue else {
return nil
}
return {{ rawType }}(rawValue: value)
{% endif %}
}
set {
let key = "{{ attribute.name }}"
willChangeValue(forKey: key)
defer { didChangeValue(forKey: key) }
setPrimitiveValue(newValue{% if not unwrapOptional %}?{% endif %}.rawValue, forKey: key)
}
}
{% elif attribute.usesScalarValueType and attribute.isOptional %}
{{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}? {
get {
let key = "{{ attribute.name }}"
willAccessValue(forKey: key)
defer { didAccessValue(forKey: key) }
return primitiveValue(forKey: key) as? {{ attribute.typeName }}
}
set {
let key = "{{ attribute.name }}"
willChangeValue(forKey: key)
defer { didChangeValue(forKey: key) }
setPrimitiveValue(newValue, forKey: key)
}
}
{% else %}
@NSManaged {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}{% if attribute.isOptional %}?{% endif %}
{% endif %}
{% endfor %}
{% for relationship in entity.relationships %}
{% if relationship.isToMany %}
@NSManaged {{ accessModifier }} var {{ relationship.name }}: {% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}>{% endif %}{% if relationship.isOptional %}?{% endif %}
{% else %}
@NSManaged {{ accessModifier }} var {{ relationship.name }}: {{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% if relationship.isOptional %}?{% endif %}
{% endif %}
{% endfor %}
{% for fetchedProperty in entity.fetchedProperties %}
@NSManaged {{ accessModifier }} var {{ fetchedProperty.name }}: [{{ fetchedProperty.fetchRequest.entity }}]
{% endfor %}
// swiftlint:enable discouraged_optional_boolean discouraged_optional_collection
}
{% for relationship in entity.relationships where relationship.isToMany %}
{% set destinationEntityClassName %}{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% endset %}
{% set collectionClassName %}{% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ destinationEntityClassName }}>{% endif %}{% endset %}
{% set relationshipName %}{{ relationship.name | upperFirstLetter }}{% endset %}
// MARK: Relationship {{ relationshipName }}
extension {{ entityClassName }} {
{% if relationship.isOrdered %}
@objc(insertObject:in{{ relationshipName }}AtIndex:)
@NSManaged public func insertInto{{ relationshipName }}(_ value: {{ destinationEntityClassName }}, at idx: Int)
@objc(removeObjectFrom{{ relationshipName }}AtIndex:)
@NSManaged public func removeFrom{{ relationshipName }}(at idx: Int)
@objc(insert{{ relationshipName }}:atIndexes:)
@NSManaged public func insertInto{{ relationshipName }}(_ values: [{{ destinationEntityClassName }}], at indexes: NSIndexSet)
@objc(remove{{ relationshipName }}AtIndexes:)
@NSManaged public func removeFrom{{ relationshipName }}(at indexes: NSIndexSet)
@objc(replaceObjectIn{{ relationshipName }}AtIndex:withObject:)
@NSManaged public func replace{{ relationshipName }}(at idx: Int, with value: {{ destinationEntityClassName }})
@objc(replace{{ relationshipName }}AtIndexes:with{{ relationshipName }}:)
@NSManaged public func replace{{ relationshipName }}(at indexes: NSIndexSet, with values: [{{ destinationEntityClassName }}])
{% endif %}
@objc(add{{ relationshipName }}Object:)
@NSManaged public func addTo{{ relationshipName }}(_ value: {{ destinationEntityClassName }})
@objc(remove{{ relationshipName }}Object:)
@NSManaged public func removeFrom{{ relationshipName }}(_ value: {{ destinationEntityClassName }})
@objc(add{{ relationshipName }}:)
@NSManaged public func addTo{{ relationshipName }}(_ values: {{ collectionClassName }})
@objc(remove{{ relationshipName }}:)
@NSManaged public func removeFrom{{ relationshipName }}(_ values: {{ collectionClassName }})
}
{% endfor %}
{% if model.fetchRequests[entity.name].count > 0 %}
// MARK: Fetch Requests
extension {{ entityClassName }} {
{% for fetchRequest in model.fetchRequests[entity.name] %}
{% set resultTypeName %}{% filter removeNewlines:"leading" %}
{% if fetchRequest.resultType == "Object" %}
{{ entityClassName }}
{% elif fetchRequest.resultType == "Object ID" %}
NSManagedObjectID
{% elif fetchRequest.resultType == "Dictionary" %}
[String: Any]
{% endif %}
{% endfilter %}{% endset %}
class func fetch{{ fetchRequest.name | upperFirstLetter }}({% filter removeNewlines:"leading" %}
managedObjectContext: NSManagedObjectContext
{% for variableName, variableType in fetchRequest.substitutionVariables %}
, {{ variableName | lowerFirstWord }}: {{ variableType }}
{% endfor %}
{% endfilter %}) throws -> [{{ resultTypeName }}] {
guard let persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator else {
fatalError("Managed object context has no persistent store coordinator for getting fetch request templates")
}
let model = persistentStoreCoordinator.managedObjectModel
let substitutionVariables: [String: Any] = [
{% for variableName, variableType in fetchRequest.substitutionVariables %}
"{{ variableName }}": {{ variableName | lowerFirstWord }}{{ "," if not forloop.last }}
{% empty %}
:
{% endfor %}
]
guard let fetchRequest = model.fetchRequestFromTemplate(withName: "{{ fetchRequest.name }}", substitutionVariables: substitutionVariables) else {
fatalError("No fetch request template named '{{ fetchRequest.name }}' found.")
}
guard let result = try managedObjectContext.fetch(fetchRequest) as? [{{ resultTypeName }}] else {
fatalError("Unable to cast fetch result to correct result type.")
}
return result
}
{% endfor %}
}
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
// swiftlint:enable identifier_name line_length type_body_length

View file

@ -0,0 +1,211 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
// swiftlint:disable superfluous_disable_command implicit_return
// swiftlint:disable sorted_imports
import CoreData
import Foundation
{% for import in param.extraImports %}
import {{ import }}
{% empty %}
{# If extraImports is a single String instead of an array, `for` considers it empty but we still have to check if there's a single String value #}
{% if param.extraImports %}import {{ param.extraImports }}{% endif %}
{% endfor %}
// swiftlint:disable attributes file_length vertical_whitespace_closing_braces
// swiftlint:disable identifier_name line_length type_body_length
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% for model in models %}
{% for name, entity in model.entities %}
{% set superclass %}{{ model.entities[entity.superEntity].className|default:"NSManagedObject" }}{% endset %}
{% set entityClassName %}{{ entity.className|default:"NSManagedObject" }}{% endset %}
// MARK: - {{ entity.name }}
{% if not entity.shouldGenerateCode %}
// Note: '{{ entity.name }}' has codegen enabled for Xcode, skipping code generation.
{% elif entityClassName|contains:"." %}
// Warning: '{{ entityClassName }}' cannot be a valid type name, skipping code generation.
{% else %}
{% if param.generateObjcName %}
@objc({{ entityClassName }})
{% endif %}
{{ accessModifier }} class {{ entityClassName }}: {{ superclass }} {
{% set override %}{% if superclass != "NSManagedObject" %}override {% endif %}{% endset %}
{{ override }}{{ accessModifier }} class var entityName: String {
return "{{ entity.name }}"
}
{{ override }}{{ accessModifier }} class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext)
}
@available(*, deprecated, renamed: "makeFetchRequest", message: "To avoid collisions with the less concrete method in `NSManagedObject`, please use `makeFetchRequest()` instead.")
@nonobjc {{ accessModifier }} class func fetchRequest() -> NSFetchRequest<{{ entityClassName }}> {
return NSFetchRequest<{{ entityClassName }}>(entityName: entityName)
}
@nonobjc {{ accessModifier }} class func makeFetchRequest() -> NSFetchRequest<{{ entityClassName }}> {
return NSFetchRequest<{{ entityClassName }}>(entityName: entityName)
}
// swiftlint:disable discouraged_optional_boolean discouraged_optional_collection
{% for attribute in entity.attributes %}
{% if attribute.userInfo.RawType %}
{% set rawType attribute.userInfo.RawType %}
{% set unwrapOptional attribute.userInfo.unwrapOptional %}
{{ accessModifier }} var {{ attribute.name }}: {{ rawType }}{% if not unwrapOptional %}?{% endif %} {
get {
let key = "{{ attribute.name }}"
willAccessValue(forKey: key)
defer { didAccessValue(forKey: key) }
{% if unwrapOptional %}
guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue,
let result = {{ rawType }}(rawValue: value) else {
fatalError("Could not convert value for key '\(key)' to type '{{ rawType }}'")
}
return result
{% else %}
guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue else {
return nil
}
return {{ rawType }}(rawValue: value)
{% endif %}
}
set {
let key = "{{ attribute.name }}"
willChangeValue(forKey: key)
defer { didChangeValue(forKey: key) }
setPrimitiveValue(newValue{% if not unwrapOptional %}?{% endif %}.rawValue, forKey: key)
}
}
{% elif attribute.usesScalarValueType and attribute.isOptional %}
{{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}? {
get {
let key = "{{ attribute.name }}"
willAccessValue(forKey: key)
defer { didAccessValue(forKey: key) }
return primitiveValue(forKey: key) as? {{ attribute.typeName }}
}
set {
let key = "{{ attribute.name }}"
willChangeValue(forKey: key)
defer { didChangeValue(forKey: key) }
setPrimitiveValue(newValue, forKey: key)
}
}
{% else %}
@NSManaged {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}{% if attribute.isOptional %}?{% endif %}
{% endif %}
{% endfor %}
{% for relationship in entity.relationships %}
{% if relationship.isToMany %}
@NSManaged {{ accessModifier }} var {{ relationship.name }}: {% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}>{% endif %}{% if relationship.isOptional %}?{% endif %}
{% else %}
@NSManaged {{ accessModifier }} var {{ relationship.name }}: {{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% if relationship.isOptional %}?{% endif %}
{% endif %}
{% endfor %}
{% for fetchedProperty in entity.fetchedProperties %}
@NSManaged {{ accessModifier }} var {{ fetchedProperty.name }}: [{{ fetchedProperty.fetchRequest.entity }}]
{% endfor %}
// swiftlint:enable discouraged_optional_boolean discouraged_optional_collection
}
{% for relationship in entity.relationships where relationship.isToMany %}
{% set destinationEntityClassName %}{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% endset %}
{% set collectionClassName %}{% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ destinationEntityClassName }}>{% endif %}{% endset %}
{% set relationshipName %}{{ relationship.name | upperFirstLetter }}{% endset %}
// MARK: Relationship {{ relationshipName }}
extension {{ entityClassName }} {
{% if relationship.isOrdered %}
@objc(insertObject:in{{ relationshipName }}AtIndex:)
@NSManaged public func insertInto{{ relationshipName }}(_ value: {{ destinationEntityClassName }}, at idx: Int)
@objc(removeObjectFrom{{ relationshipName }}AtIndex:)
@NSManaged public func removeFrom{{ relationshipName }}(at idx: Int)
@objc(insert{{ relationshipName }}:atIndexes:)
@NSManaged public func insertInto{{ relationshipName }}(_ values: [{{ destinationEntityClassName }}], at indexes: NSIndexSet)
@objc(remove{{ relationshipName }}AtIndexes:)
@NSManaged public func removeFrom{{ relationshipName }}(at indexes: NSIndexSet)
@objc(replaceObjectIn{{ relationshipName }}AtIndex:withObject:)
@NSManaged public func replace{{ relationshipName }}(at idx: Int, with value: {{ destinationEntityClassName }})
@objc(replace{{ relationshipName }}AtIndexes:with{{ relationshipName }}:)
@NSManaged public func replace{{ relationshipName }}(at indexes: NSIndexSet, with values: [{{ destinationEntityClassName }}])
{% endif %}
@objc(add{{ relationshipName }}Object:)
@NSManaged public func addTo{{ relationshipName }}(_ value: {{ destinationEntityClassName }})
@objc(remove{{ relationshipName }}Object:)
@NSManaged public func removeFrom{{ relationshipName }}(_ value: {{ destinationEntityClassName }})
@objc(add{{ relationshipName }}:)
@NSManaged public func addTo{{ relationshipName }}(_ values: {{ collectionClassName }})
@objc(remove{{ relationshipName }}:)
@NSManaged public func removeFrom{{ relationshipName }}(_ values: {{ collectionClassName }})
}
{% endfor %}
{% if model.fetchRequests[entity.name].count > 0 %}
// MARK: Fetch Requests
extension {{ entityClassName }} {
{% for fetchRequest in model.fetchRequests[entity.name] %}
{% set resultTypeName %}{% filter removeNewlines:"leading" %}
{% if fetchRequest.resultType == "Object" %}
{{ entityClassName }}
{% elif fetchRequest.resultType == "Object ID" %}
NSManagedObjectID
{% elif fetchRequest.resultType == "Dictionary" %}
[String: Any]
{% endif %}
{% endfilter %}{% endset %}
class func fetch{{ fetchRequest.name | upperFirstLetter }}({% filter removeNewlines:"leading" %}
managedObjectContext: NSManagedObjectContext
{% for variableName, variableType in fetchRequest.substitutionVariables %}
, {{ variableName | lowerFirstWord }}: {{ variableType }}
{% endfor %}
{% endfilter %}) throws -> [{{ resultTypeName }}] {
guard let persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator else {
fatalError("Managed object context has no persistent store coordinator for getting fetch request templates")
}
let model = persistentStoreCoordinator.managedObjectModel
let substitutionVariables: [String: Any] = [
{% for variableName, variableType in fetchRequest.substitutionVariables %}
"{{ variableName }}": {{ variableName | lowerFirstWord }}{{ "," if not forloop.last }}
{% empty %}
:
{% endfor %}
]
guard let fetchRequest = model.fetchRequestFromTemplate(withName: "{{ fetchRequest.name }}", substitutionVariables: substitutionVariables) else {
fatalError("No fetch request template named '{{ fetchRequest.name }}' found.")
}
guard let result = try managedObjectContext.fetch(fetchRequest) as? [{{ resultTypeName }}] else {
fatalError("Unable to cast fetch result to correct result type.")
}
return result
}
{% endfor %}
}
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
// swiftlint:enable identifier_name line_length type_body_length

View file

@ -0,0 +1,103 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if groups.count > 0 %}
{% set enumName %}{{param.enumName|default:"Files"}}{% endset %}
{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command file_length line_length implicit_return
// MARK: - Files
{% macro groupBlock group %}
{% for file in group.files %}
{% call fileBlock file %}
{% endfor %}
{% for dir in group.directories %}
{% call dirBlock dir %}
{% endfor %}
{% endmacro %}
{% macro fileBlock file %}
/// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %}
{% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %}
{{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}")
{% endmacro %}
{% macro dirBlock directory %}
{% for file in directory.files %}
{% call fileBlock file %}
{% endfor %}
{% for dir in directory.directories %}
{% call dirBlock dir %}
{% endfor %}
{% endmacro %}
// swiftlint:disable explicit_type_interface identifier_name
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
{{accessModifier}} enum {{enumName}} {
{% if groups.count > 1 or param.forceFileNameEnum %}
{% for group in groups %}
{{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call groupBlock group %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call groupBlock groups.first %}
{% endif %}
}
// swiftlint:enable explicit_type_interface identifier_name
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
// MARK: - Implementation Details
{{accessModifier}} struct {{resourceType}} {
{{accessModifier}} let name: String
{{accessModifier}} let ext: String?
{{accessModifier}} let relativePath: String
{{accessModifier}} let mimeType: String
{{accessModifier}} var url: URL {
return url(locale: nil)
}
{{accessModifier}} func url(locale: Locale?) -> URL {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
let url = bundle.url(
forResource: name,
withExtension: ext,
subdirectory: relativePath,
localization: locale?.identifier
)
guard let result = url else {
let file = name + (ext.flatMap { ".\($0)" } ?? "")
fatalError("Could not locate file named \(file)")
}
return result
}
{{accessModifier}} var path: String {
return path(locale: nil)
}
{{accessModifier}} func path(locale: Locale?) -> String {
return url(locale: locale).path
}
}
{% if not param.bundle %}
// swiftlint:disable convenience_type explicit_type_interface
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type explicit_type_interface
{% endif %}
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,103 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if groups.count > 0 %}
{% set enumName %}{{param.enumName|default:"Files"}}{% endset %}
{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command file_length line_length implicit_return
// MARK: - Files
{% macro groupBlock group %}
{% for file in group.files %}
{% call fileBlock file %}
{% endfor %}
{% for dir in group.directories %}
{% call dirBlock dir %}
{% endfor %}
{% endmacro %}
{% macro fileBlock file %}
/// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %}
{% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %}
{{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}")
{% endmacro %}
{% macro dirBlock directory %}
{% for file in directory.files %}
{% call fileBlock file %}
{% endfor %}
{% for dir in directory.directories %}
{% call dirBlock dir %}
{% endfor %}
{% endmacro %}
// swiftlint:disable explicit_type_interface identifier_name
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
{{accessModifier}} enum {{enumName}} {
{% if groups.count > 1 or param.forceFileNameEnum %}
{% for group in groups %}
{{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call groupBlock group %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call groupBlock groups.first %}
{% endif %}
}
// swiftlint:enable explicit_type_interface identifier_name
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
// MARK: - Implementation Details
{{accessModifier}} struct {{resourceType}} {
{{accessModifier}} let name: String
{{accessModifier}} let ext: String?
{{accessModifier}} let relativePath: String
{{accessModifier}} let mimeType: String
{{accessModifier}} var url: URL {
return url(locale: nil)
}
{{accessModifier}} func url(locale: Locale?) -> URL {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
let url = bundle.url(
forResource: name,
withExtension: ext,
subdirectory: relativePath,
localization: locale?.identifier
)
guard let result = url else {
let file = name + (ext.flatMap { ".\($0)" } ?? "")
fatalError("Could not locate file named \(file)")
}
return result
}
{{accessModifier}} var path: String {
return path(locale: nil)
}
{{accessModifier}} func path(locale: Locale?) -> String {
return url(locale: locale).path
}
}
{% if not param.bundle %}
// swiftlint:disable convenience_type explicit_type_interface
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type explicit_type_interface
{% endif %}
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,107 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if groups.count > 0 %}
{% set enumName %}{{param.enumName|default:"Files"}}{% endset %}
{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command file_length line_length implicit_return
// MARK: - Files
{% macro groupBlock group %}
{% for file in group.files %}
{% call fileBlock file %}
{% endfor %}
{% for dir in group.directories %}
{% call dirBlock dir "" %}
{% endfor %}
{% endmacro %}
{% macro fileBlock file %}
/// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %}
{% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %}
{{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}")
{% endmacro %}
{% macro dirBlock directory parent %}
{% set fullDir %}{{parent}}{{directory.name}}/{% endset %}
/// {{ fullDir }}
{{accessModifier}} enum {{directory.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% for file in directory.files %}
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
{% endfor %}
{% for dir in directory.directories %}
{% filter indent:2 %}{% call dirBlock dir fullDir %}{% endfilter %}
{% endfor %}
}
{% endmacro %}
// swiftlint:disable explicit_type_interface identifier_name
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
{{accessModifier}} enum {{enumName}} {
{% if groups.count > 1 or param.forceFileNameEnum %}
{% for group in groups %}
{{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call groupBlock group %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call groupBlock groups.first %}
{% endif %}
}
// swiftlint:enable explicit_type_interface identifier_name
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
// MARK: - Implementation Details
{{accessModifier}} struct {{resourceType}} {
{{accessModifier}} let name: String
{{accessModifier}} let ext: String?
{{accessModifier}} let relativePath: String
{{accessModifier}} let mimeType: String
{{accessModifier}} var url: URL {
return url(locale: nil)
}
{{accessModifier}} func url(locale: Locale?) -> URL {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
let url = bundle.url(
forResource: name,
withExtension: ext,
subdirectory: relativePath,
localization: locale?.identifier
)
guard let result = url else {
let file = name + (ext.flatMap { ".\($0)" } ?? "")
fatalError("Could not locate file named \(file)")
}
return result
}
{{accessModifier}} var path: String {
return path(locale: nil)
}
{{accessModifier}} func path(locale: Locale?) -> String {
return url(locale: locale).path
}
}
{% if not param.bundle %}
// swiftlint:disable convenience_type explicit_type_interface
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type explicit_type_interface
{% endif %}
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,107 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if groups.count > 0 %}
{% set enumName %}{{param.enumName|default:"Files"}}{% endset %}
{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command file_length line_length implicit_return
// MARK: - Files
{% macro groupBlock group %}
{% for file in group.files %}
{% call fileBlock file %}
{% endfor %}
{% for dir in group.directories %}
{% call dirBlock dir "" %}
{% endfor %}
{% endmacro %}
{% macro fileBlock file %}
/// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %}
{% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %}
{{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}")
{% endmacro %}
{% macro dirBlock directory parent %}
{% set fullDir %}{{parent}}{{directory.name}}/{% endset %}
/// {{ fullDir }}
{{accessModifier}} enum {{directory.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% for file in directory.files %}
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
{% endfor %}
{% for dir in directory.directories %}
{% filter indent:2 %}{% call dirBlock dir fullDir %}{% endfilter %}
{% endfor %}
}
{% endmacro %}
// swiftlint:disable explicit_type_interface identifier_name
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
{{accessModifier}} enum {{enumName}} {
{% if groups.count > 1 or param.forceFileNameEnum %}
{% for group in groups %}
{{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call groupBlock group %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call groupBlock groups.first %}
{% endif %}
}
// swiftlint:enable explicit_type_interface identifier_name
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
// MARK: - Implementation Details
{{accessModifier}} struct {{resourceType}} {
{{accessModifier}} let name: String
{{accessModifier}} let ext: String?
{{accessModifier}} let relativePath: String
{{accessModifier}} let mimeType: String
{{accessModifier}} var url: URL {
return url(locale: nil)
}
{{accessModifier}} func url(locale: Locale?) -> URL {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
let url = bundle.url(
forResource: name,
withExtension: ext,
subdirectory: relativePath,
localization: locale?.identifier
)
guard let result = url else {
let file = name + (ext.flatMap { ".\($0)" } ?? "")
fatalError("Could not locate file named \(file)")
}
return result
}
{{accessModifier}} var path: String {
return path(locale: nil)
}
{{accessModifier}} func path(locale: Locale?) -> String {
return url(locale: locale).path
}
}
{% if not param.bundle %}
// swiftlint:disable convenience_type explicit_type_interface
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type explicit_type_interface
{% endif %}
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,110 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if families %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %}
#if os(macOS)
import AppKit.NSFont
#elseif os(iOS) || os(tvOS) || os(watchOS)
import UIKit.UIFont
#endif
// Deprecated typealiases
@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0")
{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// swiftlint:disable implicit_return
// MARK: - Fonts
// swiftlint:disable identifier_name line_length type_body_length
{% macro transformPath path %}{% filter removeNewlines %}
{% if param.preservePath %}
{{path}}
{% else %}
{{path|basename}}
{% endif %}
{% endfilter %}{% endmacro %}
{{accessModifier}} enum {{param.enumName|default:"FontFamily"}} {
{% for family in families %}
{{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% for font in family.fonts %}
{{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}")
{% endfor %}
{{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}]
}
{% endfor %}
{{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 }
{{accessModifier}} static func registerAllCustomFonts() {
allCustomFonts.forEach { $0.register() }
}
}
// swiftlint:enable identifier_name line_length type_body_length
// MARK: - Implementation Details
{{accessModifier}} struct {{fontType}} {
{{accessModifier}} let name: String
{{accessModifier}} let family: String
{{accessModifier}} let path: String
#if os(macOS)
{{accessModifier}} typealias Font = NSFont
#elseif os(iOS) || os(tvOS) || os(watchOS)
{{accessModifier}} typealias Font = UIFont
#endif
{{accessModifier}} func font(size: CGFloat) -> Font! {
return Font(font: self, size: size)
}
{{accessModifier}} func register() {
// swiftlint:disable:next conditional_returns_on_newline
guard let url = url else { return }
CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil)
}
fileprivate var url: URL? {
{% if param.lookupFunction %}
return {{param.lookupFunction}}(name, family, path)
{% else %}
return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil)
{% endif %}
}
}
{{accessModifier}} extension {{fontType}}.Font {
convenience init?(font: {{fontType}}, size: CGFloat) {
#if os(iOS) || os(tvOS) || os(watchOS)
if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) {
font.register()
}
#elseif os(macOS)
if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none {
font.register()
}
#endif
self.init(name: font.name, size: size)
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No fonts found
{% endif %}

View file

@ -0,0 +1,113 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if families %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %}
#if os(macOS)
import AppKit.NSFont
#elseif os(iOS) || os(tvOS) || os(watchOS)
import UIKit.UIFont
#endif
// Deprecated typealiases
@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0")
{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - Fonts
// swiftlint:disable identifier_name line_length type_body_length
{% macro transformPath path %}{% filter removeNewlines %}
{% if param.preservePath %}
{{path}}
{% else %}
{{path|basename}}
{% endif %}
{% endfilter %}{% endmacro %}
{{accessModifier}} enum {{param.enumName|default:"FontFamily"}} {
{% for family in families %}
{{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% for font in family.fonts %}
{{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}")
{% endfor %}
{{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}]
}
{% endfor %}
{{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 }
{{accessModifier}} static func registerAllCustomFonts() {
allCustomFonts.forEach { $0.register() }
}
}
// swiftlint:enable identifier_name line_length type_body_length
// MARK: - Implementation Details
{{accessModifier}} struct {{fontType}} {
{{accessModifier}} let name: String
{{accessModifier}} let family: String
{{accessModifier}} let path: String
#if os(macOS)
{{accessModifier}} typealias Font = NSFont
#elseif os(iOS) || os(tvOS) || os(watchOS)
{{accessModifier}} typealias Font = UIFont
#endif
{{accessModifier}} func font(size: CGFloat) -> Font {
guard let font = Font(font: self, size: size) else {
fatalError("Unable to initialize font '\(name)' (\(family))")
}
return font
}
{{accessModifier}} func register() {
// swiftlint:disable:next conditional_returns_on_newline
guard let url = url else { return }
CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil)
}
fileprivate var url: URL? {
// swiftlint:disable:next implicit_return
{% if param.lookupFunction %}
return {{param.lookupFunction}}(name, family, path)
{% else %}
return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil)
{% endif %}
}
}
{{accessModifier}} extension {{fontType}}.Font {
convenience init?(font: {{fontType}}, size: CGFloat) {
#if os(iOS) || os(tvOS) || os(watchOS)
if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) {
font.register()
}
#elseif os(macOS)
if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none {
font.register()
}
#endif
self.init(name: font.name, size: size)
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No fonts found
{% endif %}

View file

@ -0,0 +1,157 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if platform and storyboards %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %}
{% set prefix %}{% if isAppKit %}NS{% else %}UI{% endif %}{% endset %}
{% set controller %}{% if isAppKit %}Controller{% else %}ViewController{% endif %}{% endset %}
// swiftlint:disable sorted_imports
import Foundation
{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %}
import {{module}}
{% endfor %}
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length implicit_return
// MARK: - Storyboard Scenes
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
{% macro moduleName item %}{% filter removeNewlines %}
{% if item.moduleIsPlaceholder %}
{{ env.PRODUCT_MODULE_NAME|default:param.module }}
{% else %}
{{ item.module }}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro className item %}{% filter removeNewlines %}
{% set module %}{% call moduleName item %}{% endset %}
{% if module and ( not param.ignoreTargetModule or module != env.PRODUCT_MODULE_NAME and module != param.module ) %}
{{module}}.
{% endif %}
{{item.type}}
{% endfilter %}{% endmacro %}
{{accessModifier}} enum {{param.enumName|default:"StoryboardScene"}} {
{% for storyboard in storyboards %}
{% set storyboardName %}{{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}{% endset %}
{{accessModifier}} enum {{storyboardName}}: StoryboardType {
{{accessModifier}} static let storyboardName = "{{storyboard.name}}"
{% if storyboard.initialScene %}
{% set sceneClass %}{% call className storyboard.initialScene %}{% endset %}
{{accessModifier}} static let initialScene = InitialSceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self)
{% endif %}
{% for scene in storyboard.scenes %}
{% set sceneID %}{{scene.identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set sceneClass %}{% call className scene %}{% endset %}
{{accessModifier}} static let {{sceneID}} = SceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self, identifier: "{{scene.identifier}}")
{% endfor %}
}
{% endfor %}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
// MARK: - Implementation Details
{{accessModifier}} protocol StoryboardType {
static var storyboardName: String { get }
}
{{accessModifier}} extension StoryboardType {
static var storyboard: {{prefix}}Storyboard {
let name = {% if isAppKit %}NSStoryboard.Name({% endif %}self.storyboardName{% if isAppKit %}){% endif %}
{% if param.lookupFunction %}
return {{param.lookupFunction}}(name)
{% else %}
return {{prefix}}Storyboard(name: name, bundle: {{param.bundle|default:"BundleToken.bundle"}})
{% endif %}
}
}
{{accessModifier}} struct SceneType<T{% if not isAppKit %}: UIViewController{% endif %}> {
{{accessModifier}} let storyboard: StoryboardType.Type
{{accessModifier}} let identifier: String
{{accessModifier}} func instantiate() -> T {
let identifier = {% if isAppKit %}NSStoryboard.SceneIdentifier({% endif %}self.identifier{% if isAppKit %}){% endif %}
guard let controller = storyboard.storyboard.instantiate{{controller}}(withIdentifier: identifier) as? T else {
fatalError("{{controller}} '\(identifier)' is not of the expected class \(T.self).")
}
return controller
}
{% if isAppKit %}
@available(macOS 10.15, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController {
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
}
@available(macOS 10.15, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController {
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
}
{% else %}
@available(iOS 13.0, tvOS 13.0, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T {
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
}
{% endif %}
}
{{accessModifier}} struct InitialSceneType<T{% if not isAppKit %}: UIViewController{% endif %}> {
{{accessModifier}} let storyboard: StoryboardType.Type
{{accessModifier}} func instantiate() -> T {
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}() as? T else {
fatalError("{{controller}} is not of the expected class \(T.self).")
}
return controller
}
{% if isAppKit %}
@available(macOS 10.15, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController {
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
}
return controller
}
@available(macOS 10.15, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController {
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
}
return controller
}
{% else %}
@available(iOS 13.0, tvOS 13.0, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T {
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
}
return controller
}
{% endif %}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% elif storyboards %}
// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately
{% else %}
// No storyboard found
{% endif %}

View file

@ -0,0 +1,159 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if platform and storyboards %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %}
{% set prefix %}{% if isAppKit %}NS{% else %}UI{% endif %}{% endset %}
{% set controller %}{% if isAppKit %}Controller{% else %}ViewController{% endif %}{% endset %}
// swiftlint:disable sorted_imports
import Foundation
{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %}
import {{module}}
{% endfor %}
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length implicit_return
// MARK: - Storyboard Scenes
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
{% macro moduleName item %}{% filter removeNewlines %}
{% if item.moduleIsPlaceholder %}
{{ env.PRODUCT_MODULE_NAME|default:param.module }}
{% else %}
{{ item.module }}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro className item %}{% filter removeNewlines %}
{% set module %}{% call moduleName item %}{% endset %}
{% if module and ( not param.ignoreTargetModule or module != env.PRODUCT_MODULE_NAME and module != param.module ) %}
{{module}}.
{% endif %}
{{item.type}}
{% endfilter %}{% endmacro %}
{{accessModifier}} enum {{param.enumName|default:"StoryboardScene"}} {
{% for storyboard in storyboards %}
{% set storyboardName %}{{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}{% endset %}
{{accessModifier}} enum {{storyboardName}}: StoryboardType {
{{accessModifier}} static let storyboardName = "{{storyboard.name}}"
{% if storyboard.initialScene %}
{% set sceneClass %}{% call className storyboard.initialScene %}{% endset %}
{{accessModifier}} static let initialScene = InitialSceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self)
{% endif %}
{% for scene in storyboard.scenes %}
{% set sceneID %}{{scene.identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set sceneClass %}{% call className scene %}{% endset %}
{{accessModifier}} static let {{sceneID}} = SceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self, identifier: "{{scene.identifier}}")
{% endfor %}
}
{% endfor %}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
// MARK: - Implementation Details
{{accessModifier}} protocol StoryboardType {
static var storyboardName: String { get }
}
{{accessModifier}} extension StoryboardType {
static var storyboard: {{prefix}}Storyboard {
let name = {% if isAppKit %}NSStoryboard.Name({% endif %}self.storyboardName{% if isAppKit %}){% endif %}
{% if param.lookupFunction %}
return {{param.lookupFunction}}(name)
{% else %}
return {{prefix}}Storyboard(name: name, bundle: {{param.bundle|default:"BundleToken.bundle"}})
{% endif %}
}
}
{{accessModifier}} struct SceneType<T{% if not isAppKit %}: UIViewController{% endif %}> {
{{accessModifier}} let storyboard: StoryboardType.Type
{{accessModifier}} let identifier: String
{{accessModifier}} func instantiate() -> T {
let identifier = {% if isAppKit %}NSStoryboard.SceneIdentifier({% endif %}self.identifier{% if isAppKit %}){% endif %}
guard let controller = storyboard.storyboard.instantiate{{controller}}(withIdentifier: identifier) as? T else {
fatalError("{{controller}} '\(identifier)' is not of the expected class \(T.self).")
}
return controller
}
{% if isAppKit %}
@available(macOS 10.15, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController {
let identifier = NSStoryboard.SceneIdentifier(self.identifier)
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
}
@available(macOS 10.15, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController {
let identifier = NSStoryboard.SceneIdentifier(self.identifier)
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
}
{% else %}
@available(iOS 13.0, tvOS 13.0, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T {
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
}
{% endif %}
}
{{accessModifier}} struct InitialSceneType<T{% if not isAppKit %}: UIViewController{% endif %}> {
{{accessModifier}} let storyboard: StoryboardType.Type
{{accessModifier}} func instantiate() -> T {
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}() as? T else {
fatalError("{{controller}} is not of the expected class \(T.self).")
}
return controller
}
{% if isAppKit %}
@available(macOS 10.15, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController {
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
}
return controller
}
@available(macOS 10.15, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController {
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
}
return controller
}
{% else %}
@available(iOS 13.0, tvOS 13.0, *)
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T {
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
}
return controller
}
{% endif %}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% elif storyboards %}
// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately
{% else %}
// No storyboard found
{% endif %}

View file

@ -0,0 +1,60 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if platform and storyboards %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %}
// swiftlint:disable sorted_imports
import Foundation
{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %}
import {{module}}
{% endfor %}
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - Storyboard Segues
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
{{accessModifier}} enum {{param.enumName|default:"StoryboardSegue"}} {
{% for storyboard in storyboards where storyboard.segues %}
{{accessModifier}} enum {{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}: String, SegueType {
{% for segue in storyboard.segues %}
{% set segueID %}{{segue.identifier|swiftIdentifier:"pretty"|lowerFirstWord}}{% endset %}
case {{segueID|escapeReservedKeywords}}{% if segueID != segue.identifier %} = "{{segue.identifier}}"{% endif %}
{% endfor %}
}
{% endfor %}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
// MARK: - Implementation Details
{{accessModifier}} protocol SegueType: RawRepresentable {}
{{accessModifier}} extension {% if isAppKit %}NSSeguePerforming{% else %}UIViewController{% endif %} {
func perform<S: SegueType>(segue: S, sender: Any? = nil) where S.RawValue == String {
let identifier = {% if isAppKit %}NSStoryboardSegue.Identifier({% endif %}segue.rawValue{% if isAppKit %}){% endif %}
performSegue{% if isAppKit %}?{% endif %}(withIdentifier: identifier, sender: sender)
}
}
{{accessModifier}} extension SegueType where RawValue == String {
init?(_ segue: {% if isAppKit %}NS{% else %}UI{% endif %}StoryboardSegue) {
{% if isAppKit %}
#if swift(>=4.2)
guard let identifier = segue.identifier else { return nil }
#else
guard let identifier = segue.identifier?.rawValue else { return nil }
#endif
{% else %}
guard let identifier = segue.identifier else { return nil }
{% endif %}
self.init(rawValue: identifier)
}
}
{% elif storyboards %}
// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately
{% else %}
// No storyboard found
{% endif %}

View file

@ -0,0 +1,60 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if platform and storyboards %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %}
// swiftlint:disable sorted_imports
import Foundation
{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %}
import {{module}}
{% endfor %}
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - Storyboard Segues
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
{{accessModifier}} enum {{param.enumName|default:"StoryboardSegue"}} {
{% for storyboard in storyboards where storyboard.segues %}
{{accessModifier}} enum {{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}: String, SegueType {
{% for segue in storyboard.segues %}
{% set segueID %}{{segue.identifier|swiftIdentifier:"pretty"|lowerFirstWord}}{% endset %}
case {{segueID|escapeReservedKeywords}}{% if segueID != segue.identifier %} = "{{segue.identifier}}"{% endif %}
{% endfor %}
}
{% endfor %}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
// MARK: - Implementation Details
{{accessModifier}} protocol SegueType: RawRepresentable {}
{{accessModifier}} extension {% if isAppKit %}NSSeguePerforming{% else %}UIViewController{% endif %} {
func perform<S: SegueType>(segue: S, sender: Any? = nil) where S.RawValue == String {
let identifier = {% if isAppKit %}NSStoryboardSegue.Identifier({% endif %}segue.rawValue{% if isAppKit %}){% endif %}
performSegue{% if isAppKit %}?{% endif %}(withIdentifier: identifier, sender: sender)
}
}
{{accessModifier}} extension SegueType where RawValue == String {
init?(_ segue: {% if isAppKit %}NS{% else %}UI{% endif %}StoryboardSegue) {
{% if isAppKit %}
#if swift(>=4.2)
guard let identifier = segue.identifier else { return nil }
#else
guard let identifier = segue.identifier?.rawValue else { return nil }
#endif
{% else %}
guard let identifier = segue.identifier else { return nil }
{% endif %}
self.init(rawValue: identifier)
}
}
{% elif storyboards %}
// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately
{% else %}
// No storyboard found
{% endif %}

View file

@ -0,0 +1,82 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - JSON Files
{% macro fileBlock file %}
{% call documentBlock file file.document %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% elif document.metadata.type == "Dictionary" %}
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value document.data %}
{% endfor %}
{% else %}
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% elif metadata.type == "Optional" %}
Any?
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
{% endfilter %}{% endmacro %}
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "String" %}
"{{ value }}"
{% elif metadata.type == "Optional" %}
nil
{% elif metadata.type == "Array" and value %}
[{% for value in value %}
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
{{ ", " if not forloop.last }}
{% endfor %}]
{% elif metadata.type == "Dictionary" %}
[{% for key,value in value %}
"{{key}}": {% call valueBlock value metadata.properties[key] %}
{{ ", " if not forloop.last }}
{% empty %}
:
{% endfor %}]
{% elif metadata.type == "Bool" %}
{% if value %}true{% else %}false{% endif %}
{% else %}
{{ value }}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length number_separator type_body_length
{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length number_separator type_body_length
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,82 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - JSON Files
{% macro fileBlock file %}
{% call documentBlock file file.document %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% elif document.metadata.type == "Dictionary" %}
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value document.data %}
{% endfor %}
{% else %}
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% elif metadata.type == "Optional" %}
Any?
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
{% endfilter %}{% endmacro %}
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "String" %}
"{{ value }}"
{% elif metadata.type == "Optional" %}
nil
{% elif metadata.type == "Array" and value %}
[{% for value in value %}
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
{{ ", " if not forloop.last }}
{% endfor %}]
{% elif metadata.type == "Dictionary" %}
[{% for key,value in value %}
"{{key}}": {% call valueBlock value metadata.properties[key] %}
{{ ", " if not forloop.last }}
{% empty %}
:
{% endfor %}]
{% elif metadata.type == "Bool" %}
{% if value %}true{% else %}false{% endif %}
{% else %}
{{ value }}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length number_separator type_body_length
{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length number_separator type_body_length
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,112 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - JSON Files
{% macro fileBlock file %}
{% call documentBlock file file.document %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}")
{% elif document.metadata.type == "Dictionary" %}
private static let _document = JSONDocument(path: "{% call transformPath file.path %}")
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value %}
{% endfor %}
{% else %}
{{accessModifier}} static let value: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}")
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% elif metadata.type == "Optional" %}
Any?
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = _document["{{key}}"]
{% endfilter %}{% endmacro %}
{% macro transformPath path %}{% filter removeNewlines %}
{% if param.preservePath %}
{{path}}
{% else %}
{{path|basename}}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length type_body_length
{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length type_body_length
// MARK: - Implementation Details
private func objectFromJSON<T>(at path: String) -> T {
{% if param.lookupFunction %}
guard let url = {{param.lookupFunction}}(path),
{% else %}
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
{% endif %}
let json = try? JSONSerialization.jsonObject(with: Data(contentsOf: url), options: []),
let result = json as? T else {
fatalError("Unable to load JSON at path: \(path)")
}
return result
}
private struct JSONDocument {
let data: [String: Any]
init(path: String) {
self.data = objectFromJSON(at: path)
}
subscript<T>(key: String) -> T {
guard let result = data[key] as? T else {
fatalError("Property '\(key)' is not of type \(T.self)")
}
return result
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,112 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - JSON Files
{% macro fileBlock file %}
{% call documentBlock file file.document %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}")
{% elif document.metadata.type == "Dictionary" %}
private static let _document = JSONDocument(path: "{% call transformPath file.path %}")
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value %}
{% endfor %}
{% else %}
{{accessModifier}} static let value: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}")
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% elif metadata.type == "Optional" %}
Any?
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = _document["{{key}}"]
{% endfilter %}{% endmacro %}
{% macro transformPath path %}{% filter removeNewlines %}
{% if param.preservePath %}
{{path}}
{% else %}
{{path|basename}}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length type_body_length
{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length type_body_length
// MARK: - Implementation Details
private func objectFromJSON<T>(at path: String) -> T {
{% if param.lookupFunction %}
guard let url = {{param.lookupFunction}}(path),
{% else %}
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
{% endif %}
let json = try? JSONSerialization.jsonObject(with: Data(contentsOf: url), options: []),
let result = json as? T else {
fatalError("Unable to load JSON at path: \(path)")
}
return result
}
private struct JSONDocument {
let data: [String: Any]
init(path: String) {
self.data = objectFromJSON(at: path)
}
subscript<T>(key: String) -> T {
guard let result = data[key] as? T else {
fatalError("Property '\(key)' is not of type \(T.self)")
}
return result
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,82 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - Plist Files
{% macro fileBlock file %}
{% call documentBlock file file.document %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% elif document.metadata.type == "Dictionary" %}
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value document.data %}
{% endfor %}
{% else %}
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
{% endfilter %}{% endmacro %}
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "String" %}
"{{ value }}"
{% elif metadata.type == "Date" %}
Date(timeIntervalSinceReferenceDate: {{ value.timeIntervalSinceReferenceDate }})
{% elif metadata.type == "Optional" %}
nil
{% elif metadata.type == "Array" and value %}
[{% for value in value %}
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
{{ ", " if not forloop.last }}
{% endfor %}]
{% elif metadata.type == "Dictionary" %}
[{% for key,value in value %}
"{{key}}": {% call valueBlock value metadata.properties[key] %}
{{ ", " if not forloop.last }}
{% empty %}
:
{% endfor %}]
{% elif metadata.type == "Bool" %}
{% if value %}true{% else %}false{% endif %}
{% else %}
{{ value }}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length number_separator type_body_length
{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length number_separator type_body_length
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,82 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - Plist Files
{% macro fileBlock file %}
{% call documentBlock file file.document %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% elif document.metadata.type == "Dictionary" %}
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value document.data %}
{% endfor %}
{% else %}
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
{% endfilter %}{% endmacro %}
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "String" %}
"{{ value }}"
{% elif metadata.type == "Date" %}
Date(timeIntervalSinceReferenceDate: {{ value.timeIntervalSinceReferenceDate }})
{% elif metadata.type == "Optional" %}
nil
{% elif metadata.type == "Array" and value %}
[{% for value in value %}
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
{{ ", " if not forloop.last }}
{% endfor %}]
{% elif metadata.type == "Dictionary" %}
[{% for key,value in value %}
"{{key}}": {% call valueBlock value metadata.properties[key] %}
{{ ", " if not forloop.last }}
{% empty %}
:
{% endfor %}]
{% elif metadata.type == "Bool" %}
{% if value %}true{% else %}false{% endif %}
{% else %}
{{ value }}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length number_separator type_body_length
{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length number_separator type_body_length
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,117 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - Plist Files
{% macro fileBlock file %}
{% call documentBlock file file.document %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = arrayFromPlist(at: "{% call transformPath file.path %}")
{% elif document.metadata.type == "Dictionary" %}
private static let _document = PlistDocument(path: "{% call transformPath file.path %}")
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value %}
{% endfor %}
{% else %}
// Unsupported root type `{{rootType}}`
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = _document["{{key}}"]
{% endfilter %}{% endmacro %}
{% macro transformPath path %}{% filter removeNewlines %}
{% if param.preservePath %}
{{path}}
{% else %}
{{path|basename}}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length type_body_length
{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length type_body_length
// MARK: - Implementation Details
private func arrayFromPlist<T>(at path: String) -> [T] {
{% if param.lookupFunction %}
guard let url = {{param.lookupFunction}}(path),
{% else %}
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
{% endif %}
let data = NSArray(contentsOf: url) as? [T] else {
fatalError("Unable to load PLIST at path: \(path)")
}
return data
}
private struct PlistDocument {
let data: [String: Any]
init(path: String) {
{% if param.lookupFunction %}
guard let url = {{param.lookupFunction}}(path),
{% else %}
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
{% endif %}
let data = NSDictionary(contentsOf: url) as? [String: Any] else {
fatalError("Unable to load PLIST at path: \(path)")
}
self.data = data
}
subscript<T>(key: String) -> T {
guard let result = data[key] as? T else {
fatalError("Property '\(key)' is not of type \(T.self)")
}
return result
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,117 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - Plist Files
{% macro fileBlock file %}
{% call documentBlock file file.document %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = arrayFromPlist(at: "{% call transformPath file.path %}")
{% elif document.metadata.type == "Dictionary" %}
private static let _document = PlistDocument(path: "{% call transformPath file.path %}")
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value %}
{% endfor %}
{% else %}
// Unsupported root type `{{rootType}}`
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = _document["{{key}}"]
{% endfilter %}{% endmacro %}
{% macro transformPath path %}{% filter removeNewlines %}
{% if param.preservePath %}
{{path}}
{% else %}
{{path|basename}}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length type_body_length
{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length type_body_length
// MARK: - Implementation Details
private func arrayFromPlist<T>(at path: String) -> [T] {
{% if param.lookupFunction %}
guard let url = {{param.lookupFunction}}(path),
{% else %}
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
{% endif %}
let data = NSArray(contentsOf: url) as? [T] else {
fatalError("Unable to load PLIST at path: \(path)")
}
return data
}
private struct PlistDocument {
let data: [String: Any]
init(path: String) {
{% if param.lookupFunction %}
guard let url = {{param.lookupFunction}}(path),
{% else %}
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
{% endif %}
let data = NSDictionary(contentsOf: url) as? [String: Any] else {
fatalError("Unable to load PLIST at path: \(path)")
}
self.data = data
}
subscript<T>(key: String) -> T {
guard let result = data[key] as? T else {
fatalError("Property '\(key)' is not of type \(T.self)")
}
return result
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,99 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if tables.count > 0 %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Strings
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
_ p{{forloop.counter}}: Any
{% else %}
_ p{{forloop.counter}}: {{type}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
String(describing: p{{forloop.counter}})
{% elif type == "UnsafeRawPointer" %}
Int(bitPattern: p{{forloop.counter}})
{% else %}
p{{forloop.counter}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro recursiveBlock table item %}
{% for string in item.strings %}
{% if not param.noComments %}
{% for line in string.translation|split:"\n" %}
/// {{line}}
{% endfor %}
{% endif %}
{% if string.types %}
{{accessModifier}} static func {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
}
{% elif param.lookupFunction %}
{# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #}
{{accessModifier}} static var {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") }
{% else %}
{{accessModifier}} static let {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}")
{% endif %}
{% endfor %}
{% for child in item.children %}
{% call recursiveBlock table child %}
{% endfor %}
{% endmacro %}
// swiftlint:disable function_parameter_count identifier_name line_length type_body_length
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
{{accessModifier}} enum {{enumName}} {
{% if tables.count > 1 or param.forceFileNameEnum %}
{% for table in tables %}
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call recursiveBlock tables.first.name tables.first.levels %}
{% endif %}
}
// swiftlint:enable function_parameter_count identifier_name line_length type_body_length
// MARK: - Implementation Details
extension {{enumName}} {
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
{% if param.lookupFunction %}
let format = {{ param.lookupFunction }}(key, table)
{% else %}
let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table)
{% endif %}
return String(format: format, locale: Locale.current, arguments: args)
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No string found
{% endif %}

View file

@ -0,0 +1,99 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if tables.count > 0 %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Strings
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
_ p{{forloop.counter}}: Any
{% else %}
_ p{{forloop.counter}}: {{type}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
String(describing: p{{forloop.counter}})
{% elif type == "UnsafeRawPointer" %}
Int(bitPattern: p{{forloop.counter}})
{% else %}
p{{forloop.counter}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro recursiveBlock table item %}
{% for string in item.strings %}
{% if not param.noComments %}
{% for line in string.translation|split:"\n" %}
/// {{line}}
{% endfor %}
{% endif %}
{% if string.types %}
{{accessModifier}} static func {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
}
{% elif param.lookupFunction %}
{# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #}
{{accessModifier}} static var {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") }
{% else %}
{{accessModifier}} static let {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}")
{% endif %}
{% endfor %}
{% for child in item.children %}
{% call recursiveBlock table child %}
{% endfor %}
{% endmacro %}
// swiftlint:disable function_parameter_count identifier_name line_length type_body_length
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
{{accessModifier}} enum {{enumName}} {
{% if tables.count > 1 or param.forceFileNameEnum %}
{% for table in tables %}
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call recursiveBlock tables.first.name tables.first.levels %}
{% endif %}
}
// swiftlint:enable function_parameter_count identifier_name line_length type_body_length
// MARK: - Implementation Details
extension {{enumName}} {
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
{% if param.lookupFunction %}
let format = {{ param.lookupFunction }}(key, table)
{% else %}
let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table)
{% endif %}
return String(format: format, locale: Locale.current, arguments: args)
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No string found
{% endif %}

View file

@ -0,0 +1,68 @@
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if tables.count > 0 %}
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
({% call paramTranslate type %})p{{ forloop.counter }}{{ " :" if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
p{{forloop.counter}}{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro paramTranslate swiftType %}
{% if swiftType == "Any" %}
id
{% elif swiftType == "CChar" %}
char
{% elif swiftType == "Float" %}
float
{% elif swiftType == "Int" %}
NSInteger
{% elif swiftType == "String" %}
id
{% elif swiftType == "UnsafePointer<CChar>" %}
char*
{% elif swiftType == "UnsafeRawPointer" %}
void*
{% else %}
objc-h.stencil is missing '{{swiftType}}'
{% endif %}
{% endmacro %}
{% macro emitOneMethod table item %}
{% for string in item.strings %}
{% if not param.noComments %}
{% for line in string.translation|split:"\n" %}
/// {{line}}
{% endfor %}
{% endif %}
{% if string.types %}
{% if string.types.count == 1 %}
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValue:{% call parametersBlock string.types %};
{% else %}
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValues:{% call parametersBlock string.types %};
{% endif %}
{% else %}
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}};
{% endif %}
{% endfor %}
{% for child in item.children %}
{% call emitOneMethod table child %}
{% endfor %}
{% endmacro %}
{% for table in tables %}
@interface {{ table.name }} : NSObject
{% call emitOneMethod table.name table.levels %}
@end
{% endfor %}
NS_ASSUME_NONNULL_END
{% else %}
// No strings found
{% endif %}

View file

@ -0,0 +1,90 @@
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if tables.count > 0 %}
#import "{{ param.headerName|default:"Localizable.h" }}"
{% if not param.bundle %}
@interface BundleToken : NSObject
@end
@implementation BundleToken
@end
{% endif %}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
static NSString* tr(NSString *tableName, NSString *key, ...) {
NSBundle *bundle = {{param.bundle|default:"[NSBundle bundleForClass:BundleToken.class]"}};
NSString *format = [bundle localizedStringForKey:key value:nil table:tableName];
NSLocale *locale = [NSLocale currentLocale];
va_list args;
va_start(args, key);
NSString *result = [[NSString alloc] initWithFormat:format locale:locale arguments:args];
va_end(args);
return result;
};
#pragma clang diagnostic pop
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
({% call paramTranslate type %})p{{ forloop.counter }}{{ " :" if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
p{{forloop.counter}}{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro paramTranslate swiftType %}
{% if swiftType == "Any" %}
id
{% elif swiftType == "CChar" %}
char
{% elif swiftType == "Float" %}
float
{% elif swiftType == "Int" %}
NSInteger
{% elif swiftType == "String" %}
id
{% elif swiftType == "UnsafePointer<CChar>" %}
char*
{% elif swiftType == "UnsafeRawPointer" %}
void*
{% else %}
objc-m.stencil is missing '{{swiftType}}'
{% endif %}
{% endmacro %}
{% macro tableContents table item %}
{% for string in item.strings %}
{% if string.types %}
{% if string.types.count == 1 %}
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValue:{% call parametersBlock string.types %}
{% else %}
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValues:{% call parametersBlock string.types %}
{% endif %}
{
return tr(@"{{table}}", @"{{string.key}}", {% call argumentsBlock string.types %});
}
{% else %}
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}} {
return tr(@"{{table}}", @"{{string.key}}");
}
{% endif %}
{% endfor %}
{% for child in item.children %}
{% call tableContents table child %}
{% endfor %}
{% endmacro %}
{% for table in tables %}
{% set tableName %}{{table.name|default:"Localized"}}{% endset %}
@implementation {{ tableName }} : NSObject
{% call tableContents table.name table.levels %}
@end
{% endfor %}
{% else %}
// No strings found
{% endif %}

View file

@ -0,0 +1,104 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if tables.count > 0 %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Strings
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
_ p{{forloop.counter}}: Any
{% else %}
_ p{{forloop.counter}}: {{type}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
String(describing: p{{forloop.counter}})
{% elif type == "UnsafeRawPointer" %}
Int(bitPattern: p{{forloop.counter}})
{% else %}
p{{forloop.counter}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro recursiveBlock table item %}
{% for string in item.strings %}
{% if not param.noComments %}
{% for line in string.translation|split:"\n" %}
/// {{line}}
{% endfor %}
{% endif %}
{% if string.types %}
{{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
}
{% elif param.lookupFunction %}
{# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #}
{{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") }
{% else %}
{{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}")
{% endif %}
{% endfor %}
{% for child in item.children %}
{{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %}
}
{% endfor %}
{% endmacro %}
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
{{accessModifier}} enum {{enumName}} {
{% if tables.count > 1 or param.forceFileNameEnum %}
{% for table in tables %}
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call recursiveBlock tables.first.name tables.first.levels %}
{% endif %}
}
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
// MARK: - Implementation Details
extension {{enumName}} {
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
{% if param.lookupFunction %}
let format = {{ param.lookupFunction }}(key, table)
{% else %}
let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table)
{% endif %}
return String(format: format, locale: Locale.current, arguments: args)
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No string found
{% endif %}

View file

@ -0,0 +1,104 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if tables.count > 0 %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Strings
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
_ p{{forloop.counter}}: Any
{% else %}
_ p{{forloop.counter}}: {{type}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
String(describing: p{{forloop.counter}})
{% elif type == "UnsafeRawPointer" %}
Int(bitPattern: p{{forloop.counter}})
{% else %}
p{{forloop.counter}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro recursiveBlock table item %}
{% for string in item.strings %}
{% if not param.noComments %}
{% for line in string.translation|split:"\n" %}
/// {{line}}
{% endfor %}
{% endif %}
{% if string.types %}
{{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
}
{% elif param.lookupFunction %}
{# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #}
{{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") }
{% else %}
{{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}")
{% endif %}
{% endfor %}
{% for child in item.children %}
{{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %}
}
{% endfor %}
{% endmacro %}
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
{{accessModifier}} enum {{enumName}} {
{% if tables.count > 1 or param.forceFileNameEnum %}
{% for table in tables %}
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call recursiveBlock tables.first.name tables.first.levels %}
{% endif %}
}
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
// MARK: - Implementation Details
extension {{enumName}} {
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
{% if param.lookupFunction %}
let format = {{ param.lookupFunction }}(key, table)
{% else %}
let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table)
{% endif %}
return String(format: format, locale: Locale.current, arguments: args)
}
}
{% if not param.bundle and not param.lookupFunction %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No string found
{% endif %}

View file

@ -0,0 +1,329 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if catalogs %}
{% set enumName %}{{param.enumName|default:"Asset"}}{% endset %}
{% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %}
{% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %}
{% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %}
{% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %}
{% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %}
{% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
#if os(macOS)
import AppKit
#elseif os(iOS)
{% if resourceCount.arresourcegroup > 0 %}
import ARKit
{% endif %}
import UIKit
#elseif os(tvOS) || os(watchOS)
import UIKit
#endif
// Deprecated typealiases
{% if resourceCount.color > 0 %}
@available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0")
{{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color
{% endif %}
{% if resourceCount.image > 0 %}
@available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0")
{{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image
{% endif %}
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Asset Catalogs
{% macro enumBlock assets %}
{% call casesBlock assets %}
{% if param.allValues %}
// swiftlint:disable trailing_comma
{% if resourceCount.arresourcegroup > 0 %}
{{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %}
]
{% endif %}
{% if resourceCount.color > 0 %}
{{accessModifier}} static let allColors: [{{colorType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %}
]
{% endif %}
{% if resourceCount.data > 0 %}
{{accessModifier}} static let allDataAssets: [{{dataType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %}
]
{% endif %}
{% if resourceCount.image > 0 %}
{{accessModifier}} static let allImages: [{{imageType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %}
]
{% endif %}
{% if resourceCount.symbol > 0 %}
{{accessModifier}} static let allSymbols: [{{symbolType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %}
]
{% endif %}
// swiftlint:enable trailing_comma
{% endif %}
{% endmacro %}
{% macro casesBlock assets %}
{% for asset in assets %}
{% if asset.type == "arresourcegroup" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}")
{% elif asset.type == "color" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}")
{% elif asset.type == "data" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}")
{% elif asset.type == "image" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}")
{% elif asset.type == "symbol" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}")
{% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
{{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
}
{% elif asset.items %}
{% call casesBlock asset.items %}
{% endif %}
{% endfor %}
{% endmacro %}
{% macro allValuesBlock assets filter prefix %}
{% for asset in assets %}
{% if asset.type == filter %}
{{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}},
{% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
{% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %}
{% call allValuesBlock asset.items filter prefix2 %}
{% elif asset.items %}
{% call allValuesBlock asset.items filter prefix %}
{% endif %}
{% endfor %}
{% endmacro %}
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
{{accessModifier}} enum {{enumName}} {
{% if catalogs.count > 1 or param.forceFileNameEnum %}
{% for catalog in catalogs %}
{{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call enumBlock catalogs.first.assets %}
{% endif %}
}
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
// MARK: - Implementation Details
{% if resourceCount.arresourcegroup > 0 %}
{{accessModifier}} struct {{arResourceGroupType}} {
{{accessModifier}} fileprivate(set) var name: String
#if os(iOS)
@available(iOS 11.3, *)
{{accessModifier}} var referenceImages: Set<ARReferenceImage> {
return ARReferenceImage.referenceImages(in: self)
}
@available(iOS 12.0, *)
{{accessModifier}} var referenceObjects: Set<ARReferenceObject> {
return ARReferenceObject.referenceObjects(in: self)
}
#endif
}
#if os(iOS)
@available(iOS 11.3, *)
{{accessModifier}} extension ARReferenceImage {
static func referenceImages(in asset: {{arResourceGroupType}}) -> Set<ARReferenceImage> {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set()
}
}
@available(iOS 12.0, *)
{{accessModifier}} extension ARReferenceObject {
static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set<ARReferenceObject> {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set()
}
}
#endif
{% endif %}
{% if resourceCount.color > 0 %}
{{accessModifier}} final class {{colorType}} {
{{accessModifier}} fileprivate(set) var name: String
#if os(macOS)
{{accessModifier}} typealias Color = NSColor
#elseif os(iOS) || os(tvOS) || os(watchOS)
{{accessModifier}} typealias Color = UIColor
#endif
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
{{accessModifier}} private(set) lazy var color: Color = Color(asset: self)
#if os(iOS) || os(tvOS)
@available(iOS 11.0, tvOS 11.0, *)
{{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else {
fatalError("Unable to load color asset named \(name).")
}
return color
}
#endif
fileprivate init(name: String) {
self.name = name
}
}
{{accessModifier}} extension {{colorType}}.Color {
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
convenience init!(asset: {{colorType}}) {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
#if os(iOS) || os(tvOS)
self.init(named: asset.name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
self.init(named: NSColor.Name(asset.name), bundle: bundle)
#elseif os(watchOS)
self.init(named: asset.name)
#endif
}
}
{% endif %}
{% if resourceCount.data > 0 %}
{{accessModifier}} struct {{dataType}} {
{{accessModifier}} fileprivate(set) var name: String
@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *)
{{accessModifier}} var data: NSDataAsset {
return NSDataAsset(asset: self)
}
}
@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *)
{{accessModifier}} extension NSDataAsset {
convenience init!(asset: {{dataType}}) {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
#if os(iOS) || os(tvOS) || os(watchOS)
self.init(name: asset.name, bundle: bundle)
#elseif os(macOS)
self.init(name: NSDataAsset.Name(asset.name), bundle: bundle)
#endif
}
}
{% endif %}
{% if resourceCount.image > 0 %}
{{accessModifier}} struct {{imageType}} {
{{accessModifier}} fileprivate(set) var name: String
#if os(macOS)
{{accessModifier}} typealias Image = NSImage
#elseif os(iOS) || os(tvOS) || os(watchOS)
{{accessModifier}} typealias Image = UIImage
#endif
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *)
{{accessModifier}} var image: Image {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
#if os(iOS) || os(tvOS)
let image = Image(named: name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
let name = NSImage.Name(self.name)
let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name)
#elseif os(watchOS)
let image = Image(named: name)
#endif
guard let result = image else {
fatalError("Unable to load image asset named \(name).")
}
return result
}
#if os(iOS) || os(tvOS)
@available(iOS 8.0, tvOS 9.0, *)
{{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else {
fatalError("Unable to load image asset named \(name).")
}
return result
}
#endif
}
{{accessModifier}} extension {{imageType}}.Image {
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, *)
@available(macOS, deprecated,
message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property")
convenience init!(asset: {{imageType}}) {
#if os(iOS) || os(tvOS)
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
self.init(named: asset.name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
self.init(named: NSImage.Name(asset.name))
#elseif os(watchOS)
self.init(named: asset.name)
#endif
}
}
{% endif %}
{% if resourceCount.symbol > 0 %}
{{accessModifier}} struct {{symbolType}} {
{{accessModifier}} fileprivate(set) var name: String
#if os(iOS) || os(tvOS) || os(watchOS)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
{{accessModifier}} typealias Configuration = UIImage.SymbolConfiguration
{{accessModifier}} typealias Image = UIImage
@available(iOS 12.0, tvOS 12.0, watchOS 5.0, *)
{{accessModifier}} var image: Image {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
#if os(iOS) || os(tvOS)
let image = Image(named: name, in: bundle, compatibleWith: nil)
#elseif os(watchOS)
let image = Image(named: name)
#endif
guard let result = image else {
fatalError("Unable to load symbol asset named \(name).")
}
return result
}
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
{{accessModifier}} func image(with configuration: Configuration) -> Image {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
guard let result = Image(named: name, in: bundle, with: configuration) else {
fatalError("Unable to load symbol asset named \(name).")
}
return result
}
#endif
}
{% endif %}
{% if not param.bundle %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No assets found
{% endif %}

View file

@ -0,0 +1,337 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if catalogs %}
{% set enumName %}{{param.enumName|default:"Asset"}}{% endset %}
{% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %}
{% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %}
{% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %}
{% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %}
{% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %}
{% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
#if os(macOS)
import AppKit
#elseif os(iOS)
{% if resourceCount.arresourcegroup > 0 %}
import ARKit
{% endif %}
import UIKit
#elseif os(tvOS) || os(watchOS)
import UIKit
#endif
// Deprecated typealiases
{% if resourceCount.color > 0 %}
@available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0")
{{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color
{% endif %}
{% if resourceCount.image > 0 %}
@available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0")
{{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image
{% endif %}
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Asset Catalogs
{% macro enumBlock assets %}
{% call casesBlock assets %}
{% if param.allValues %}
// swiftlint:disable trailing_comma
{% if resourceCount.arresourcegroup > 0 %}
{{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %}
]
{% endif %}
{% if resourceCount.color > 0 %}
{{accessModifier}} static let allColors: [{{colorType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %}
]
{% endif %}
{% if resourceCount.data > 0 %}
{{accessModifier}} static let allDataAssets: [{{dataType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %}
]
{% endif %}
{% if resourceCount.image > 0 %}
{{accessModifier}} static let allImages: [{{imageType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %}
]
{% endif %}
{% if resourceCount.symbol > 0 %}
{{accessModifier}} static let allSymbols: [{{symbolType}}] = [
{% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %}
]
{% endif %}
// swiftlint:enable trailing_comma
{% endif %}
{% endmacro %}
{% macro casesBlock assets %}
{% for asset in assets %}
{% if asset.type == "arresourcegroup" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}")
{% elif asset.type == "color" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}")
{% elif asset.type == "data" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}")
{% elif asset.type == "image" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}")
{% elif asset.type == "symbol" %}
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}")
{% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
{{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
}
{% elif asset.items %}
{% call casesBlock asset.items %}
{% endif %}
{% endfor %}
{% endmacro %}
{% macro allValuesBlock assets filter prefix %}
{% for asset in assets %}
{% if asset.type == filter %}
{{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}},
{% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
{% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %}
{% call allValuesBlock asset.items filter prefix2 %}
{% elif asset.items %}
{% call allValuesBlock asset.items filter prefix %}
{% endif %}
{% endfor %}
{% endmacro %}
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
{{accessModifier}} enum {{enumName}} {
{% if catalogs.count > 1 or param.forceFileNameEnum %}
{% for catalog in catalogs %}
{{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call enumBlock catalogs.first.assets %}
{% endif %}
}
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
// MARK: - Implementation Details
{% if resourceCount.arresourcegroup > 0 %}
{{accessModifier}} struct {{arResourceGroupType}} {
{{accessModifier}} fileprivate(set) var name: String
#if os(iOS)
@available(iOS 11.3, *)
{{accessModifier}} var referenceImages: Set<ARReferenceImage> {
return ARReferenceImage.referenceImages(in: self)
}
@available(iOS 12.0, *)
{{accessModifier}} var referenceObjects: Set<ARReferenceObject> {
return ARReferenceObject.referenceObjects(in: self)
}
#endif
}
#if os(iOS)
@available(iOS 11.3, *)
{{accessModifier}} extension ARReferenceImage {
static func referenceImages(in asset: {{arResourceGroupType}}) -> Set<ARReferenceImage> {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set()
}
}
@available(iOS 12.0, *)
{{accessModifier}} extension ARReferenceObject {
static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set<ARReferenceObject> {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set()
}
}
#endif
{% endif %}
{% if resourceCount.color > 0 %}
{{accessModifier}} final class {{colorType}} {
{{accessModifier}} fileprivate(set) var name: String
#if os(macOS)
{{accessModifier}} typealias Color = NSColor
#elseif os(iOS) || os(tvOS) || os(watchOS)
{{accessModifier}} typealias Color = UIColor
#endif
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
{{accessModifier}} private(set) lazy var color: Color = {
guard let color = Color(asset: self) else {
fatalError("Unable to load color asset named \(name).")
}
return color
}()
#if os(iOS) || os(tvOS)
@available(iOS 11.0, tvOS 11.0, *)
{{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else {
fatalError("Unable to load color asset named \(name).")
}
return color
}
#endif
fileprivate init(name: String) {
self.name = name
}
}
{{accessModifier}} extension {{colorType}}.Color {
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
convenience init?(asset: {{colorType}}) {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
#if os(iOS) || os(tvOS)
self.init(named: asset.name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
self.init(named: NSColor.Name(asset.name), bundle: bundle)
#elseif os(watchOS)
self.init(named: asset.name)
#endif
}
}
{% endif %}
{% if resourceCount.data > 0 %}
{{accessModifier}} struct {{dataType}} {
{{accessModifier}} fileprivate(set) var name: String
@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *)
{{accessModifier}} var data: NSDataAsset {
guard let data = NSDataAsset(asset: self) else {
fatalError("Unable to load data asset named \(name).")
}
return data
}
}
@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *)
{{accessModifier}} extension NSDataAsset {
convenience init?(asset: {{dataType}}) {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
#if os(iOS) || os(tvOS) || os(watchOS)
self.init(name: asset.name, bundle: bundle)
#elseif os(macOS)
self.init(name: NSDataAsset.Name(asset.name), bundle: bundle)
#endif
}
}
{% endif %}
{% if resourceCount.image > 0 %}
{{accessModifier}} struct {{imageType}} {
{{accessModifier}} fileprivate(set) var name: String
#if os(macOS)
{{accessModifier}} typealias Image = NSImage
#elseif os(iOS) || os(tvOS) || os(watchOS)
{{accessModifier}} typealias Image = UIImage
#endif
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *)
{{accessModifier}} var image: Image {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
#if os(iOS) || os(tvOS)
let image = Image(named: name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
let name = NSImage.Name(self.name)
let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name)
#elseif os(watchOS)
let image = Image(named: name)
#endif
guard let result = image else {
fatalError("Unable to load image asset named \(name).")
}
return result
}
#if os(iOS) || os(tvOS)
@available(iOS 8.0, tvOS 9.0, *)
{{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else {
fatalError("Unable to load image asset named \(name).")
}
return result
}
#endif
}
{{accessModifier}} extension {{imageType}}.Image {
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, *)
@available(macOS, deprecated,
message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property")
convenience init?(asset: {{imageType}}) {
#if os(iOS) || os(tvOS)
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
self.init(named: asset.name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
self.init(named: NSImage.Name(asset.name))
#elseif os(watchOS)
self.init(named: asset.name)
#endif
}
}
{% endif %}
{% if resourceCount.symbol > 0 %}
{{accessModifier}} struct {{symbolType}} {
{{accessModifier}} fileprivate(set) var name: String
#if os(iOS) || os(tvOS) || os(watchOS)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
{{accessModifier}} typealias Configuration = UIImage.SymbolConfiguration
{{accessModifier}} typealias Image = UIImage
@available(iOS 12.0, tvOS 12.0, watchOS 5.0, *)
{{accessModifier}} var image: Image {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
#if os(iOS) || os(tvOS)
let image = Image(named: name, in: bundle, compatibleWith: nil)
#elseif os(watchOS)
let image = Image(named: name)
#endif
guard let result = image else {
fatalError("Unable to load symbol asset named \(name).")
}
return result
}
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
{{accessModifier}} func image(with configuration: Configuration) -> Image {
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
guard let result = Image(named: name, in: bundle, with: configuration) else {
fatalError("Unable to load symbol asset named \(name).")
}
return result
}
#endif
}
{% endif %}
{% if not param.bundle %}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
{% endif %}
{% else %}
// No assets found
{% endif %}

View file

@ -0,0 +1,92 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - YAML Files
{% macro fileBlock file %}
{% if file.documents.count > 1 %}
{% for document in file.documents %}
{% set documentName %}{{documentPrefix}}{{forloop.counter}}{% endset %}
{{accessModifier}} enum {{documentName|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call documentBlock file document %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call documentBlock file file.documents.first %}
{% endif %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% elif document.metadata.type == "Dictionary" %}
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value document.data %}
{% endfor %}
{% else %}
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% elif metadata.type == "Optional" %}
Any?
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
{% endfilter %}{% endmacro %}
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "String" %}
"{{ value }}"
{% elif metadata.type == "Optional" %}
nil
{% elif metadata.type == "Array" and value %}
[{% for value in value %}
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
{{ ", " if not forloop.last }}
{% endfor %}]
{% elif metadata.type == "Dictionary" %}
[{% for key,value in value %}
"{{key}}": {% call valueBlock value metadata.properties[key] %}
{{ ", " if not forloop.last }}
{% empty %}
:
{% endfor %}]
{% elif metadata.type == "Bool" %}
{% if value %}true{% else %}false{% endif %}
{% else %}
{{ value }}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length number_separator type_body_length
{{accessModifier}} enum {{param.enumName|default:"YAMLFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length number_separator type_body_length
{% else %}
// No files found
{% endif %}

View file

@ -0,0 +1,92 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if files %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length
// MARK: - YAML Files
{% macro fileBlock file %}
{% if file.documents.count > 1 %}
{% for document in file.documents %}
{% set documentName %}{{documentPrefix}}{{forloop.counter}}{% endset %}
{{accessModifier}} enum {{documentName|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call documentBlock file document %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call documentBlock file file.documents.first %}
{% endif %}
{% endmacro %}
{% macro documentBlock file document %}
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
{% if document.metadata.type == "Array" %}
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% elif document.metadata.type == "Dictionary" %}
{% for key,value in document.metadata.properties %}
{{accessModifier}} {% call propertyBlock key value document.data %}
{% endfor %}
{% else %}
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
{% endif %}
{% endmacro %}
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "Array" %}
[{% call typeBlock metadata.element %}]
{% elif metadata.type == "Dictionary" %}
[String: Any]
{% elif metadata.type == "Optional" %}
Any?
{% else %}
{{metadata.type}}
{% endif %}
{% endfilter %}{% endmacro %}
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
{% endfilter %}{% endmacro %}
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
{% if metadata.type == "String" %}
"{{ value }}"
{% elif metadata.type == "Optional" %}
nil
{% elif metadata.type == "Array" and value %}
[{% for value in value %}
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
{{ ", " if not forloop.last }}
{% endfor %}]
{% elif metadata.type == "Dictionary" %}
[{% for key,value in value %}
"{{key}}": {% call valueBlock value metadata.properties[key] %}
{{ ", " if not forloop.last }}
{% empty %}
:
{% endfor %}]
{% elif metadata.type == "Bool" %}
{% if value %}true{% else %}false{% endif %}
{% else %}
{{ value }}
{% endif %}
{% endfilter %}{% endmacro %}
// swiftlint:disable identifier_name line_length number_separator type_body_length
{{accessModifier}} enum {{param.enumName|default:"YAMLFiles"}} {
{% if files.count > 1 or param.forceFileNameEnum %}
{% for file in files %}
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call fileBlock files.first %}
{% endif %}
}
// swiftlint:enable identifier_name line_length number_separator type_body_length
{% else %}
// No files found
{% endif %}

BIN
.swiftgen/bin/swiftgen Executable file

Binary file not shown.

View file

@ -0,0 +1,29 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if families %}
import SwiftUI
{% for family in families %}
{% set identifierName %}{{family.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
{% set styleTypeName %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}Style{% endset %}
extension Font {
public static func {{identifierName}}(_ style: {{styleTypeName}}, fixedSize: CGFloat) -> Font {
return Font.custom(style.rawValue, fixedSize: fixedSize)
}
public static func {{identifierName}}(_ style: {{styleTypeName}}, size: CGFloat, relativeTo textStyle: TextStyle = .body) -> Font {
return Font.custom(style.rawValue, size: size, relativeTo: textStyle)
}
public enum {{styleTypeName}}: String {
{% for font in family.fonts %}
case {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = "{{font.name}}"
{% endfor %}
}
}
{% endfor %}
{% else %}
// No fonts found
{% endif %}
// swiftlint:enable all

View file

@ -0,0 +1,85 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if tables.count > 0 %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
import Foundation
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Strings
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
_ p{{forloop.counter}}: Any
{% else %}
_ p{{forloop.counter}}: {{type}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
{% for type in types %}
{% if type == "String" %}
String(describing: p{{forloop.counter}})
{% elif type == "UnsafeRawPointer" %}
Int(bitPattern: p{{forloop.counter}})
{% else %}
p{{forloop.counter}}
{% endif %}
{{ ", " if not forloop.last }}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro recursiveBlock table item %}
{% for string in item.strings %}
{% if not param.noComments %}
{% for line in string.translation|split:"\n" %}
/// {{line}}
{% endfor %}
{% endif %}
{% if string.types %}
{{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
{{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
}
{% else %}
{{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { {{enumName}}.tr("{{table}}", "{{string.key}}") }
{% endif %}
{% endfor %}
{% for child in item.children %}
{{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %}
}
{% endfor %}
{% endmacro %}
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
{{accessModifier}} enum {{enumName}} {
{% if tables.count > 1 or param.forceFileNameEnum %}
{% for table in tables %}
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
}
{% endfor %}
{% else %}
{% call recursiveBlock tables.first.name tables.first.levels %}
{% endif %}
}
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
// MARK: - Implementation Details
import Localize_Swift
extension {{enumName}} {
static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
let selectedLanguage = Localize.currentLanguage()
guard let path = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"),
let bundle = Bundle(path: path) else { return "Setup language error" }
return NSLocalizedString(key, tableName: table, bundle: bundle, comment: "")
}
}
{% endif %}
// swiftlint: enable all

View file

@ -0,0 +1,48 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if catalogs %}
import SwiftUI
{% macro casesBlock assets %}
{% for asset in assets %}
{% if asset.items and asset.isNamespaced == "true" %}
public enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
}
{% elif asset.items %}
{% call casesBlock asset.items %}
{% elif asset.type == "color" %}
public static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = Color("{{asset.value}}")
{% elif asset.type == "image" %}
public static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = Image("{{asset.value}}")
{% endif %}
{% endfor %}
{% endmacro %}
{% for catalog in catalogs %}
{% if catalog.name == "Colors" %}
extension Color {
{% for catalog in catalogs %}
{% if catalog.name == "Colors" %}
{% call casesBlock catalog.assets %}
{% endif %}
{% endfor %}
}
{% endif %}
{% endfor %}
{% for catalog in catalogs %}
{% if catalog.name == "Images" %}
extension Image {
{% for catalog in catalogs %}
{% if catalog.name == "Images" %}
{% call casesBlock catalog.assets %}
{% endif %}
{% endfor %}
}
{% endif %}
{% endfor %}
{% else %}
// No assets found
{% endif %}
// swiftlint: enable all

View file

@ -0,0 +1,36 @@
// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
{% if catalogs %}
import Foundation
typealias AssetStrings = String
{% macro casesBlock assets %}
{% for asset in assets %}
{% if asset.items and asset.isNamespaced == "true" %}
public enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
{% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
}
{% elif asset.items %}
{% call casesBlock asset.items %}
{% elif asset.type == "image" %}
public static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = String("{{asset.value}}")
{% endif %}
{% endfor %}
{% endmacro %}
{% for catalog in catalogs %}
{% if catalog.name == "Images" %}
extension String {
{% for catalog in catalogs %}
{% if catalog.name == "Images" %}
{% call casesBlock catalog.assets %}
{% endif %}
{% endfor %}
}
{% endif %}
{% endfor %}
{% else %}
// No assets found
{% endif %}
// swiftlint: enable all

115
.swiftlint.yml Normal file
View file

@ -0,0 +1,115 @@
---
colon:
severity: error
line_length:
ignores_comments: true
warning: 260
error: 300
type_body_length:
warning: 300
error: 500
file_length:
warning: 800
error: 1000
function_parameter_count:
warning: 20
error: 30
function_body_length:
warning: 120
error: 150
cyclomatic_complexity:
warning: 40
error: 50
nesting:
type_level:
warning: 3
error: 6
function_level:
warning: 500
error: 10
vertical_parameter_alignment:
severity: warning
implicitly_unwrapped_optional:
severity: warning
force_unwrapping:
severity: error
vertical_whitespace:
severity: error
force_try:
severity: error
trailing_semicolon:
severity: error
type_name:
min_length: 3
severity: warning
identifier_name:
min_length: 3
max_length: 60
# validates_start_with_lowercase: true
allowed_symbols: "_"
excluded:
- iv
- id
- ip
- on
- ui
- x
- y
- tz
- to
- db
# Disable rules from the default enabled set.
disabled_rules:
- trailing_whitespace
- implicit_getter
- redundant_string_enum_value
- switch_case_alignment
# Enable rules not from the default set.
opt_in_rules:
# - function_default_parameter_at_end
- empty_count
- indentation_width
# - index_at_zero
- legacy_constant
# - implicitly_unwrapped_optional
- force_unwrapping
# - no header
- file_header
# - for force unwrapping
- implicitly_unwrapped_optional
- vertical_parameter_alignment_on_call
- vertical_whitespace_between_cases
- vertical_whitespace_closing_braces
- vertical_whitespace_opening_braces
# Acts as a whitelist, only the rules specified in this list will be enabled. Can not be specified alongside disabled_rules or opt_in_rules.
only_rules:
# This is an entirely separate list of rules that are only run by the analyze command. All analyzer rules are opt-in, so this is the only configurable rule list (there is no disabled/whitelist equivalent).
analyzer_rules:
- unused_import
- unused_declaration
unused_declaration:
include_public_and_open: true
# paths to ignore during linting. Takes precedence over `included`.
excluded:
- SomePathHere

674
COPYING
View file

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View file

@ -1 +0,0 @@

View file

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>NotificationService</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.snikket.shared</string>
<string>group.snikket.notifications</string>
</array>
</dict>
</plist>

View file

@ -1,237 +0,0 @@
//
// NotificationService.swift
//
// Siskin IM
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import BackgroundTasks
import UserNotifications
import Shared
import TigaseSwift
import os.log
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)? {
didSet {
debug("content handler set!");
}
}
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
debug("Received push!");
if let bestAttemptContent = bestAttemptContent {
bestAttemptContent.sound = UNNotificationSound.default;
bestAttemptContent.categoryIdentifier = "MESSAGE";
if let account = BareJID(bestAttemptContent.userInfo["account"] as? String) {
DispatchQueue.main.async {
NotificationManager.instance.initialize(provider: ExtensionNotificationManagerProvider());
self.debug("push for account:", account);
if let encryped = bestAttemptContent.userInfo["encrypted"] as? String, let ivStr = bestAttemptContent.userInfo["iv"] as? String {
if let key = NotificationEncryptionKeys.key(for: account), let data = Data(base64Encoded: encryped), let iv = Data(base64Encoded: ivStr) {
self.debug("got encrypted push with known key");
let cipher = Cipher.AES_GCM();
var decoded = Data();
if cipher.decrypt(iv: iv, key: key, encoded: data, auth: nil, output: &decoded) {
self.debug("got decrypted data:", String(data: decoded, encoding: .utf8) as Any);
if let payload = try? JSONDecoder().decode(Payload.self, from: decoded) {
self.debug("decoded payload successfully!");
NotificationManager.instance.prepareNewMessageNotification(content: bestAttemptContent, account: account, sender: payload.sender.bareJid, type: payload.type, nickname: payload.nickname, body: payload.message, completionHandler: { content in
DispatchQueue.main.async {
contentHandler(content);
}
});
return;
}
}
}
contentHandler(bestAttemptContent)
} else {
self.debug("got plain push with", bestAttemptContent.userInfo[AnyHashable("sender")] as? String as Any, bestAttemptContent.userInfo[AnyHashable("body")] as? String as Any, bestAttemptContent.userInfo[AnyHashable("unread-messages")] as? Int as Any, bestAttemptContent.userInfo[AnyHashable("nickname")] as? String as Any);
NotificationManager.instance.prepareNewMessageNotification(content: bestAttemptContent, account: account, sender: JID(bestAttemptContent.userInfo[AnyHashable("sender")] as? String)?.bareJid, type: .unknown, nickname: bestAttemptContent.userInfo[AnyHashable("nickname")] as? String, body: bestAttemptContent.userInfo[AnyHashable("body")] as? String, completionHandler: { content in
DispatchQueue.main.async {
contentHandler(content);
}
});
}
}
return;
} else {
contentHandler(bestAttemptContent);
}
} else {
contentHandler(request.content);
}
// if #available(iOS 13.0, *) {
// let taskRequest = BGAppRefreshTaskRequest(identifier: "org.tigase.messenger.mobile.refresh");
// taskRequest.earliestBeginDate = nil
// do {
// debug("scheduling background app refresh")
// BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: "org.tigase.messenger.mobile.refresh")
// try BGTaskScheduler.shared.submit(taskRequest);
// } catch {
// debug("Could not schedule app refresh: \(error)")
// }
// }
}
// func updateNotification(content: UNMutableNotificationContent, account: BareJID, unread: Int, sender: JID, type kind: Payload.Kind, nickname: String?, body: String) {
// let tmp = try! DBConnection.main.prepareStatement(NotificationService.GET_NAME_QUERY).findFirst(["account": account, "jid": sender.bareJid] as [String: Any?], map: { (cursor) -> (String?, Int)? in
// return (cursor["name"], cursor["type"]!);
// });
// let name = tmp?.0;
// let type: Payload.Kind = tmp?.1 == 1 ? .groupchat : .chat;
// switch type {
// case .chat:
// content.title = name ?? sender.stringValue;
// content.body = body;
// content.userInfo = ["account": account.stringValue, "sender": sender.bareJid.stringValue];
// case .groupchat:
// if let nickname = nickname {
// content.title = "\(nickname) mentioned you in \(name ?? sender.bareJid.stringValue)";
// } else {
// content.title = "\(name ?? sender.bareJid.stringValue)";
// }
// content.body = body;
// content.userInfo = ["account": account.stringValue, "sender": sender.bareJid.stringValue];
// default:
// break;
// }
// content.categoryIdentifier = NotificationCategory.MESSAGE.rawValue;
// //content.badge = 2;
//
// }
func debug(_ data: Any...) {
os_log("%{public}@", log: OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SiskinPush"), "\(Date()): \(data)");
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
extension DBConnection {
static func main<T>(execute: @escaping (DBConnection) throws ->T) throws -> T {
let dbURL = mainDbURL();
let connection = try DBConnection.init(dbPath: dbURL.path);
return try execute(connection);
}
}
class ExtensionNotificationManagerProvider: NotificationManagerProvider {
static let GET_NAME_QUERY = "select name, 0 as type from roster_items where account = :account and jid = :jid union select name, 1 as type from chats where account = :account and jid = :jid and type > 0 order by type desc";
static let GET_UNREAD_CHATS = "select c.account, c.jid from chats c inner join chat_history ch where ch.account = c.account and ch.jid = c.jid and ch.state in (2,6,7) group by c.account, c.jid";
func getChatNameAndType(for account: BareJID, with jid: BareJID, completionHandler: @escaping (String?, Payload.Kind) -> Void) {
let tmp = try? DBConnection.main(execute: { conn in
return try conn.prepareStatement(ExtensionNotificationManagerProvider.GET_NAME_QUERY).findFirst(["account": account, "jid": jid] as [String: Any?], map: { (cursor) -> (String?, Int)? in
return (cursor["name"], cursor["type"]!);
});
});
completionHandler(tmp?.0, tmp?.1 == 0 ? .chat : .groupchat);
}
func countBadge(withThreadId: String?, completionHandler: @escaping (Int) -> Void) {
NotificationManager.unreadChatsThreadIds { (result) in
var unreadChats = result;
let activeAccounts = self.getActiveAccounts()
try? DBConnection.main(execute: { conn in
try conn.prepareStatement(ExtensionNotificationManagerProvider.GET_UNREAD_CHATS).query(forEach: { cursor in
if let account: BareJID = cursor["account"], let jid: BareJID = cursor["jid"] {
if activeAccounts.contains(account) {
unreadChats.insert("account=\(account.stringValue)|sender=\(jid.stringValue)")
}
}
})
});
if let threadId = withThreadId {
unreadChats.insert(threadId);
}
completionHandler(unreadChats.count);
}
}
func shouldShowNotification(account: BareJID, sender: BareJID?, body: String?, completionHandler: @escaping (Bool)->Void) {
completionHandler(true);
}
func getActiveAccounts() -> [BareJID] {
let query = [ String(kSecClass) : kSecClassGenericPassword, String(kSecMatchLimit) : kSecMatchLimitAll, String(kSecReturnAttributes) : kCFBooleanTrue as Any, String(kSecAttrService) : "xmpp" ] as [String : Any];
var result: CFTypeRef?;
guard SecItemCopyMatching(query as CFDictionary, &result) == noErr else {
return [];
}
guard let results = result as? [[String: NSObject]] else {
return [];
}
let accounts = results.map { item -> BareJID in
return BareJID(item[kSecAttrAccount as String] as! String);
}.sorted(by: { (j1, j2) -> Bool in
j1.stringValue.compare(j2.stringValue) == .orderedAscending
})
return accounts.filter { account in
let query = getAccountQuery(account.stringValue)
var result: CFTypeRef?
guard SecItemCopyMatching(query as CFDictionary, &result) == noErr else { return false }
guard let r = result as? [String: NSObject] else { return false }
var dict: [String: Any]? = nil;
if let data = r[String(kSecAttrGeneric)] as? NSData {
do {
dict = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSDictionary.self, from: data as Data) as? [String : Any]
} catch {
// failed to get account object
}
}
if (dict?["active"] as? Bool) ?? false {
return true
} else {
return false
}
}
}
func getAccountQuery(_ name:String, withData:CFString = kSecReturnAttributes) -> [String: Any] {
return [ String(kSecClass) : kSecClassGenericPassword, String(kSecMatchLimit) : kSecMatchLimitOne, String(withData) : kCFBooleanTrue!, String(kSecAttrService) : "xmpp" as NSObject, String(kSecAttrAccount) : name as NSObject ];
}
}

View file

@ -1,15 +0,0 @@
# Snikket iOS client
This is the source code for the Snikket iOS client.
# License
Snikket for iOS is based on [Siskin IM](https://siskin.im/) by <a href="https://tigase.net/"><img alt="Tigase Tigase Logo" src="https://github.com/tigase/website-assets/blob/master/tigase/images/tigase-logo.png?raw=true" width="25"/> Tigase</a>.
The official Siskin IM repository is available at: https://github.com/tigase/siskin-im/
Copyright (c) 2004 Tigase, Inc. and Snikket Community Interest Company.
Snikket and the Snikket logo are trademarks of Snikket Community Interest Company.
Licensed under GPL License Version 3.

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View file

@ -1,39 +0,0 @@
//
// NotificationCategory.swift
//
// Siskin IM
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import Foundation
public enum NotificationCategory: String {
case UNKNOWN
case ERROR
case MESSAGE
case SUBSCRIPTION_REQUEST
case MUC_ROOM_INVITATION
case CALL
case UNSENT_MESSAGES
public static func from(identifier: String?) -> NotificationCategory {
guard let str = identifier else {
return .UNKNOWN;
}
return NotificationCategory(rawValue: str) ?? .UNKNOWN;
}
}

View file

@ -1,32 +0,0 @@
//
// Shared.h
//
// Siskin IM
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
#import <Foundation/Foundation.h>
//! Project version number for Shared.
//FOUNDATION_EXPORT double SharedVersionNumber;
//! Project version string for Shared.
//FOUNDATION_EXPORT const unsigned char SharedVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Shared/PublicHeader.h>

View file

@ -1,54 +0,0 @@
//
// DBConnection_main.swift
//
// Siskin IM
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import Foundation
extension DBConnection {
public static func mainDbURL() -> URL {
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.snikket.shared")!;
return containerURL.appendingPathComponent("snikket_main.db");
}
public private(set) static var createIfNotExist: Bool = false;
public static func migrateToGroupIfNeeded() throws {
let dbURL = mainDbURL();
if !FileManager.default.fileExists(atPath: dbURL.path) {
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true);
let documentDirectory = paths[0];
let path = documentDirectory.appending("/mobile_messenger1.db");
if FileManager.default.fileExists(atPath: path) {
try FileManager.default.moveItem(atPath: path, toPath: dbURL.path);
}
}
createIfNotExist = true;
}
public static func initialize(dbPath: String) throws -> DBConnection {
let conn = try DBConnection(dbPath: dbPath);
try? conn.execute("PRAGMA busy_timeout = 2")
try DBSchemaManager(dbConnection: conn).upgradeSchema();
return conn;
}
}

View file

@ -1,590 +0,0 @@
//
// DBManager.swift
//
// Siskin IM
// Copyright (C) 2016 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import Foundation
import UIKit
import TigaseSwift
import SQLite3
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
open class DBConnection {
fileprivate var handle_:OpaquePointer? = nil;
open var handle:OpaquePointer {
get {
return handle_!;
}
}
public let dispatcher: QueueDispatcher;
open var lastInsertRowId: Int? {
let rowid = sqlite3_last_insert_rowid(handle);
return rowid > 0 ? Int(rowid) : nil;
}
open var changesCount: Int {
return Int(sqlite3_changes(handle));
}
convenience public init(dbUrl: URL) throws {
try self.init(dbPath: dbUrl.path);
}
public init(dbPath: String) throws {
dispatcher = QueueDispatcher(label: "db_queue");
try dispatcher.sync {
let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE;
_ = try self.check(sqlite3_open_v2(dbPath, &self.handle_, flags | SQLITE_OPEN_FULLMUTEX, nil));
}
}
deinit {
sqlite3_close(handle);
}
open func execute(_ query: String) throws {
try dispatcher.sync {
_ = try self.check(sqlite3_exec(self.handle, query, nil, nil, nil));
}
}
open func prepareStatement(_ query: String) throws -> DBStatement {
return try dispatcher.sync {
return try DBStatement(connection: self, query: query);
}
}
fileprivate func check(_ result:Int32, statement:DBStatement? = nil) throws -> Int32 {
guard let error = DBResult(errorCode: result, connection: self, statement: statement) else {
return result;
}
throw error;
}
}
public enum DBResult: Error {
fileprivate static let successCodes = [ SQLITE_OK, SQLITE_ROW, SQLITE_DONE ];
case error(message:String, code: Int32, statement:DBStatement?)
init?(errorCode: Int32, connection: DBConnection, statement:DBStatement?) {
guard !DBResult.successCodes.contains(errorCode) else {
return nil;
}
let tmp = sqlite3_errmsg(connection.handle);
let message = String(cString: tmp!);
self = .error(message: message, code: errorCode, statement: statement);
}
}
open class DBStatement {
fileprivate var handle:OpaquePointer? = nil;
fileprivate let connection:DBConnection;
open lazy var columnCount:Int = Int(sqlite3_column_count(self.handle));
open lazy var columnNames:[String] = (0..<Int32(self.columnCount)).map { (idx:Int32) -> String in
return String(cString: sqlite3_column_name(self.handle, idx)!);
}
open lazy var cursor:DBCursor = DBCursor(statement: self);
public let dispatcher: QueueDispatcher;
open var lastInsertRowId: Int? {
return connection.lastInsertRowId;
}
open var changesCount: Int {
return connection.changesCount;
}
init(connection:DBConnection, query:String, dispatcher: QueueDispatcher = QueueDispatcher(label: "DBStatementDispatcher")) throws {
self.connection = connection;
self.dispatcher = dispatcher;
_ = try connection.check(sqlite3_prepare_v2(connection.handle, query, -1, &handle, nil));
}
deinit {
sqlite3_finalize(handle);
}
fileprivate func step(_ expect: Int32 = SQLITE_ROW) throws -> Bool {
return try connection.dispatcher.sync() {
let result = try self.connection.check(sqlite3_step(self.handle));
return result == expect;
}
}
fileprivate func bind(_ params:Any?...) throws -> DBStatement {
_ = try bind(params);
return self;
}
fileprivate func bind(_ params:[String:Any?]) throws -> DBStatement {
reset()
for (k,v) in params {
let pos = sqlite3_bind_parameter_index(handle, ":"+k);
if pos == 0 {
print("got pos = 0, while parameter count = ", sqlite3_bind_parameter_count(handle));
}
try bind(v, pos: pos);
}
return self;
}
fileprivate func bind(_ params:[Any?]) throws -> DBStatement {
reset()
for pos in 1...params.count {
_ = try bind(params[pos-1], atIndex: pos);
}
return self;
}
fileprivate func bind(_ value:Any?, atIndex:Int) throws -> DBStatement {
try bind(value, pos: Int32(atIndex));
return self;
}
fileprivate func bind(_ value_:Any?, pos:Int32) throws {
var r:Int32 = SQLITE_OK;
if value_ == nil {
r = sqlite3_bind_null(handle, pos);
} else if let value:Any = value_ {
switch value {
case let v as [UInt8]:
r = sqlite3_bind_blob(handle, pos, v, Int32(v.count), SQLITE_TRANSIENT);
case let v as Data:
r = v.withUnsafeBytes() { (bytes) -> Int32 in
return sqlite3_bind_blob(handle, pos, bytes.baseAddress!, Int32(v.count), SQLITE_TRANSIENT);
}
case let v as Double:
r = sqlite3_bind_double(handle, pos, v);
case let v as UInt32:
r = sqlite3_bind_int(handle, pos, Int32(bitPattern: v));
case let v as Int32:
r = sqlite3_bind_int(handle, pos, v);
case let v as Int:
r = sqlite3_bind_int64(handle, pos, Int64(v));
case let v as Bool:
r = sqlite3_bind_int(handle, pos, Int32(v ? 1 : 0));
case let v as String:
r = sqlite3_bind_text(handle, pos, v, -1, SQLITE_TRANSIENT);
case let v as Date:
let timestamp = Int64(v.timeIntervalSince1970 * 1000);
r = sqlite3_bind_int64(handle, pos, timestamp);
case let v as StringValue:
r = sqlite3_bind_text(handle, pos, v.stringValue, -1, SQLITE_TRANSIENT);
default:
throw DBResult.error(message: "Unsupported type \(value.self) for parameter \(pos)", code: SQLITE_FAIL, statement: self);
}
} else {
sqlite3_bind_null(handle, pos)
}
_ = try check(r);
}
fileprivate func execute(_ params:[String:Any?]) throws -> DBStatement? {
_ = try bind(params);
return try execute();
}
fileprivate func execute(_ params:Any?...) throws -> DBStatement? {
return try execute(params);
}
fileprivate func execute(_ params:[Any?]) throws -> DBStatement? {
if params.count > 0 {
_ = try bind(params);
}
reset(false);
return try step() ? self : nil;
}
// open func query(_ params:[String:Any?]) throws -> DBCursor? {
// return try execute(params)?.cursor;
// }
//
// open func query(_ params:Any?...) throws -> DBCursor? {
// return try execute(params)?.cursor;
// }
open func findFirst<T>(_ params: [String:Any?], map: (DBCursor)-> T?) throws -> T? {
return try dispatcher.sync {
guard let cursor = try execute(params)?.cursor else {
return nil;
}
return map(cursor);
}
}
open func findFirst<T>(_ params: Any?..., map: (DBCursor)-> T?) throws -> T? {
return try dispatcher.sync {
guard let cursor = try execute(params)?.cursor else {
return nil;
}
return map(cursor);
}
}
open func query(_ params:[String:Any?], forEach: (DBCursor)->Void) throws {
try dispatcher.sync {
if let cursor = try execute(params)?.cursor {
repeat {
forEach(cursor);
} while cursor.next();
}
}
}
open func query(_ params:Any?..., forEach: (DBCursor)->Void) throws {
try dispatcher.sync {
if let cursor = try execute(params)?.cursor {
repeat {
forEach(cursor);
} while cursor.next();
}
}
}
open func queryFirstMatching<T>(_ params:[String:Any?], forEachRowUntil: (DBCursor)->T?) throws -> T? {
return try dispatcher.sync {
if let cursor = try execute(params)?.cursor {
var result: T?;
repeat {
result = forEachRowUntil(cursor);
} while result == nil && cursor.next();
return result;
}
return nil;
}
}
open func queryFirstMatching<T>(_ params:Any?..., forEachRowUntil: (DBCursor)->T?) throws -> T? {
var result: T? = nil;
try dispatcher.sync {
if let cursor = try execute(params)?.cursor {
repeat {
result = forEachRowUntil(cursor);
} while result == nil && cursor.next();
}
}
return result;
}
open func query<T>(_ params:[String:Any?], map: (DBCursor)->T?) throws -> [T] {
var result = [T]();
try dispatcher.sync {
var tmp: T? = nil;
if let cursor = try execute(params)?.cursor {
repeat {
tmp = map(cursor);
if tmp != nil {
result.append(tmp!);
}
} while cursor.next();
}
}
return result;
}
open func query<T>(_ params:Any?..., map: (DBCursor)->T?) throws -> [T] {
var result = [T]();
try dispatcher.sync {
var tmp: T? = nil;
if let cursor = try execute(params)?.cursor {
repeat {
tmp = map(cursor);
if tmp != nil {
result.append(tmp!);
}
} while cursor.next();
}
}
return result;
}
open func insert(_ params:Any?...) throws -> Int? {
return try connection.dispatcher.sync {
if params.count > 0 {
_ = try self.bind(params);
}
self.reset(false);
if try self.step(SQLITE_DONE) {
return self.lastInsertRowId;
}
return nil;
}
}
open func insert(_ params:[String:Any?]) throws -> Int? {
return try connection.dispatcher.sync {
_ = try self.bind(params);
self.reset(false);
if try self.step(SQLITE_DONE) {
return self.lastInsertRowId;
}
return nil;
}
}
open func update(_ params:Any?...) throws -> Int {
return try connection.dispatcher.sync {
_ = try self.execute(params);
return self.changesCount;
}
}
open func update(_ params:[String:Any?]) throws -> Int {
return try connection.dispatcher.sync {
_ = try self.execute(params);
return self.changesCount;
}
}
open func scalar(_ params:Any?...) throws -> Int? {
return try dispatcher.sync {
let cursor = try self.execute(params)?.cursor;
return cursor?[0];
}
}
open func scalar(_ params:[String:Any?]) throws -> Int? {
return try dispatcher.sync {
let cursor = try self.execute(params)?.cursor;
return cursor?[0];
}
}
open func scalar(_ params:Any?..., columnName: String) throws -> Int? {
return try dispatcher.sync {
let cursor = try self.execute(params)?.cursor;
return cursor?[columnName];
}
}
open func scalar(_ params:[String:Any?], columnName: String) throws -> Int? {
return try dispatcher.sync {
let cursor = try self.execute(params)?.cursor;
return cursor?[columnName];
}
}
fileprivate func reset(_ bindings:Bool=true) {
sqlite3_reset(handle);
if bindings {
sqlite3_clear_bindings(handle);
}
}
fileprivate func check(_ result:Int32) throws -> Int32 {
return try connection.check(result, statement: self);
}
}
open class DBCursor {
fileprivate let connection: DBConnection;
fileprivate let handle:OpaquePointer;
open lazy var columnCount:Int = Int(sqlite3_column_count(self.handle));
open lazy var columnNames:[String] = (0..<Int32(self.columnCount)).map { (idx:Int32) -> String in
return String(cString: sqlite3_column_name(self.handle, idx)!);
}
public init(statement:DBStatement) {
self.connection = statement.connection;
self.handle = statement.handle!;
}
open subscript(index: Int) -> Double {
return sqlite3_column_double(handle, Int32(index));
}
open subscript(index: Int) -> Int {
return Int(sqlite3_column_int64(handle, Int32(index)));
}
open subscript(index: Int) -> Int32 {
return sqlite3_column_int(handle, Int32(index));
}
open subscript(index: Int) -> String? {
let ptr = sqlite3_column_text(handle, Int32(index));
if ptr == nil {
return nil;
}
return String(cString: UnsafePointer(ptr!));
}
open subscript(index: Int) -> Bool {
return sqlite3_column_int64(handle, Int32(index)) != 0;
}
open subscript(index: Int) -> [UInt8]? {
let idx = Int32(index);
let origPtr = sqlite3_column_blob(handle, idx);
if origPtr == nil {
return nil;
}
let count = Int(sqlite3_column_bytes(handle, idx));
let ptr = origPtr?.assumingMemoryBound(to: UInt8.self)
return DBCursor.convert(count, data: ptr!);
}
open subscript(index: Int) -> Data? {
let idx = Int32(index);
let origPtr = sqlite3_column_blob(handle, idx);
if origPtr == nil {
return nil;
}
let count = Int(sqlite3_column_bytes(handle, idx));
return Data(bytes: origPtr!, count: count);
}
open subscript(index: Int) -> Date {
let timestamp = Double(sqlite3_column_int64(handle, Int32(index))) / 1000;
return Date(timeIntervalSince1970: timestamp);
}
open subscript(index: Int) -> JID? {
if let str:String = self[index] {
return JID(str);
}
return nil;
}
open subscript(index: Int) -> BareJID? {
if let str:String = self[index] {
return BareJID(str);
}
return nil;
}
open subscript(column: String) -> Double? {
return forColumn(column) {
return self[$0];
}
}
open subscript(column: String) -> Int? {
// return forColumn(column) {
// let v:Int? = self[$0];
// print("for \(column), position \($0) got \(v)")
// return v;
// }
if let idx = columnNames.firstIndex(of: column) {
return self[idx];
}
return nil;
}
open subscript(column: String) -> Int32? {
// return forColumn(column) {
// let v:Int? = self[$0];
// print("for \(column), position \($0) got \(v)")
// return v;
// }
if let idx = columnNames.firstIndex(of: column) {
return self[idx];
}
return nil;
}
open subscript(column: String) -> String? {
return forColumn(column) {
return self[$0];
}
}
open subscript(column: String) -> Bool? {
return forColumn(column) {
return self[$0];
}
}
open subscript(column: String) -> [UInt8]? {
return forColumn(column) {
return self[$0];
}
}
open subscript(column: String) -> Data? {
return forColumn(column) {
return self[$0];
}
}
open subscript(column: String) -> Date? {
return forColumn(column) {
return self[$0];
}
}
open subscript(column: String) -> JID? {
return forColumn(column) {
return self[$0];
}
}
open subscript(column: String) -> BareJID? {
return forColumn(column) {
return self[$0];
}
}
fileprivate func forColumn<T>(_ column:String, exec:(Int)->T?) -> T? {
if let idx = columnNames.firstIndex(of: column) {
return exec(idx);
}
return nil;
}
fileprivate static func convert<T>(_ count: Int, data: UnsafePointer<T>) -> [T] {
let buffer = UnsafeBufferPointer(start: data, count: count);
return Array(buffer)
}
open func next() -> Bool {
return connection.dispatcher.sync {
return sqlite3_step(self.handle) == SQLITE_ROW;
}
}
open func next() -> DBCursor? {
return next() ? self : nil;
}
}

View file

@ -1,121 +0,0 @@
//
// DBSchemaManager.swift
//
// Siskin IM
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import Foundation
import TigaseSwift
public class DBSchemaManager {
static let CURRENT_VERSION = 15;
fileprivate let dbConnection: DBConnection;
public init(dbConnection: DBConnection) {
self.dbConnection = dbConnection;
}
open func upgradeSchema() throws {
var version = try! getSchemaVersion();
while (version < DBSchemaManager.CURRENT_VERSION) {
switch version {
case 0:
try loadSchemaFile(fileName: "/db-schema-1.sql");
do {
try dbConnection.execute("select preview from chat_history");
} catch {
try dbConnection.execute("ALTER TABLE chat_history ADD COLUMN preview TEXT");
}
try cleanUpDuplicatedChats();
case 1:
try loadSchemaFile(fileName: "/db-schema-2.sql");
try cleanUpDuplicatedChats();
default:
try loadSchemaFile(fileName: "/db-schema-\(version + 1).sql");
break;
}
version = try! getSchemaVersion();
}
let journalMode = try dbConnection.prepareStatement("pragma journal_mode").findFirst(map: { cursor -> String? in
return cursor["journal_mode"];
})!;
if journalMode != "wal" {
try dbConnection.execute("PRAGMA journal_mode=WAL");
}
// need to make sure that "error" column exists as there was an issue with db-schema-2.sql
// which did not create this column
do {
try dbConnection.execute("select error from chat_history");
} catch {
try dbConnection.execute("ALTER TABLE chat_history ADD COLUMN error TEXT;");
}
let toRemove: [(String,String,Int32)] = try dbConnection.prepareStatement("SELECT sess.account as account, sess.name as name, sess.device_id as deviceId FROM omemo_sessions sess WHERE NOT EXISTS (select 1 FROM omemo_identities i WHERE i.account = sess.account and i.name = sess.name and i.device_id = sess.device_id)").query([:] as [String: Any?], map: { (cursor:DBCursor) -> (String, String, Int32)? in
return (cursor["account"]!, cursor["name"]!, cursor["deviceId"]!);
});
try toRemove.forEach { tuple in
let (account, name, device) = tuple;
_ = try dbConnection.prepareStatement("DELETE FROM omemo_sessions WHERE account = :account AND name = :name AND device_id = :deviceId").update(["account": account, "name": name, "deviceId": device] as [String: Any?]);
}
}
open func getSchemaVersion() throws -> Int {
return try self.dbConnection.prepareStatement("PRAGMA user_version").scalar() ?? 0;
}
fileprivate func loadSchemaFile(fileName: String) throws {
guard let bundle = Bundle.allFrameworks.first(where: { (bundle) -> Bool in
guard let resourcePath = bundle.resourcePath else {
return false;
}
return FileManager.default.fileExists(atPath: resourcePath.appending(fileName));
}) else {
return;
}
let resourcePath = bundle.resourcePath! + fileName;
print("loading SQL from file", resourcePath);
let dbSchema = try String(contentsOfFile: resourcePath, encoding: String.Encoding.utf8);
print("read schema:", dbSchema);
try dbConnection.execute(dbSchema);
print("loaded schema from file", fileName);
}
fileprivate func cleanUpDuplicatedChats() throws {
// deal with duplicated chats for the same bare jid
print("looking for duplicated chats...");
let duplicates: [(String, String, Int)] = try dbConnection.prepareStatement("select min(c.id) as id, c.account, c.jid from (select count(id) as count, account, jid from chats group by account, jid) x inner join chats c on c.account = x.account and c.jid = x.jid where count > 1 group by c.account, c.jid").query() { (cursor) -> (String, String, Int) in
let account: String = cursor["account"]!;
let jid: String = cursor["jid"]!;
let id: Int = cursor["id"] ?? 0;
print("account", account, "jid", jid, "id", id);
return (account, jid, id);
}
print("found duplicates", duplicates);
try duplicates.forEach({ (account, jid, idToLeave) in
let removed = try dbConnection.prepareStatement("delete from chats where account = ? and jid = ? and id <> :id").scalar(account, jid, idToLeave)!;
print("for account", account, "and jid", jid, "removed", removed, "duplicated chats");
});
print("duplicated chats cleanup finished!");
}
}

View file

@ -1,101 +0,0 @@
CREATE TABLE IF NOT EXISTS chats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account TEXT NOT NULL,
jid TEXT NOT NULL,
type INTEGER NOT NULL,
timestamp INTEGER,
thread_id TEXT,
resource TEXT,
nickname TEXT,
password TEXT,
room_state INTEGER
);
CREATE TABLE IF NOT EXISTS chat_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account TEXT NOT NULL,
jid TEXT NOT NULL,
author_jid TEXT,
author_nickname TEXT,
timestamp INTEGER,
item_type INTEGER,
data TEXT,
stanza_id TEXT,
state INTEGER,
preview TEXT
);
CREATE INDEX IF NOT EXISTS chat_history_jid_idx on chats (
jid, account
);
CREATE TABLE IF NOT EXISTS roster_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account TEXT NOT NULL,
jid TEXT NOT NULL,
name TEXT,
subscription TEXT,
timestamp INTEGER,
ask INTEGER
);
CREATE TABLE IF NOT EXISTS roster_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT
);
CREATE TABLE IF NOT EXISTS roster_items_groups (
item_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
FOREIGN KEY(item_id) REFERENCES roster_items(id),
FOREIGN KEY(group_id) REFERENCES roster_groups(id)
);
CREATE TABLE IF NOT EXISTS vcards_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT NOT NULL,
data TEXT,
timestamp INTEGER
);
CREATE INDEX IF NOT EXISTS vcards_cache_jid_idx on vcards_cache (
jid
);
CREATE TABLE IF NOT EXISTS caps_features (
node TEXT NOT NULL,
feature TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS caps_features_node_idx on caps_features (
node
);
CREATE INDEX IF NOT EXISTS caps_features_feature_idx on caps_features (
feature
);
CREATE TABLE IF NOT EXISTS caps_identities (
node TEXT NOT NULL,
name TEXT,
type TEXT,
category TEXT
);
CREATE INDEX IF NOT EXISTS caps_indentities_node_idx on caps_identities (
node
);
CREATE TABLE IF NOT EXISTS avatars_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT NOT NULL,
account TEXT NOT NULL,
hash TEXT NOT NULL,
type TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS avatars_cache_jid_idx on avatars_cache (
jid
);
PRAGMA user_version = 1;

View file

@ -1,9 +0,0 @@
BEGIN;
ALTER TABLE chat_history ADD COLUMN recipient_nickname TEXT;
COMMIT;
PRAGMA user_version = 10;

View file

@ -1,15 +0,0 @@
BEGIN;
ALTER TABLE roster_items ADD COLUMN annotations TEXT;
ALTER TABLE chat_history ADD COLUMN server_msg_id TEXT;
ALTER TABLE chat_history ADD COLUMN remote_msg_id TEXT;
ALTER TABLE chat_history ADD COLUMN participant_id TEXT;
CREATE INDEX IF NOT EXISTS chat_history_account_jid_server_msg_id_idx on chat_history (
account, server_msg_id
);
COMMIT;
PRAGMA user_version = 11;

View file

@ -1,9 +0,0 @@
BEGIN;
ALTER TABLE chat_history ADD COLUMN master_id INT;
ALTER TABLE chat_history ADD COLUMN correction_stanza_id TEXT;
ALTER TABLE chat_history ADD COLUMN correction_timestamp INTEGER;
COMMIT;
PRAGMA user_version = 12;

View file

@ -1,14 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS chat_history_sync (
id TEXT NOT NULL COLLATE NOCASE,
account TEXT NOT NULL COLLATE NOCASE,
component TEXT COLLATE NOCASE,
from_timestamp INTEGER NOT NULL,
from_id TEXT,
to_timestamp INTEGER NOT NULL
);
COMMIT;
PRAGMA user_version = 13;

View file

@ -1,7 +0,0 @@
BEGIN;
ALTER TABLE roster_items ADD COLUMN nickname TEXT;
COMMIT;
PRAGMA user_version = 14;

View file

@ -1,13 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS last_message_sync (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account TEXT NOT NULL COLLATE NOCASE,
jid TEXT NOT NULL COLLATE NOCASE,
received_id TEXT,
read_id TEXT
);
COMMIT;
PRAGMA user_version = 15;

View file

@ -1,153 +0,0 @@
BEGIN;
ALTER TABLE chats RENAME TO chats_old;
CREATE TABLE IF NOT EXISTS chats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account TEXT NOT NULL COLLATE NOCASE,
jid TEXT NOT NULL COLLATE NOCASE,
type INTEGER NOT NULL,
timestamp INTEGER,
thread_id TEXT,
resource TEXT,
nickname TEXT,
password TEXT,
room_state INTEGER
);
INSERT INTO chats (
account, jid, type, timestamp, thread_id, resource, nickname, password, room_state
)
SELECT account, jid, type, timestamp, thread_id, resource, nickname, password, room_state
FROM chats_old;
DROP TABLE chats_old;
CREATE INDEX IF NOT EXISTS chat_jid_idx on chats (
jid, account
);
ALTER TABLE chat_history RENAME TO chat_history_old;
CREATE TABLE IF NOT EXISTS chat_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account TEXT NOT NULL COLLATE NOCASE,
jid TEXT NOT NULL COLLATE NOCASE,
author_jid TEXT COLLATE NOCASE,
author_nickname TEXT,
timestamp INTEGER,
item_type INTEGER,
data TEXT,
stanza_id TEXT,
state INTEGER,
preview TEXT,
error TEXT
);
INSERT INTO chat_history (
account, jid, author_jid, author_nickname, timestamp, item_type, data, stanza_id, state, preview
)
SELECT account, jid, author_jid, author_nickname, timestamp, item_type, data, stanza_id, state, preview
FROM chat_history_old;
DROP TABLE chat_history_old;
CREATE INDEX IF NOT EXISTS chat_history_account_jid_timestamp_idx on chat_history (
account, jid, timestamp
);
CREATE INDEX IF NOT EXISTS chat_history_account_jid_state_idx on chat_history (
account, jid, state
);
ALTER TABLE roster_items RENAME TO roster_items_old;
ALTER TABLE roster_items_groups RENAME TO roster_items_groups_old;
CREATE TABLE IF NOT EXISTS roster_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account TEXT NOT NULL COLLATE NOCASE,
jid TEXT NOT NULL COLLATE NOCASE,
name TEXT,
subscription TEXT,
timestamp INTEGER,
ask INTEGER
);
INSERT INTO roster_items (
account, jid, name, subscription, timestamp, ask
)
SELECT account, jid, name, subscription, timestamp, ask
FROM roster_items_old;
CREATE TABLE IF NOT EXISTS roster_items_groups (
item_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
FOREIGN KEY(item_id) REFERENCES roster_items(id),
FOREIGN KEY(group_id) REFERENCES roster_groups(id)
);
INSERT INTO roster_items_groups (
item_id, group_id
)
SELECT i.id, go.group_id
FROM
roster_items_groups_old go
INNER JOIN roster_items_old io on io.id = go.item_id
INNER JOIN roster_items i on i.jid = io.jid;
DROP TABLE roster_items_groups_old;
DROP TABLE roster_items_old;
CREATE INDEX IF NOT EXISTS roster_item_jid_idx on roster_items (
jid, account
);
CREATE INDEX IF NOT EXISTS roster_item_groups_item_id_idx ON roster_items_groups (item_id);
CREATE INDEX IF NOT EXISTS roster_item_groups_group_id_idx ON roster_items_groups (group_id);
ALTER TABLE vcards_cache RENAME TO vcards_cache_old;
CREATE TABLE IF NOT EXISTS vcards_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT NOT NULL COLLATE NOCASE,
data TEXT,
timestamp INTEGER
);
INSERT INTO vcards_cache (
jid, data, timestamp
)
SELECT jid, data, timestamp
FROM vcards_cache_old;
DROP TABLE vcards_cache_old;
CREATE INDEX IF NOT EXISTS vcards_cache_jid_idx on vcards_cache (
jid
);
ALTER TABLE avatars_cache RENAME TO avatars_cache_old;
CREATE TABLE IF NOT EXISTS avatars_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT NOT NULL COLLATE NOCASE,
account TEXT NOT NULL COLLATE NOCASE,
hash TEXT NOT NULL,
type TEXT NOT NULL
);
INSERT INTO avatars_cache (
jid, account, hash, type
)
SELECT jid, account, hash, type
FROM avatars_cache_old;
DROP TABLE avatars_cache_old;
CREATE INDEX IF NOT EXISTS avatars_cache_jid_idx on avatars_cache (
jid
);
COMMIT;
PRAGMA user_version = 2;

View file

@ -1,7 +0,0 @@
BEGIN;
ALTER TABLE chats ADD COLUMN message_draft TEXT;
COMMIT;
PRAGMA user_version = 3;

View file

@ -1,7 +0,0 @@
BEGIN;
ALTER TABLE chats ADD COLUMN name TEXT;
COMMIT;
PRAGMA user_version = 4;

View file

@ -1,47 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS omemo_sessions (
account TEXT NOT NULL COLLATE NOCASE,
name TEXT NOT NULL,
device_id INTEGER NOT NULL,
key TEXT NOT NULL,
UNIQUE (account, name, device_id) ON CONFLICT REPLACE
);
CREATE TABLE IF NOT EXISTS omemo_identities (
account TEXT NOT NULL COLLATE NOCASE,
name TEXT NOT NULL,
device_id INTEGER NOT NULL,
fingerprint TEXT NOT NULL,
key BLOB NOT NULL,
own INTEGER NOT NULL,
status INTEGER NOT NULL,
UNIQUE (account, name, fingerprint) ON CONFLICT IGNORE
);
CREATE TABLE IF NOT EXISTS omemo_pre_keys (
account TEXT NOT NULL COLLATE NOCASE,
id INTEGER NOT NULL,
key BLOB NOT NULL,
UNIQUE (account, id) ON CONFLICT REPLACE
);
CREATE TABLE IF NOT EXISTS omemo_signed_pre_keys (
account TEXT NOT NULL COLLATE NOCASE,
id INTEGER NOT NULL,
key BLOB NOT NULL,
UNIQUE (account, id) ON CONFLICT REPLACE
);
ALTER TABLE chats ADD COLUMN encryption TEXT;
ALTER TABLE chat_history ADD COLUMN encryption int;
ALTER TABLE chat_history ADD COLUMN fingerprint text;
COMMIT;
PRAGMA user_version = 5;

View file

@ -1,7 +0,0 @@
BEGIN;
ALTER TABLE chats ADD COLUMN options TEXT;
COMMIT;
PRAGMA user_version = 6;

View file

@ -1,12 +0,0 @@
BEGIN;
UPDATE chat_history SET state = 11 WHERE state = 5;
UPDATE chat_history SET state = 5 WHERE state = 9;
UPDATE chat_history SET state = 9 WHERE state = 4;
UPDATE chat_history SET state = 4 WHERE state = 7;
UPDATE chat_history SET state = 7 WHERE state = 6;
UPDATE chat_history SET state = 6 WHERE state = 8;
COMMIT;
PRAGMA user_version = 7;

View file

@ -1,13 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS chats_read (
account TEXT NOT NULL COLLATE NOCASE,
jid TEXT NOT NULL COLLATE NOCASE,
timestamp INTEGER,
UNIQUE (account, jid)
);
COMMIT;
PRAGMA user_version = 8;

View file

@ -1,7 +0,0 @@
BEGIN;
ALTER TABLE chat_history ADD COLUMN appendix TEXT;
COMMIT;
PRAGMA user_version = 9;

View file

@ -1,39 +0,0 @@
//
// NotificationEncryptionKeys.swift
//
// Siskin IM
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import Foundation
import TigaseSwift
public class NotificationEncryptionKeys {
private static let storage = UserDefaults(suiteName: "group.snikket.notifications")!;
public static func key(for account: BareJID) -> Data? {
storage.data(forKey: account.stringValue)
}
public static func set(key: Data?, for account: BareJID) {
storage.setValue(key, forKey: account.stringValue);
}
}

View file

@ -1,200 +0,0 @@
//
// NotificationManager.swift
//
// Siskin IM
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import Foundation
import TigaseSwift
import UserNotifications
import os
public class NotificationManager {
public static let instance: NotificationManager = NotificationManager();
public private(set) var provider: NotificationManagerProvider!;
public func initialize(provider: NotificationManagerProvider) {
self.provider = provider;
}
public static func unreadChatsThreadIds(completionHandler: @escaping (Set<String>)->Void) {
unreadThreadIds(for: [.MESSAGE], completionHandler: completionHandler);
}
public static func unreadThreadIds(for categories: [NotificationCategory], completionHandler: @escaping (Set<String>)->Void) {
UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in
let unreadChats = Set(notifications.filter({(notification) in
let category = NotificationCategory.from(identifier: notification.request.content.categoryIdentifier);
return categories.contains(category);
}).map({ (notification) in
return notification.request.content.threadIdentifier;
}));
completionHandler(unreadChats);
}
}
public func notifyNewMessage(account: BareJID, sender: BareJID?, type kind: Payload.Kind, nickname: String?, body: String) {
shouldShowNotification(account: account, sender: sender, body: body, completionHandler: { (result) in
guard result else {
return;
}
self.intNotifyNewMessage(account: account, sender: sender, type: kind, nickname: nickname, body: body);
});
}
public func shouldShowNotification(account: BareJID, sender: BareJID?, body: String?, completionHandler: @escaping (Bool)->Void) {
provider.shouldShowNotification(account: account, sender: sender, body: body) { (result) in
if result {
if let uid = self.generateMessageUID(account: account, sender: sender, body: body) {
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
let should = !notifications.contains(where: { (notification) -> Bool in
guard let nuid = notification.request.content.userInfo["uid"] as? String else {
return false;
}
return nuid == uid;
});
completionHandler(should);
});
return;
}
}
completionHandler(result);
}
}
private func intNotifyNewMessage(account: BareJID, sender: BareJID?, type kind: Payload.Kind, nickname: String?, body: String) {
let id = UUID().uuidString;
let content = UNMutableNotificationContent();
prepareNewMessageNotification(content: content, account: account, sender: sender, type: kind, nickname: nickname, body: body) { (content) in
UNUserNotificationCenter.current().add(UNNotificationRequest(identifier: id, content: content, trigger: nil)) { (error) in
print("message notification error", error as Any);
}
}
}
public func prepareNewMessageNotification(content: UNMutableNotificationContent, account: BareJID, sender jid: BareJID?, type kind: Payload.Kind, nickname: String?, body msg: String?, completionHandler: @escaping (UNMutableNotificationContent)->Void) {
content.sound = .default;
content.categoryIdentifier = NotificationCategory.MESSAGE.rawValue;
if let sender = jid, let body = msg {
let uid = generateMessageUID(account: account, sender: sender, body: body)!;
content.threadIdentifier = "account=\(account.stringValue)|sender=\(sender.stringValue)";
self.provider.getChatNameAndType(for: account, with: sender, completionHandler: { (name, type) in
os_log("%{public}@", log: OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SiskinPush"), "Found: name: \(name ?? ""), type: \(type.rawValue)");
switch type {
case .chat:
content.title = name ?? sender.stringValue;
if body.starts(with: "/me ") {
content.body = String(body.dropFirst(4));
} else {
content.body = body;
}
content.userInfo = ["account": account.stringValue, "sender": sender.stringValue, "uid": uid];
case .groupchat:
content.title = "\(name ?? sender.stringValue)";
if body.starts(with: "/me ") {
if let nickname = nickname {
content.body = "\(nickname) \(body.dropFirst(4))";
} else {
content.body = String(body.dropFirst(4));
}
} else {
content.body = body;
if let nickname = nickname {
content.subtitle = nickname;
}
}
content.userInfo = ["account": account.stringValue, "sender": sender.stringValue, "uid": uid];
default:
break;
}
self.provider.countBadge(withThreadId: content.threadIdentifier, completionHandler: { count in
content.badge = count as NSNumber;
completionHandler(content);
});
});
} else {
content.threadIdentifier = "account=\(account.stringValue)";
content.body = "New message!";
self.provider.countBadge(withThreadId: content.threadIdentifier, completionHandler: { count in
content.badge = count as NSNumber;
completionHandler(content);
});
}
}
func generateMessageUID(account: BareJID, sender: BareJID?, body: String?) -> String? {
if let sender = sender, let body = body {
return Digest.sha256.digest(toHex: "\(account)|\(sender)|\(body)".data(using: .utf8));
}
return nil;
}
}
public protocol NotificationManagerProvider {
func getChatNameAndType(for account: BareJID, with jid: BareJID, completionHandler: @escaping (String?, Payload.Kind)->Void);
func countBadge(withThreadId: String?, completionHandler: @escaping (Int)->Void);
func shouldShowNotification(account: BareJID, sender: BareJID?, body: String?, completionHandler: @escaping (Bool)->Void);
}
public class Payload: Decodable {
public var unread: Int;
public var sender: JID;
public var type: Kind;
public var nickname: String?;
public var message: String?;
public var sid: String?;
public var media: [String]?;
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self);
unread = try container.decode(Int.self, forKey: .unread);
sender = try container.decode(JID.self, forKey: .sender);
type = Kind(rawValue: (try container.decodeIfPresent(String.self, forKey: .type)) ?? Kind.unknown.rawValue)!;
nickname = try container.decodeIfPresent(String.self, forKey: .nickname);
message = try container.decodeIfPresent(String.self, forKey: .message);
sid = try container.decodeIfPresent(String.self, forKey: .sid)
media = try container.decodeIfPresent([String].self, forKey: .media);
// -- and so on...
}
public enum Kind: String {
case unknown
case groupchat
case chat
case call
}
public enum CodingKeys: String, CodingKey {
case unread
case sender
case type
case nickname
case message
case sid
case media
}
}

View file

@ -1,352 +0,0 @@
//
// Cipher+AES.swift
//
// Siskin IM
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import Foundation
import OpenSSL
open class Cipher {
}
extension Cipher {
open class AES_GCM {
public init() {
}
public static func generateKey(ofSize: Int) -> Data? {
var key = Data(count: ofSize/8);
let result = key.withUnsafeMutableBytes({ (ptr: UnsafeMutableRawBufferPointer) -> Int32 in
return SecRandomCopyBytes(kSecRandomDefault, ofSize/8, ptr.baseAddress!);
});
guard result == errSecSuccess else {
print("failed to generated AES encryption key:", result)
return nil;
}
return key;
}
open func encrypt(iv: Data, key: Data, message data: Data, output: UnsafeMutablePointer<Data>?, tag: UnsafeMutablePointer<Data>?) -> Bool {
let ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, key.count == 32 ? EVP_aes_256_gcm() : EVP_aes_128_gcm(), nil, nil, nil);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, Int32(iv.count), nil);
iv.withUnsafeBytes({ (ivBytes: UnsafeRawBufferPointer) -> Void in
key.withUnsafeBytes({ (keyBytes: UnsafeRawBufferPointer) -> Void in
EVP_EncryptInit_ex(ctx, nil, nil, keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), ivBytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
})
});
EVP_CIPHER_CTX_set_padding(ctx, 1);
var outbuf = Array(repeating: UInt8(0), count: data.count);
var outbufLen: Int32 = 0;
let encryptedBody = data.withUnsafeBytes { ( bytes) -> Data in
EVP_EncryptUpdate(ctx, &outbuf, &outbufLen, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(data.count));
return Data(bytes: &outbuf, count: Int(outbufLen));
}
EVP_EncryptFinal_ex(ctx, &outbuf, &outbufLen);
var tagData = Data(count: 16);
tagData.withUnsafeMutableBytes({ (bytes) -> Void in
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
});
EVP_CIPHER_CTX_free(ctx);
tag?.initialize(to: tagData);
output?.initialize(to: encryptedBody);
return true;
}
open func encrypt(iv: Data, key: Data, provider: CipherDataProvider, consumer: CipherDataConsumer, chunkSize: Int = 512*1024) -> Data {
let ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, key.count == 32 ? EVP_aes_256_gcm() : EVP_aes_128_gcm(), nil, nil, nil);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, Int32(iv.count), nil);
iv.withUnsafeBytes({ (ivBytes: UnsafeRawBufferPointer) -> Void in
key.withUnsafeBytes({ (keyBytes: UnsafeRawBufferPointer) -> Void in
EVP_EncryptInit_ex(ctx, nil, nil, keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), ivBytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
})
});
EVP_CIPHER_CTX_set_padding(ctx, 1);
var ended: Bool = false;
var buffer = Data(count: chunkSize * 2);
repeat {
switch provider.chunk(size: chunkSize) {
case .data(let data):
let result = data.withUnsafeBytes { ( bytes) -> Data in
let wrote = buffer.withUnsafeMutableBytes { (outbuf) -> Int in
var outbufLen: Int32 = 0;
EVP_EncryptUpdate(ctx, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self), &outbufLen, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(data.count));
return Int(outbufLen)
}
return buffer.subdata(in: 0..<wrote);
}
_ = consumer.consume(data: result);
case .ended:
buffer.withUnsafeMutableBytes { (outbuf) -> Void in
var outbufLen: Int32 = 0;
EVP_EncryptFinal_ex(ctx, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self), &outbufLen);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self));
}
ended = true;
}
} while !ended;
return buffer.subdata(in: 0..<16);
}
open func decrypt(iv: Data, key: Data, encoded payload: Data, auth tag: Data?, output: UnsafeMutablePointer<Data>?) -> Bool {
let ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, key.count == 32 ? EVP_aes_256_gcm() : EVP_aes_128_gcm(), nil, nil, nil);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, Int32(iv.count), nil);
key.withUnsafeBytes({ (keyBytes) -> Void in
iv.withUnsafeBytes({ (ivBytes) -> Void in
EVP_DecryptInit_ex(ctx, nil, nil, keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), ivBytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
})
})
EVP_CIPHER_CTX_set_padding(ctx, 1);
var auth = tag;
var encoded = payload;
if auth == nil {
auth = payload.subdata(in: (payload.count - 16)..<payload.count);
encoded = payload.subdata(in: 0..<(payload.count-16));
}
var outbuf = Array(repeating: UInt8(0), count: encoded.count);
var outbufLen: Int32 = 0;
let decoded = encoded.withUnsafeBytes({ (bytes) -> Data in
EVP_DecryptUpdate(ctx, &outbuf, &outbufLen, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(encoded.count));
return Data(bytes: &outbuf, count: Int(outbufLen));
});
if auth != nil {
auth!.withUnsafeMutableBytes({ [count = auth!.count] (bytes: UnsafeMutableRawBufferPointer) -> Void in
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, Int32(count), bytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
});
}
let ret = EVP_DecryptFinal_ex(ctx, &outbuf, &outbufLen);
EVP_CIPHER_CTX_free(ctx);
guard ret >= 0 else {
print("authentication of encrypted message failed:", ret);
return false;
}
output?.initialize(to: decoded);
return true;
}
open func decrypt(iv: Data, key: Data, provider: CipherDataProvider, consumer: CipherDataConsumer, chunkSize: Int = 512 * 1024) -> Bool {
let ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, key.count == 32 ? EVP_aes_256_gcm() : EVP_aes_128_gcm(), nil, nil, nil);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, Int32(iv.count), nil);
key.withUnsafeBytes({ (keyBytes) -> Void in
iv.withUnsafeBytes({ (ivBytes) -> Void in
EVP_DecryptInit_ex(ctx, nil, nil, keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), ivBytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
})
})
EVP_CIPHER_CTX_set_padding(ctx, 1);
var ended: Bool = false;
var buffer = Data(count: chunkSize * 2);
var result = false;
repeat {
switch provider.chunk(size: chunkSize) {
case .data(let data):
let result = data.withUnsafeBytes { ( bytes) -> Data in
let wrote = buffer.withUnsafeMutableBytes { (outbuf) -> Int in
var outbufLen: Int32 = 0;
EVP_DecryptUpdate(ctx, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self), &outbufLen, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(data.count));
return Int(outbufLen)
}
return buffer.subdata(in: 0..<wrote);
}
_ = consumer.consume(data: result);
case .ended:
if var auth = (provider as? CipherDataProviderWithAuth)?.authTag() {
auth.withUnsafeMutableBytes({ [count = auth.count] (bytes: UnsafeMutableRawBufferPointer) -> Void in
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, Int32(count), bytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
});
}
result = buffer.withUnsafeMutableBytes { (outbuf) -> Bool in
var outbufLen: Int32 = 0;
return EVP_DecryptFinal_ex(ctx, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self), &outbufLen) >= 0;
}
ended = true;
}
} while !ended;
return result;
}
}
public class DataDataProvider: CipherDataProvider {
let data: Data;
private(set) var offset: Int = 0;
public init(data: Data) {
self.data = data;
}
public func chunk(size chunkSize: Int) -> Cipher.DataProviderResult {
guard offset < data.count else {
return .ended;
}
let size = min(chunkSize, data.count - offset);
defer {
offset = offset + size;
}
return .data(data.subdata(in: offset..<(offset + size)));
}
}
public class FileDataProvider: CipherDataProviderWithAuth {
let inputStream: InputStream;
var count: Int = 0;
var limit: Int = 0;
public convenience init(inputStream: InputStream, fileSize: Int, hasAuthTag: Bool) {
if hasAuthTag {
self.init(inputStream: inputStream, limit: fileSize - 16);
} else {
self.init(inputStream: inputStream);
}
}
public init(inputStream: InputStream, limit: Int = Int.max) {
self.limit = limit;
self.inputStream = inputStream;
self.inputStream.open();
}
deinit {
self.inputStream.close();
}
public func chunk(size: Int) -> Cipher.DataProviderResult {
guard inputStream.hasBytesAvailable else {
inputStream.close();
return .ended;
}
let limit = min(size, self.limit - self.count);
guard limit > 0 else {
return .ended;
}
var buf = Array(repeating: UInt8(0), count: limit);
let read = inputStream.read(&buf, maxLength: limit);
guard read > 0 else {
return .ended;
}
count = count + read;
return .data(Data(bytes: &buf, count: read));
}
public func authTag() -> Data? {
guard inputStream.hasBytesAvailable else {
return nil;
}
var buf = Array(repeating: UInt8(0), count: 16);
let read = inputStream.read(&buf, maxLength: 16);
guard read > 0 else {
return nil;
}
return Data(bytes: &buf, count: read);
}
}
public class TempFileConsumer: CipherDataConsumer {
public let url: URL;
private var outputStream: OutputStream?;
public private(set) var size: Int = 0;
public init?() {
self.url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString);
guard let outputStream = OutputStream(url: url, append: true) else {
return nil;
}
self.outputStream = outputStream;
self.outputStream?.open();
guard self.outputStream != nil, self.outputStream!.hasSpaceAvailable else {
return nil;
}
}
public func consume(data: Data) -> Int {
let wrote = data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> Int in
return outputStream!.write(ptr.bindMemory(to: UInt8.self).baseAddress!, maxLength: data.count);
}
size = size + wrote;
return wrote;
}
public func close() {
self.outputStream!.close();
}
deinit {
if outputStream != nil {
outputStream?.close();
}
try? FileManager.default.removeItem(at: url);
}
}
public enum DataProviderResult {
case data(Data)
case ended
}
}
public protocol CipherDataProvider {
func chunk(size: Int) -> Cipher.DataProviderResult;
}
public protocol CipherDataProviderWithAuth: CipherDataProvider {
func authTag() -> Data?;
}
public protocol CipherDataConsumer {
func consume(data: Data) -> Int;
}

View file

@ -1,28 +0,0 @@
//
// AccountTableViewCell.swift
//
// Siskin IM
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import UIKit
class AccountTableViewCell: UITableViewCell {
@IBOutlet var accountLabel: UILabel!;
}

View file

@ -1,90 +0,0 @@
//
// AccountsTableViewController.swift
//
// Siskin IM
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import UIKit
class AccountsTableViewController: UITableViewController {
var accounts: [String] = [];
var delegate: ShareViewController?;
var selected: String? = nil;
override func viewDidLoad() {
accounts = getAccounts();
super.viewDidLoad();
//tableView.register(AccountTableViewCell.self, forCellReuseIdentifier: "accountTableViewCell");
}
func getAccounts() -> [String] {
var accounts = [String]();
let query = [ String(kSecClass) : kSecClassGenericPassword, String(kSecMatchLimit) : kSecMatchLimitAll, String(kSecReturnAttributes) : kCFBooleanTrue as Any, String(kSecAttrService) : "xmpp" ] as [String : Any];
var result:AnyObject?;
let lastResultCode: OSStatus = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0));
}
if lastResultCode == noErr {
if let results = result as? [[String:NSObject]] {
for r in results {
let name = r[String(kSecAttrAccount)] as! String;
if let data = r[String(kSecAttrGeneric)] as? NSData {
let dict = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? [String:AnyObject];
if dict!["active"] as? Bool ?? false {
accounts.append(name);
}
}
}
}
}
return accounts;
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1;
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return accounts.count;
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "accountTableViewCell", for: indexPath) as! AccountTableViewCell;
let name = accounts[indexPath.row];
cell.accountLabel.text = name;
if selected != nil && selected! == name {
cell.accessoryType = .checkmark;
} else {
cell.accessoryType = .none;
}
return cell;
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let account = accounts[indexPath.row];
selected = account;
delegate!.accountSelection(account: account);
navigationController?.popViewController(animated: true);
}
}

View file

@ -1,140 +0,0 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "1x",
"size" : "57x57"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "57x57"
},
{
"filename" : "logo-120px.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "50x50"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "50x50"
},
{
"filename" : "logo-72px.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "72x72"
},
{
"filename" : "logo-144px.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "72x72"
},
{
"filename" : "logo-76px.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "logo-152px.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "logo-167px.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "logo-1024px.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -1,125 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModule="Tigase_Messenger___Share" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="8bI-gs-bmD"/>
<viewControllerLayoutGuide type="bottom" id="d5i-Ba-RvD"/>
</layoutGuides>
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<extendedEdge key="edgesForExtendedLayout"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="136.80000000000001" y="137.18140929535232"/>
</scene>
<!--Accounts-->
<scene sceneID="taW-D3-3Yx">
<objects>
<tableViewController storyboardIdentifier="accountSelectionViewController" title="Accounts" useStoryboardIdentifierAsRestorationIdentifier="YES" id="6l2-wd-vXh" customClass="AccountsTableViewController" customModule="Tigase_Messenger___Share" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="977-Qx-KPe">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="accountTableViewCell" selectionStyle="default" indentationWidth="10" reuseIdentifier="accountTableViewCell" id="SZs-Vf-W54" customClass="AccountTableViewCell" customModule="Tigase_Messenger___Share" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="SZs-Vf-W54" id="iwn-zZ-n1r">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="95D-JK-fLM">
<rect key="frame" x="8" y="8" width="359" height="27.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="95D-JK-fLM" firstAttribute="top" secondItem="iwn-zZ-n1r" secondAttribute="topMargin" id="1uK-gH-Usi"/>
<constraint firstItem="95D-JK-fLM" firstAttribute="leading" secondItem="iwn-zZ-n1r" secondAttribute="leadingMargin" id="5IP-c3-dAl"/>
<constraint firstAttribute="bottomMargin" secondItem="95D-JK-fLM" secondAttribute="bottom" id="Gso-BC-0A3"/>
<constraint firstAttribute="trailingMargin" secondItem="95D-JK-fLM" secondAttribute="trailing" id="WJZ-yT-t97"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="accountLabel" destination="95D-JK-fLM" id="Y8d-XC-iCV"/>
</connections>
</tableViewCell>
</prototypes>
<sections/>
<connections>
<outlet property="dataSource" destination="6l2-wd-vXh" id="2cP-jZ-k4K"/>
<outlet property="delegate" destination="6l2-wd-vXh" id="MdV-Xt-gOz"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="NGG-wN-6ep" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1606" y="186"/>
</scene>
<!--Recipients-->
<scene sceneID="bWt-BT-C6m">
<objects>
<tableViewController storyboardIdentifier="recipientsSelectionViewController" title="Recipients" useStoryboardIdentifierAsRestorationIdentifier="YES" id="wpy-g7-270" customClass="RecipientsSelectionViewController" customModule="Tigase_Messenger___Share" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="SH7-od-nvj">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="accountTableViewCell" selectionStyle="default" indentationWidth="10" reuseIdentifier="recipientTableViewCell" id="lwl-dC-64B" customClass="RecipientTableViewCell" customModule="Tigase_Messenger___Share" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="lwl-dC-64B" id="0s3-Da-XsL">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Geq-xy-Tr2">
<rect key="frame" x="8" y="8" width="359" height="27.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="Geq-xy-Tr2" firstAttribute="leading" secondItem="0s3-Da-XsL" secondAttribute="leadingMargin" id="9uo-QN-8Au"/>
<constraint firstAttribute="trailingMargin" secondItem="Geq-xy-Tr2" secondAttribute="trailing" id="L9E-QP-QXL"/>
<constraint firstItem="Geq-xy-Tr2" firstAttribute="top" secondItem="0s3-Da-XsL" secondAttribute="topMargin" id="ZB0-2w-ydq"/>
<constraint firstAttribute="bottomMargin" secondItem="Geq-xy-Tr2" secondAttribute="bottom" id="tnB-2B-bZs"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="name" destination="Geq-xy-Tr2" id="zJE-Qp-ccD"/>
</connections>
</tableViewCell>
</prototypes>
<sections/>
<connections>
<outlet property="dataSource" destination="wpy-g7-270" id="0GI-B2-E3d"/>
<outlet property="delegate" destination="wpy-g7-270" id="wbe-ir-gee"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="AYL-Qg-6cV" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="884" y="-202"/>
</scene>
</scenes>
</document>

View file

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Share with Snikket</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationDictionaryVersion</key>
<integer>2</integer>
<key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>

View file

@ -1,29 +0,0 @@
//
// RecipientTableViewCell.swift
//
// Siskin IM
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import UIKit
class RecipientTableViewCell: UITableViewCell {
@IBOutlet var name: UILabel!;
@IBOutlet var jid: UILabel!;
}

View file

@ -1,123 +0,0 @@
//
// RecipientsSelectionViewController.swift
//
// Siskin IM
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import UIKit
import TigaseSwift
class RecipientsSelectionViewController: UITableViewController {
var selected: [JID] = [];
var allItems = [JID:RosterItem]();
var items: [RosterItem] = [];
var xmppClient: XMPPClient! {
didSet {
let store = RosterModule.getRosterStore(xmppClient.sessionObject) as! DefaultRosterStore;
self.allItems.removeAll();
store.getJids().forEach({(jid) in
if let item = store.get(for: jid) {
allItems[jid] = item;
}
});
updateItem(item: nil);
}
}
var delegate: ShareViewController?;
var sharedDefaults = UserDefaults(suiteName: "group.snikket.share");
fileprivate let indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 80, height: 80));
override func viewDidLoad() {
super.viewDidLoad();
indicator.style = .gray;
indicator.backgroundColor = UIColor.white;
indicator.hidesWhenStopped = true;
self.view.addSubview(indicator);
}
override func viewWillAppear(_ animated: Bool) {
if xmppClient.state == .connecting {
indicator.startAnimating();
}
super.viewWillAppear(animated);
let view = self.parent!.view!;
indicator.center = CGPoint(x: view.bounds.width / 2, y: view.bounds.height / 2);
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1;
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count;
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "recipientTableViewCell", for: indexPath) as! RecipientTableViewCell;
let item = items[indexPath.row];
cell.name.text = item.name ?? item.jid.stringValue;
if selected.contains(item.jid) {
cell.accessoryType = .checkmark;
} else {
cell.accessoryType = .none;
}
return cell;
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let jid = items[indexPath.row].jid;
if let idx = selected.firstIndex(of: jid) {
selected.remove(at: idx);
} else {
selected.append(jid);
}
delegate?.recipientsChanged(selected);
tableView.reloadData();
}
func hideIndicator() {
DispatchQueue.main.async {
if self.indicator.isAnimating {
self.indicator.stopAnimating();
}
}
}
func updateItem(item: RosterItem?) {
if item != nil {
allItems[item!.jid] = item!;
}
let showHidden = sharedDefaults!.bool(forKey: "RosterDisplayHiddenGroup");
let tmp: [RosterItem] = allItems.values.filter({ (ri) -> Bool in
return showHidden || !ri.groups.contains("Hidden");
});
items = tmp.sorted { (r1, r2) -> Bool in
return (r1.name ?? r1.jid.stringValue).compare(r2.name ?? r2.jid.stringValue) == .orderedAscending;
}
tableView.reloadData();
if item != nil {
hideIndicator();
}
}
}

View file

@ -1,383 +0,0 @@
//
// ShareViewController.swift
//
// Siskin IM
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import UIKit
import Social
import Shared
import TigaseSwift
import MobileCoreServices
class ShareViewController: SLComposeServiceViewController {
var account: String? = nil;
var recipients: [JID] = [];
weak var handler: EventHandler?;
lazy var xmppClient: XMPPClient = {
let client = XMPPClient();
let sslHandler: ((SessionObject, SecTrust)->Bool) = {(sessionObject,secTrust) -> Bool in
return true;
};
client.sessionObject.setProperty(SocketConnector.SSL_CERTIFICATE_VALIDATOR, value: sslHandler);
_ = client.modulesManager.register(AuthModule());
_ = client.modulesManager.register(StreamFeaturesModule());
_ = client.modulesManager.register(SaslModule());
_ = client.modulesManager.register(ResourceBinderModule());
_ = client.modulesManager.register(SessionEstablishmentModule());
_ = client.modulesManager.register(DiscoveryModule());
client.modulesManager.register(PresenceModule()).initialPresence = false;
let messageModule = client.modulesManager.register(MessageModule());
let rosterModule = client.modulesManager.register(RosterModule());
_ = client.modulesManager.register(HttpFileUploadModule());
let handler = ShareEventHandler();
handler.controller = self;
self.handler = handler;
client.eventBus.register(handler: handler, for: RosterModule.ItemUpdatedEvent.TYPE)
return client;
}();
lazy var accountConfigurationItem: SLComposeSheetConfigurationItem = {
let item = SLComposeSheetConfigurationItem()!;
item.title = "Account";
item.tapHandler = self.showAccountSelection;
return item;
}();
lazy var buddiesConfigurationItem: SLComposeSheetConfigurationItem = {
let item = SLComposeSheetConfigurationItem()!;
item.title = "Recipients";
item.tapHandler = self.showRecipientsSelection;
return item;
}();
weak var rosterController: RecipientsSelectionViewController?;
var webUrl: URL?;
var sharedDefaults = UserDefaults(suiteName: "group.snikket.share");
override func isContentValid() -> Bool {
return account != nil && xmppClient.state == .connected && recipients.count > 0;
}
override func viewDidLoad() {
super.viewDidLoad();
let dbURL = DBConnection.mainDbURL();
if !FileManager.default.fileExists(atPath: dbURL.path) {
let controller = UIAlertController(title: "Please launch application from the home screen before continuing.", message: nil, preferredStyle: .alert);
controller.addAction(UIAlertAction(title: "OK", style: .destructive, handler: { (action) in
self.extensionContext?.cancelRequest(withError: ShareError.firstRun);
}))
self.present(controller, animated: true, completion: nil);
}
}
override func presentationAnimationDidFinish() {
if !sharedDefaults!.bool(forKey: "SharingViaHttpUpload") {
var error = true;
if let provider = (self.extensionContext!.inputItems.first as? NSExtensionItem)?.attachments?.first {
error = !provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String);
}
if error {
self.showAlert(title: "Failure", message: "Sharing feature with HTTP upload is disabled within application. To use this feature you need to enable sharing with HTTP upload in application");
}
}
}
override func didSelectPost() {
if let provider = (self.extensionContext!.inputItems.first as? NSExtensionItem)?.attachments?.first {
if provider.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
provider.loadItem(forTypeIdentifier: kUTTypeFileURL as String, options: nil, completionHandler: { (item, error) in
if let localUrl = item as? URL {
let uti = try? localUrl.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier;
let mimeType = uti != nil ? (UTTypeCopyPreferredTagWithClass(uti! as CFString, kUTTagClassMIMEType)?.takeRetainedValue() as String?) : nil;
let size = try? FileManager.default.attributesOfItem(atPath: localUrl.path)[FileAttributeKey.size] as? UInt64;
self.upload(localUrl: localUrl, type: mimeType, handler: {(remoteUrl) in
guard remoteUrl != nil else {
self.showAlert(title: "Failure", message: "Please try again later.");
return;
}
if self.sharedDefaults!.integer(forKey: "fileDownloadSizeLimit") > 0 {
let hash = Digest.sha1.digest(toHex: remoteUrl!.absoluteString.data(using: .utf8)!)!;
var params: [String: Any] = [
"jids": self.recipients.map({ $0.bareJid.stringValue }),
"name": localUrl.lastPathComponent,
"timestamp": Date()
];
if mimeType != nil {
params["mimeType"] = mimeType;
}
if size != nil {
params["size"] = Int(size!);
}
let localUploadDirUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.snikket.shared")!.appendingPathComponent("upload", isDirectory: true);
if !FileManager.default.fileExists(atPath: localUploadDirUrl.path) {
try? FileManager.default.createDirectory(at: localUploadDirUrl, withIntermediateDirectories: true, attributes: nil);
}
do {
try FileManager.default.copyItem(at: localUrl, to: localUploadDirUrl.appendingPathComponent(hash, isDirectory: false));
self.sharedDefaults!.set(params as Any?, forKey: "upload-\(hash)");
} catch {
print("could not copy a file from:", localUrl, "to:", localUploadDirUrl)
}
}
self.share(url: nil, uploadedFileURL: remoteUrl);
});
} else {
self.showAlert(title: "Failure", message: "Please try again later.");
}
})
} else if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (value, error) in
self.share(url: (value as! URL), uploadedFileURL: nil);
})
// } else if provider.hasItemConformingToTypeIdentifier(kUTTypePlainText as String) {
// provider.loadItem(forTypeIdentifier: kUTTypePlainText as String, options: nil, completionHandler: { (item, error) in
// self.share(text: item as! String);
// });
// } else {
// self.showAlert(title: "Failure", message: "Please try again later.");
}
}
}
override func didSelectCancel() {
xmppClient.disconnect(true);
super.didSelectCancel();
}
override func configurationItems() -> [Any]! {
return [accountConfigurationItem, buddiesConfigurationItem];
}
func showAlert(title: String, message: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert);
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {(action) in
self.extensionContext?.cancelRequest(withError: ShareError.failure);
}));
self.present(alert, animated: true, completion: nil);
}
}
func showAccountSelection() {
if xmppClient.state != .disconnected {
xmppClient.disconnect(true);
}
let controller = storyboard?.instantiateViewController(withIdentifier: "accountSelectionViewController") as! AccountsTableViewController;
// let controller = AccountsTableViewController(style: .plain);
controller.selected = account;
controller.delegate = self;
pushConfigurationViewController(controller);
}
func accountSelection(account: String) {
self.account = account;
self.recipients = [];
validateContent();
self.buddiesConfigurationItem.value = "";
accountConfigurationItem.value = account;
xmppClient.connectionConfiguration.setUserJID(BareJID(account)!);
if let password = getAccountPassword() {
xmppClient.connectionConfiguration.setUserPassword(password);
if let rosterStore: RosterStore = xmppClient.sessionObject.getProperty(RosterModule.ROSTER_STORE_KEY) {
rosterStore.cleared();
}
xmppClient.login();
}
}
func showRecipientsSelection() {
guard account != nil else {
return;
}
let controller = storyboard?.instantiateViewController(withIdentifier: "recipientsSelectionViewController") as! RecipientsSelectionViewController;
controller.selected = recipients;
controller.xmppClient = xmppClient;
controller.delegate = self;
self.rosterController = controller;
pushConfigurationViewController(controller);
}
func recipientsChanged(_ recipients: [JID]) {
self.recipients = recipients;
buddiesConfigurationItem.value = String(recipients.count);
validateContent();
}
func getAccountPassword() -> String? {
guard account != nil else {
return nil;
}
let query: [String: NSObject] = [ String(kSecClass) : kSecClassGenericPassword, String(kSecMatchLimit) : kSecMatchLimitOne, String(kSecReturnData) : kCFBooleanTrue, String(kSecAttrService) : "xmpp" as NSObject, String(kSecAttrAccount) : account! as NSObject ];
var result:AnyObject?;
let lastResultCode: OSStatus = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0));
}
if lastResultCode == noErr {
if let data = result as? NSData {
return String(data: data as Data, encoding: String.Encoding.utf8);
}
}
return nil;
}
func upload(localUrl: URL, type: String?, handler: @escaping (URL?)->Void) {
let size = try! FileManager.default.attributesOfItem(atPath: localUrl.path)[FileAttributeKey.size] as! UInt64;
print("trying to upload", localUrl, "size", size, "type", type as Any);
if let httpModule: HttpFileUploadModule = self.xmppClient.modulesManager.getModule(HttpFileUploadModule.ID) {
httpModule.findHttpUploadComponent(onSuccess: { (results) in
guard !results.isEmpty else {
self.showAlert(title: "Upload failed", message: "Feature not supported by XMPP server");
return;
}
let compJid = results.filter({ (k,v) -> Bool in
return v == nil || v! >= Int(size);
}).first?.key;
guard compJid != nil else {
self.showAlert(title: "Upload failed", message: "Selected object is too big!");
return;
}
httpModule.requestUploadSlot(componentJid: compJid!, filename: localUrl.pathComponents.last!, size: Int(size), contentType: type ?? "application/octet-stream", onSuccess: {(slot) in
print("allocated slot", slot.getUri, slot.putUri);
var request = URLRequest(url: slot.putUri);
slot.putHeaders.forEach({ (k,v) in
request.addValue(v, forHTTPHeaderField: k);
});
request.httpMethod = "PUT";
// let inputStream = InputStream(url: localUrl);
// request.httpBodyStream = inputStream;
request.addValue(type ?? "application/octet-stream", forHTTPHeaderField: "Content-Type");
URLSession.shared.uploadTask(with: request, fromFile: localUrl) { (data, response, error) in
guard error == nil && ((response as? HTTPURLResponse)?.statusCode ?? 500) == 201 else {
print(data as Any, error as Any, response as Any);
self.showAlert(title: "Upload failed", message: "Upload to HTTP server failed.");
return;
}
handler(slot.getUri);
}.resume();
}, onError: {(errorCondition, message) in
self.showAlert(title: "Upload failed", message: message ?? "Please try again later.");
});
}, onError: { (error) in
if error != nil && error! == ErrorCondition.item_not_found {
self.showAlert(title: "Upload failed", message: "Feature not supported by XMPP server");
} else {
self.showAlert(title: "Upload failed", message: "Please try again later.");
}
})
} else {
showAlert(title: "Upload failure", message: "Upload module not available!");
}
}
func share(text: String? = nil, url: URL? = nil, uploadedFileURL: URL? = nil) {
recipients.forEach { (recipient) in
if !contentText.isEmpty || url != nil {
let message = Message();
message.type = StanzaType.chat;
message.to = recipient;
if let text = text {
message.body = contentText.isEmpty ? text : "\(contentText!) - \(text)";
} else if let url = url {
message.body = contentText.isEmpty ? url.description : "\(contentText!) - \(url.description)";
} else {
message.body = contentText;
}
xmppClient.context.writer?.write(message);
}
if let url = uploadedFileURL {
let message = Message();
message.type = .chat;
message.to = recipient;
message.oob = url.description;
xmppClient.context.writer?.write(message);
}
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: {
self.xmppClient.disconnect();
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil);
});
}
// func shareText(url: URL?) {
// print("sharing", contentText as Any, url);
//
// recipients.forEach { (recipient) in
// let message = Message();
// message.type = StanzaType.chat;
// message.to = recipient;
// if let url = url {
// message.body = contentText.isEmpty ? url.description : "\(contentText!) - \(url.description)";
// message.oob = url.description;
// }
// xmppClient.context.writer?.write(message);
// }
//
// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: {
// self.xmppClient.disconnect();
// self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil);
// });
// }
class ShareEventHandler: EventHandler {
weak var controller: ShareViewController?;
func handle(event: Event) {
switch event {
case let e as RosterModule.ItemUpdatedEvent:
DispatchQueue.main.async {
self.controller?.rosterController?.updateItem(item: e.rosterItem!);
}
default:
break;
}
}
}
enum ShareError: Error {
case firstRun
case featureNotAvailable
case tooBig
case failure
}
}

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.snikket.shared</string>
<string>group.snikket.share</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)org.snikket.ios.Share</string>
</array>
</dict>
</plist>

View file

@ -1,11 +0,0 @@
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
"6l2-wd-vXh.title" = "Accounts";
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
"95D-JK-fLM.text" = "Label";
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
"Geq-xy-Tr2.text" = "Label";
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
"wpy-g7-270.title" = "Recipients";

View file

@ -1,11 +0,0 @@
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
"6l2-wd-vXh.title" = "Accounts";
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
"95D-JK-fLM.text" = "Label";
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
"Geq-xy-Tr2.text" = "Label";
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
"wpy-g7-270.title" = "Recipients";

View file

@ -1,11 +0,0 @@
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
"6l2-wd-vXh.title" = "Accounts";
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
"95D-JK-fLM.text" = "Label";
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
"Geq-xy-Tr2.text" = "Label";
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
"wpy-g7-270.title" = "Recipients";

View file

@ -1,11 +0,0 @@
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
"6l2-wd-vXh.title" = "Accounts";
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
"95D-JK-fLM.text" = "Label";
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
"Geq-xy-Tr2.text" = "Label";
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
"wpy-g7-270.title" = "Recipients";

View file

@ -1,11 +0,0 @@
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
"6l2-wd-vXh.title" = "Accounts";
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
"95D-JK-fLM.text" = "Label";
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
"Geq-xy-Tr2.text" = "Label";
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
"wpy-g7-270.title" = "Recipients";

View file

@ -1,11 +0,0 @@
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
"6l2-wd-vXh.title" = "Accounts";
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
"95D-JK-fLM.text" = "Label";
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
"Geq-xy-Tr2.text" = "Label";
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
"wpy-g7-270.title" = "Recipients";

File diff suppressed because it is too large Load diff

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -1,43 +0,0 @@
{
"object": {
"pins": [
{
"package": "HSLuvSwift",
"repositoryURL": "https://github.com/hsluv/hsluv-swift.git",
"state": {
"branch": null,
"revision": "9dee4378180f2a9a33c96bc20284cf64158dbe1b",
"version": "2.1.0"
}
},
{
"package": "libsignal",
"repositoryURL": "https://github.com/tigase/libsignal",
"state": {
"branch": null,
"revision": "d23d5af0d729cf66b93cea607f3f84a34b9fddfd",
"version": "1.0.0"
}
},
{
"package": "TigaseSwift",
"repositoryURL": "https://github.com/tigase/tigase-swift",
"state": {
"branch": null,
"revision": "d3953dcea80010ad433fa96d7ca6b989bf58850c",
"version": "2.1.3"
}
},
{
"package": "TigaseSwiftOMEMO",
"repositoryURL": "https://github.com/tigase/tigase-swift-omemo/",
"state": {
"branch": null,
"revision": "4c997df9a3a686b7e522ddffa4d2f2c070a94fa0",
"version": "1.1.3"
}
}
]
},
"version": 1
}

Some files were not shown because too many files have changed in this diff Show more