Compare commits
1038 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9443313e96 | |||
| 3e79f78bbd | |||
| c152631578 | |||
| b33747374f | |||
| 7a8990260c | |||
| 9b1153da04 | |||
| 38d161f1bf | |||
| 1b9daf60ee | |||
| e71d71cf9c | |||
| 29950b62e4 | |||
| 65de4b333c | |||
| 63fcf4918f | |||
| c9880b0139 | |||
| 13eabb7720 | |||
| 743b773ea3 | |||
| 249eee5dfb | |||
| 90c68d0d19 | |||
| d10a427911 | |||
| 05755a3213 | |||
| e18a633de1 | |||
| 0e68cdf744 | |||
| 7f119a8d0d | |||
| fde1e7e957 | |||
| 0ad96d505c | |||
| d4ff76fc25 | |||
| 85c1395361 | |||
| 1443e57c57 | |||
| 3b75f6213c | |||
| e9d5de64a5 | |||
| 19c61383f6 | |||
| cb6cc97fa4 | |||
| 0651bf0c2a | |||
| 68f9d29b66 | |||
| 24afb1b1ec | |||
| 47f44c488e | |||
| a9b64253f1 | |||
| 506dc646ad | |||
| b50b759c7e | |||
| ed2602e523 | |||
| bbe376cddc | |||
| 50a2b3427a | |||
| ac7c63fc1b | |||
| b50e1d7aa4 | |||
| 454e501cb6 | |||
| 69cb1b7f2e | |||
| 7c8917deb0 | |||
| e5e96b8db9 | |||
| faffea0008 | |||
| e08711ff4b | |||
| c6c99a41db | |||
| ff709e01f2 | |||
| 666bf54dd5 | |||
| fbcd6adc9e | |||
| 2dc3cfb109 | |||
| ae9b9e57cb | |||
| e19dfbff8d | |||
| 6fc2e6d0fe | |||
| 456c3e4a45 | |||
| eaca80f55a | |||
| d60ab56972 | |||
| 6b55e5ba81 | |||
| 8c16ee46cc | |||
| d56e6d5267 | |||
| 1ff84f4f96 | |||
| 3efb3ae1ae | |||
| d516d0cc0a | |||
| d2794890f8 | |||
| ae70457c56 | |||
| 800e45a27a | |||
| 7b6dae062c | |||
| 5070325d9a | |||
| 84511204d2 | |||
| ad8129efdf | |||
| 5f0f32f824 | |||
| 7347a99520 | |||
| 12b2df63b4 | |||
| 00ac394379 | |||
| 8f8cc4de14 | |||
| 577f612778 | |||
| e0f092decc | |||
| acacacdcce | |||
| fce7172bac | |||
| 264d51f1c9 | |||
| 6b1dd7f73d | |||
| ad5b4cb2fd | |||
| 1faa99a490 | |||
| cf67d09e1b | |||
| 14a87b2067 | |||
| 4f59976114 | |||
| 392dd87881 | |||
| fd8c973004 | |||
| ab400a203a | |||
| 7fbd650fbb | |||
| 01cc7ed79d | |||
| 62d5f17c75 | |||
| d198a8fa67 | |||
| 77aa2444a2 | |||
| 281b2093b1 | |||
| 94c2b68e53 | |||
| 99e3b370d5 | |||
| 093513f01f | |||
| 5e39fffe0d | |||
| 0ddd16b202 | |||
| 13fdd71613 | |||
| d000f5b45e | |||
| 19de603340 | |||
| 61af3f9b19 | |||
| f614fda7b3 | |||
| 63fb484f22 | |||
| 6e61d4f79b | |||
| 54feccdcff | |||
| 5bc0088f16 | |||
| e4f7516b6f | |||
| 61290e42fa | |||
| 82720f2978 | |||
| 08af95d3a7 | |||
| 319b9f189e | |||
| d6f71e2cdb | |||
| 92c2299a9d | |||
| ddb2c50aba | |||
| 4d7c27a1dd | |||
| 2e0bef5490 | |||
| 222855fabc | |||
| 97a503cf18 | |||
| 15b6204a9e | |||
| 6f883b4acb | |||
| 762d0350c4 | |||
| ea01dbdbc3 | |||
| 363a1f7caa | |||
| 81b945a129 | |||
| 16a5da4566 | |||
| 1c2af3c669 | |||
| b412f47578 | |||
| d9cfb03dc6 | |||
| 4baa86df01 | |||
| c0c512b9ad | |||
| 95d82db7cf | |||
| 66dbac3bb7 | |||
| d969d55ab1 | |||
| 95e8250194 | |||
| 2539157e47 | |||
| a51b85622a | |||
| 1e25391b0a | |||
| 9cc6255b45 | |||
| 112bb1b7fa | |||
| 041d015dbf | |||
| 877edca1e6 | |||
| c7b2c33a29 | |||
| ca1a18978f | |||
| 0a962c6822 | |||
| f07ae4b8f0 | |||
| b0d7eff709 | |||
| 390c02d2a8 | |||
| 233142920f | |||
| a4a147332d | |||
| f1fdddf5d4 | |||
| 489de1d8aa | |||
| 52578e21fe | |||
| 92fc374b46 | |||
| c87b189f8a | |||
| b7cf4bc918 | |||
| b803c3aab0 | |||
| 3094ba675c | |||
| 88b7c9a68f | |||
| 06e1e9bbfc | |||
| 36b8a72056 | |||
| 111b983716 | |||
| 599a723dd1 | |||
| 7f37428426 | |||
| 630a431299 | |||
| ab081e28e1 | |||
| 8b912b7d25 | |||
| 375e1380d7 | |||
| fdd3d7c653 | |||
| 18ea1236e2 | |||
| 18842a0e64 | |||
| 7a3d0606d5 | |||
| 528186535c | |||
| bd4c1a1568 | |||
| 08382cf7a3 | |||
| 1080e4f08d | |||
| 81c9fd83e3 | |||
| f65b22993b | |||
| e9059ee42c | |||
| f4feaffe19 | |||
| 176da46dd4 | |||
| e4a023713d | |||
| 7cfc964143 | |||
| b4b363cac6 | |||
| 3deaee1ef0 | |||
| 330587f6f8 | |||
| 7615ed631d | |||
| 567c1802ba | |||
| 400ba05d8e | |||
| 3cd8cd7661 | |||
| 612b411dc2 | |||
| cd216ab1c0 | |||
| 610d30fb0a | |||
| f81825bb78 | |||
| eb5c34b35a | |||
| 7ea163bc14 | |||
| acc45033b7 | |||
| aa5bfa8142 | |||
| 070950b31a | |||
| 981c1af7a8 | |||
| 2cbf211613 | |||
| 77de9ddf05 | |||
| 1dfca472e2 | |||
| 4b9dc94927 | |||
| f3eec95c98 | |||
| 005b4b744f | |||
| 002939a0ec | |||
| 35e5d5aa95 | |||
| 547e1b5cd2 | |||
| 421e90615f | |||
| 3538a50e55 | |||
| 30a758e2b7 | |||
| 9ef1876c45 | |||
| b06ffbb44e | |||
| 60076032b9 | |||
| c2b664f3b8 | |||
| fc61990e03 | |||
| ca81287311 | |||
| db995e3b35 | |||
| 1a902eefdd | |||
| 5c3fe6eaef | |||
| a033e1cc5e | |||
| fbb7892ea6 | |||
| 336096f680 | |||
| f3929e0e12 | |||
| 3736cf573e | |||
| 336ab0e21b | |||
| 6459a4a5a0 | |||
| d04c38b4a0 | |||
| 535cec7f08 | |||
| 843895e1e4 | |||
| fe29159d14 | |||
| 7c883966d0 | |||
| 332c8299fa | |||
| 120612cae0 | |||
| dbb525a469 | |||
| 4dea6d8a7b | |||
| c57d3a1e1f | |||
| 77a15aa734 | |||
| aa482452e0 | |||
| f1f322d442 | |||
| 8191a977b7 | |||
| 54c3333b81 | |||
| ad755bb222 | |||
| 920127d096 | |||
| 2696f2377b | |||
| 81fdbdac57 | |||
| dd04625562 | |||
| 98e8c8e09a | |||
| 2da27dddd6 | |||
| e40be2e5f6 | |||
| 79228e8ab7 | |||
| 161149d2df | |||
| a38561bfed | |||
| 74f44435ef | |||
| 66a754d24f | |||
| 258e2623f3 | |||
| 7043316b90 | |||
| fcb980beb8 | |||
| d186e5c6df | |||
| 5822caa980 | |||
| bb55a603db | |||
| a75938f6bb | |||
| 23ba6e97a8 | |||
| 053625a0a1 | |||
| 682cd83213 | |||
| 55a2833c6e | |||
| c6e2df2a6e | |||
| a3b6732406 | |||
| d2fade561b | |||
| ab6600eaf2 | |||
| 815f5bf60a | |||
| e9ecae2720 | |||
| c9f49d9f18 | |||
| 73faa2e59f | |||
| 70889a0671 | |||
| 26101d0068 | |||
| 340c5abdc0 | |||
| 0c121f283b | |||
| f50587eaf4 | |||
| fad50b20e0 | |||
| 99be8240b0 | |||
| b6028a9eef | |||
| fcad24f94b | |||
| 211aa5a118 | |||
| 53842c3231 | |||
| b6ab7414fd | |||
| e56ac6b2e9 | |||
| 0da343ea6f | |||
| 1fc475e826 | |||
| dc0890233f | |||
| 15a8deba02 | |||
| ac27b6d8fa | |||
| 8625fa19d6 | |||
| 6e0a3a5c4a | |||
| 388a20a79b | |||
| ef0c8d0495 | |||
| 97fb686ad4 | |||
| 2c0a6d7761 | |||
| 0070d3ca04 | |||
| 6d0c331aba | |||
| 0d81a9c696 | |||
| 1e7d2c2460 | |||
| 3197a62a3a | |||
| 4cdffbd061 | |||
| fad570f6e9 | |||
| 05e4011f2a | |||
| b28785d160 | |||
| 2b25f66086 | |||
| 54f7a9b53a | |||
| 0d6bd0e3c4 | |||
| ed3d787972 | |||
| 834e992bf0 | |||
| 4a894d2b3c | |||
| 6848c067d0 | |||
| ff3fe765f1 | |||
| ecc6d2b6f9 | |||
| 8f633dcb43 | |||
| a62b853500 | |||
| 0a12a96af5 | |||
| 62bba0b6c9 | |||
| b7c6772266 | |||
| 224ce04a41 | |||
| 05b6f308e3 | |||
| 3f96750424 | |||
| e7e501c087 | |||
| 3aeb8dd9f9 | |||
| d30331a403 | |||
| 1726f5353d | |||
| b46a5f4d83 | |||
| e1d47c2882 | |||
| dbc21a1c2a | |||
| f73645127c | |||
| 95425a81ae | |||
| 8a44bceb99 | |||
| 543fb892b1 | |||
| e3fad9e1d9 | |||
| 9f2e77d103 | |||
| 0478b552dc | |||
| 0598cbdbd3 | |||
| c1e6dc0a54 | |||
| c4968de7d7 | |||
| f02d401737 | |||
| 19ee558b57 | |||
| 8fde58535b | |||
| ec33ec5f8c | |||
| d23db60948 | |||
| fd36ad5463 | |||
| 06360f246d | |||
| 7f1226e74a | |||
| 0799ec32c9 | |||
| ae2333f2e4 | |||
| 6da6842adb | |||
| a016834614 | |||
| 6d0e6e1931 | |||
| 65c5ca082b | |||
| a8bbd7d4d8 | |||
| b5cdb5b7fe | |||
| 781c080033 | |||
| cd8e0ebb00 | |||
| b32c87a3b2 | |||
| 9ea3d8bb09 | |||
| a1600c6e0b | |||
| ebc0eeef7d | |||
| 020b21f261 | |||
| 403ef4e6ee | |||
| 74d3f60562 | |||
| bf3b66e6ff | |||
| 9265d45878 | |||
| f07c4fe7ff | |||
| 81e106dde0 | |||
| cb45004eb0 | |||
| f683a494df | |||
| f7e2e39e1c | |||
| 6147283a63 | |||
| c424ada8fd | |||
| 2a02e94987 | |||
| 2be7575a93 | |||
| 326e44b055 | |||
| ba58b4dbad | |||
| 0ee66f6a26 | |||
| 261ef01f93 | |||
| f1083cf096 | |||
| 4b5a587eb8 | |||
| 4bdd148b53 | |||
| 6bad43fb9f | |||
| 34040ac1cd | |||
| 4a58bff1fd | |||
| d8391522db | |||
| 4ce1635da5 | |||
| 26c7884002 | |||
| 3bc160c5b2 | |||
| 7e21d2486d | |||
| 1921a72f26 | |||
| ce93665ba2 | |||
| e7f72ffde2 | |||
| 94a4e8b261 | |||
| f605a10dfb | |||
| aa2fd02c67 | |||
| acf5f194f0 | |||
| 69e2aeb202 | |||
| ebc948a8cc | |||
| 584302cd2f | |||
| b4bfe14641 | |||
| 57ca376732 | |||
| bb30966458 | |||
| a40b67f458 | |||
| d0897d7166 | |||
| 729fc5355a | |||
| b83684fac9 | |||
| 6ea0e4d248 | |||
| f730ed5a00 | |||
| 30e5a09c0c | |||
| 98e2ae73e4 | |||
| 100f744827 | |||
| 9ea558d233 | |||
| d3ceaabce0 | |||
| cf62288080 | |||
| a8e541a462 | |||
| 3c29db9917 | |||
| 2b682cd197 | |||
| cf753bea67 | |||
| a4e1d13bad | |||
| 268af9b05d | |||
| 9b47a271a4 | |||
| 8608143e40 | |||
| 01534209ac | |||
| ce75fc84bb | |||
| 4f1b8e1b89 | |||
| bb6be3bde1 | |||
| 52046a60bc | |||
| aba6fb5889 | |||
| 239ad2d8c9 | |||
| c220e6dfbd | |||
| 5d0cfbf9de | |||
| 644a563d20 | |||
| c49ff60d73 | |||
| 892713bec4 | |||
| 60ca971d8e | |||
| e8f695ee32 | |||
| 7941f76033 | |||
| f871fafed6 | |||
| fbd02ffc59 | |||
| 371c729a48 | |||
| 9f9cbd9fee | |||
| 1341070411 | |||
| ed26df05ae | |||
| 6583215cda | |||
| c77823dfa8 | |||
| ec62232d9f | |||
| a54a266335 | |||
| 734e85ead6 | |||
| 5182776e1f | |||
| 57f896b86f | |||
| abea94060e | |||
| 8a21932d62 | |||
| 07469cf2c1 | |||
| b684c9f2f8 | |||
| d58ac3982c | |||
| fbdca00cf2 | |||
| 49677ea0f8 | |||
| a00a237c29 | |||
| caed724ce2 | |||
| cbd1ccb13d | |||
| fa23b2a841 | |||
| d853c54935 | |||
| 8c0236e33a | |||
| ac18d816ee | |||
| 143184f792 | |||
| 6beb7bfbf1 | |||
| b2707702f1 | |||
| 1e87138b9a | |||
| 91c6e0e7da | |||
| 5c6e8662c5 | |||
| 25444b68fd | |||
| d9f2e1171e | |||
| 101e792254 | |||
| 266b9f1c71 | |||
| dcae4e5775 | |||
| edc9f445ab | |||
| a23a22f7bf | |||
| aea49a7950 | |||
| e7a74e4951 | |||
| 78ac8e03f2 | |||
| acc5ff2ba9 | |||
| 28d431c664 | |||
| 9f980e1144 | |||
| 259446fc8c | |||
| 95223d3011 | |||
| b61f071020 | |||
| be31c5784d | |||
| 14f8d7ee6c | |||
| b32f3db623 | |||
| 19a3cd7074 | |||
| fe8f0a2234 | |||
| 4b9c70ed73 | |||
| 5592966027 | |||
| a9a0946c7e | |||
| 8ff4db8384 | |||
| 4019c380eb | |||
| fe7fc48901 | |||
| 7634eabd0a | |||
| 3089a3bef5 | |||
| cc3afe5102 | |||
| d75ba09918 | |||
| 5376359c1f | |||
| 9102800ced | |||
| 3133dd38df | |||
| bd8ca78ed1 | |||
| 92fdcf46a6 | |||
| 2134d5fe3a | |||
| 44a5e21fd0 | |||
| 2aa0463673 | |||
| f39f8b01a5 | |||
| b28b37c4ed | |||
| d7ddf072aa | |||
| 0610718029 | |||
| 2d4b6a566a | |||
| b71a07c108 | |||
| 16bb829e6a | |||
| 65985167c0 | |||
| 0f0e95473a | |||
| 0032b6708a | |||
| 425b858206 | |||
| 5787764eaf | |||
| b829641bd0 | |||
| ac7cab60dd | |||
| 5fe7c7f49e | |||
| 403d93d064 | |||
| ca68d65605 | |||
| 4c6ff1c90c | |||
| c463359725 | |||
| decab237e8 | |||
| 6577436bd4 | |||
| 4132c20897 | |||
| d520f2963d | |||
| 6da912d08a | |||
| 4e61599b1f | |||
| c8566b00bf | |||
| e567a5071c | |||
| 87fec7ce3a | |||
| 51e4761e27 | |||
| 774e638ca7 | |||
| 79b2095fbd | |||
| 05d716c5a2 | |||
| 3a1f004452 | |||
| f0db5c3a11 | |||
| d513fbd6f3 | |||
| c2dfba9870 | |||
| 1dc28a703b | |||
| d43c1168e5 | |||
| 1d2715afb4 | |||
| 33609c0fd8 | |||
| e2f178c0e9 | |||
| 80720c1281 | |||
| a118762015 | |||
| 1a5d537030 | |||
| f1664e5901 | |||
| 16dfd0e677 | |||
| e10bb71e03 | |||
| 580e8c9347 | |||
| 51b4c2e539 | |||
| 715d5a7301 | |||
| c8195bddbc | |||
| bd2319e74d | |||
| ee443eadee | |||
| 39056d50ab | |||
| feebf2f932 | |||
| f49b3015c1 | |||
| 470582b176 | |||
| 6c4075258b | |||
| 4bf5df0a19 | |||
| fbb39e1cc5 | |||
| 6319d2861e | |||
| 9c6c08380b | |||
| 25a71c950d | |||
| 271bbba44b | |||
| 83d28ede08 | |||
| f10573e71b | |||
| 06a58fab3a | |||
| b9c5ed66bb | |||
| db51765a9b | |||
| 5a05aed6ee | |||
| d50a51c769 | |||
| d922ed87f5 | |||
| 15cc58b874 | |||
| 18b730169f | |||
| 7a1d69a9d1 | |||
| 2d6efeb430 | |||
| 9cbba81572 | |||
| 3108e24830 | |||
| 2eefc77cbe | |||
| e61c9463a5 | |||
| c1567e8ae6 | |||
| af08beeb4c | |||
| 86beb05399 | |||
| 7b8b4dd2ce | |||
| b3dfb08f50 | |||
| b0bf985469 | |||
| 7e4967ca06 | |||
| e8ed8d2c7e | |||
| d55689dc5e | |||
| e2a52c7188 | |||
| c7ea76a983 | |||
| 73aa1157e6 | |||
| 0a40250aea | |||
| c3e7db8c0f | |||
| a0ef5b1715 | |||
| 6f45964342 | |||
| 31a86204a9 | |||
| 2844102696 | |||
| 82bb27199a | |||
| a3f9680006 | |||
| f851397de0 | |||
| e998d0747c | |||
| 3682da0aab | |||
| 973beb0fa8 | |||
| e8e1c36692 | |||
| b7ddc6d195 | |||
| 24dca514a7 | |||
| 60ccc4bfad | |||
| 9efac8e6a2 | |||
| 0695468020 | |||
| b5e625b46f | |||
| 3e39509af2 | |||
| ea00c5862e | |||
| e6b3ad79f0 | |||
| ef1c55cb8e | |||
| b3b484ab61 | |||
| 51acba1999 | |||
| a984ace443 | |||
| b0a915a30c | |||
| 5d9bb6c833 | |||
| 87e9e49519 | |||
| d772f3885a | |||
| 6b7277cf72 | |||
| 45e7f6b529 | |||
| feae58cde6 | |||
| 9a635b7227 | |||
| 292ad92204 | |||
| 2ba1e8a2fd | |||
| 75d8e0d167 | |||
| 8e2d6a1f34 | |||
| 30f434f18f | |||
| 4645015361 | |||
| aa15382dc1 | |||
| 1e8f90f0c8 | |||
| 6886f9345c | |||
| f561f2c51d | |||
| 7b40d16da2 | |||
| 184b1938e5 | |||
| a2d2b13d58 | |||
| e52e22e8f5 | |||
| 56c3d19cb1 | |||
| 724dd34bac | |||
| 7c6e83a0fc | |||
| 11228283fa | |||
| fa0f23320b | |||
| 05be5f9ae7 | |||
| e6e4698306 | |||
| 2efdb9d56f | |||
| 6907cedf95 | |||
| 113b29ba56 | |||
| 221996b73a | |||
| c309d69349 | |||
| 4dffafc10d | |||
| b46ebf3ded | |||
| b9351232fc | |||
| b04a5aa6a4 | |||
| 76cec79983 | |||
| 3697e01b6f | |||
| c3d25d6edc | |||
| 261bc598f0 | |||
| eba62c4857 | |||
| a4e49d490d | |||
| 6bde09345f | |||
| a81c577fc3 | |||
| 2058936108 | |||
| 5be15107b3 | |||
| fb6dd4782a | |||
| 4a944cc972 | |||
| 022144ad28 | |||
| 238cae5d31 | |||
| 0870abc22b | |||
| 3b74dd93f6 | |||
| d82eae5f94 | |||
| 14b27ada27 | |||
| 56a261eb27 | |||
| 988371fb28 | |||
| 0a377eda53 | |||
| 1fc013742a | |||
| 2a196bc768 | |||
| 6bb2313a1f | |||
| dcd0be1132 | |||
| 8ba80ac79d | |||
| ceca4baf42 | |||
| e83c26208b | |||
| 30fd3c0cc6 | |||
| 22f6aeb066 | |||
| 343be912d0 | |||
| 0972a789a4 | |||
| fe994cea75 | |||
| 7fe617e765 | |||
| d451b26788 | |||
| 6632633322 | |||
| 2841a8f348 | |||
| a0e1317c22 | |||
| bfe3e691e1 | |||
| 392ee67733 | |||
| a3b9ec6d3d | |||
| 109f69b9df | |||
| 79d61223c6 | |||
| f97b4923b6 | |||
| e391fbf254 | |||
| 8de0d5c755 | |||
| e030fff263 | |||
| f4753aacf1 | |||
| 2075cf8fb1 | |||
| e9e165e10d | |||
| 121507db1c | |||
| 139ad4f8b6 | |||
| 96712fcfcd | |||
| bac6fff8f1 | |||
| cb94f06094 | |||
| 377120ac38 | |||
| 161e56c738 | |||
| ce50dbccea | |||
| 4ff30c68fe | |||
| 8e3aaa4246 | |||
| 4f087ce783 | |||
| f03084534d | |||
| 4da2747943 | |||
| 10bb15f253 | |||
| 15601c420a | |||
| 8d8b2e3db6 | |||
| 945d5e9f25 | |||
| b87babb66c | |||
| c5d864dbb4 | |||
| 9e9ba10a52 | |||
| 06fe8e8ce9 | |||
| 692d7b50a4 | |||
| 55dd93238e | |||
| 69e642792d | |||
| 4c0afc0037 | |||
| a8777b84b5 | |||
| 5c4d36c398 | |||
| c25a9954b2 | |||
| a7bb837e4b | |||
| ca99dcbc2d | |||
| 9f06cc0575 | |||
| 2dba4fd450 | |||
| 47fc7c8475 | |||
| f7c9ad2692 | |||
| a600a87480 | |||
| 1cd821b2da | |||
| 563a081168 | |||
| b3f7f0ad90 | |||
| d3dd1934c6 | |||
| a133f6a3a0 | |||
| 442968c622 | |||
| 4f6af4ed83 | |||
| 47191bfdbc | |||
| 0f5dca38e4 | |||
| 161001357d | |||
| 2de86b189e | |||
| f739fed350 | |||
| 3a6fc380cf | |||
| 2208a8bcfd | |||
| 76b6b1e995 | |||
| 56243d7cd6 | |||
| 28c5e75375 | |||
| df4be2f4c4 | |||
| 9a778eafcd | |||
| 4c8a05251c | |||
| c0851d8f2e | |||
| 5c69459f6b | |||
| 66adcfd387 | |||
| 1a6f84b706 | |||
| 82dc103bbf | |||
| c17e90d775 | |||
| d649b4a14d | |||
| 7a6dd5c580 | |||
| 65537987df | |||
| 1ae3f86279 | |||
| e4d2187871 | |||
| 9499035725 | |||
| 3680d05f38 | |||
| 42a2d0efb8 | |||
| 0a8165ca35 | |||
| 325142bec2 | |||
| 9b137318e0 | |||
| 2330a21426 | |||
| a09b5f3a70 | |||
| e57e99b290 | |||
| 64780981f2 | |||
| 787844d2a0 | |||
| ac74dfc416 | |||
| 8f9f2f6de5 | |||
| 56d45c3a96 | |||
| f45a6f629f | |||
| 0d11c9f9fd | |||
| 2843759bcc | |||
| a6509da0b1 | |||
| fc8c18ec0a | |||
| badfbf0ad1 | |||
| 3a630b3959 | |||
| a2a6fef6aa | |||
| 2986d85f58 | |||
| 5ee602bbee | |||
| ee317862bb | |||
| 182cb27551 | |||
| e54f09d973 | |||
| dd7eb78775 | |||
| eae5eb5458 | |||
| a70323b73a | |||
| d36b53336b | |||
| 0a2c937ef2 | |||
| 3d5dfec624 | |||
| e9569123fd | |||
| d5ac624b24 | |||
| 5aade34809 | |||
| d7b44f890e | |||
| 250a18ee26 | |||
| 1446df4559 | |||
| 1afe85b259 | |||
| a93e0c15b8 | |||
| d0c261b36b | |||
| b1bc2e649d | |||
| 61eb619e3d | |||
| 807d24b76e | |||
| 80d707b094 | |||
| 601692b991 | |||
| 4b143997b8 | |||
| 6438c4770a | |||
| ab20d025e3 | |||
| e8660e83ad | |||
| ddc90812d1 | |||
| c024cbd503 | |||
| 0c9fb3b357 | |||
| 7212f3c663 | |||
| 9a68704218 | |||
| b85949bf7b | |||
| a9d1ef573d | |||
| dc185c69e4 | |||
| 32abf1f6f5 | |||
| 742b6f01ab | |||
| 2af14cfa68 | |||
| 2a3ffd2b18 | |||
| 2f72c15451 | |||
| 74a8a0ec6d | |||
| 06ce98338a | |||
| 541ae005b6 | |||
| 090877e5d9 | |||
| 3b41a38158 | |||
| f57ec43e1f | |||
| e17fb8008d | |||
| e818d99489 | |||
| 2fe68f6ac0 | |||
| ec839b490c | |||
| a2ae36e08c | |||
| e9e6e781ae | |||
| b97d728f7a | |||
| b2ec0051ba | |||
| 2769f3f880 | |||
| 99195db143 | |||
| 44ba432e8a | |||
| 33673f320a | |||
| d90c266151 | |||
| a5115044eb | |||
| d0fc7490fd | |||
| e6c4055dec | |||
| 8c1a53cfdf | |||
| 1635216e5c | |||
| f616083f3c | |||
| a4991240e2 | |||
| ac0817adf1 | |||
| bdf5f25335 | |||
| 849aba6a3d | |||
| 5e08420307 | |||
| ad08365514 | |||
| 1f574ba055 | |||
| 956e338c98 | |||
| 017a28e736 | |||
| 48e5e0baf7 | |||
| 607c96b5d3 | |||
| 8b7eb71b1f | |||
| c8a43499c5 | |||
| 02828eff5a | |||
| 06f16e780b | |||
| e4918e28bd | |||
| 919ba1e7b2 | |||
| e8e00503a4 | |||
| bf0450af0a | |||
| 7752710d0a | |||
| c7ac9c9a35 | |||
| e57fa02765 | |||
| d42cc93683 | |||
| 4ad73b30e4 | |||
| f885de099b | |||
| 8abaa9e3d7 | |||
| f50bfc7ff6 | |||
| c737c14188 | |||
| 67dbd30e87 | |||
| 75b7c2d4e3 | |||
| 449b4c056f | |||
| 92d63e3a8b | |||
| 33dd18300d | |||
| b77119a510 | |||
| ce0cfe50f3 | |||
| 69160ed4a4 | |||
| 8819cefdd7 | |||
| d238065e25 | |||
| 89d1860902 | |||
| ea113f7b66 | |||
| 677e6db943 | |||
| aef19ea441 | |||
| c41536905e | |||
| ba5e03e247 | |||
| e88c3ad800 | |||
| 97b680d61e | |||
| 2cb6fea5bd | |||
| 2508623efa | |||
| dcf59ccd67 | |||
| 7448431a3a | |||
| 0779f9ca1a | |||
| 883004f331 | |||
| ed2ffabb8e | |||
| dd96c41fa5 | |||
| a4f69dca57 | |||
| 9eba881ce2 | |||
| aa9596b4bb | |||
| e1103d8547 | |||
| a0a74e83e7 | |||
| 8c8072cdec | |||
| 7a4dd58df6 | |||
| 8314e7d9a1 | |||
| 7696d28f05 | |||
| c4fe887fe5 | |||
| f38e31936e | |||
| 339d364b40 | |||
| 519ad1aaf9 | |||
| 203f48f8dc | |||
| a4cb2723da | |||
| cb4e06a4c9 | |||
| 01f4312dce | |||
| f1457b69b1 | |||
| 692ec6e007 | |||
| 28ceb9ebe5 | |||
| 9a69dbbd67 | |||
| 9bd4df0996 | |||
| 7dd98c8033 | |||
| b3934066ed | |||
| 57058a66f5 | |||
| 0e5f58736a | |||
| 737fa76bc9 | |||
| a1b84fbf1e | |||
| 82eb17de13 | |||
| 5c3072b791 | |||
| 740a7a4bf7 | |||
| 5029c7f3a8 | |||
| 4cac405737 | |||
| 4025e0f427 | |||
| 67643271ae | |||
| e207270b5d | |||
| cdc8edf78f | |||
| f5b3bbc4dd | |||
| ef0164900a | |||
| b08a2a8627 | |||
| 90c2ee018e | |||
| cc1bfbf7eb | |||
| ea6b1278ce | |||
| 956e846eb4 | |||
| 358ffcab78 | |||
| cc6bfe72e5 | |||
| 7fe111b750 | |||
| 043f6258a3 | |||
| 2f55113c11 | |||
| 01e6728926 | |||
| 9f03f45d1b | |||
| 08dfa3fa8d | |||
| 26f2563a1a | |||
| ed1aa683df | |||
| 22589511bb | |||
| 4a00ebb8c5 | |||
| 1e3b3f24fa | |||
| c204c39daa | |||
| d9198f5d13 | |||
| 8c98958d17 | |||
| cc18572956 | |||
| fe2e5820c4 | |||
| 85bea88d6a | |||
| 396b9c8e13 | |||
| 937e615377 | |||
| 6e052a8b1a | |||
| b71eb952b3 | |||
| c6bb31f514 | |||
| 24c540e70c | |||
| 0361f242a0 | |||
| 9d614ded2e | |||
| 3e9cc99324 | |||
| 43ce339890 | |||
| 3be5fe1925 | |||
| 90d403e62b | |||
| 7f0ca4ec88 | |||
| fc36bca1bd | |||
| 951193cdb8 | |||
| 0cb0f5c8ff | |||
| 2ff5a335e2 | |||
| 6a222529af | |||
| fd2edde5a4 | |||
| 38c379476b | |||
| 45eb65e721 | |||
| 7099ed295a | |||
| 963935d659 | |||
| 1aa39859e9 | |||
| 64e6c8c9a7 | |||
| 0f3c8a88b8 | |||
| b596e6580d | |||
| ea848aacf9 | |||
| 99195dde40 | |||
| 3ef94e0958 | |||
| 38a0394005 | |||
| 541e159792 | |||
| 888ff9aea3 | |||
| 427b1a3aea | |||
| 711f39eca9 | |||
| 13cfe54a3e | |||
| 2e6643022e | |||
| 44db125626 | |||
| c7be2b44ee | |||
| a4777c8a42 | |||
| 7ceda4a9c3 | |||
| 24a03d6ecf |
@@ -0,0 +1 @@
|
||||
LFS-Files/** filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -0,0 +1,2 @@
|
||||
[lfs]
|
||||
fetchexclude = *
|
||||
-187
@@ -1,187 +0,0 @@
|
||||
Contributing to the ResearchKit Framework
|
||||
===========================
|
||||
|
||||
This page focuses on code contributions to the existing
|
||||
codebase. However, other types of contributions are welcome too, in
|
||||
keeping with the ResearchKit™ framework [best practices](../../wiki/best-practices). For example,
|
||||
contributions of original free-to-use survey content, back-end integrations,
|
||||
validation data, and analysis or processing tools are all welcome. Ask
|
||||
on [researchkit-dev](https://lists.apple.com/mailman/listinfo/researchkit-dev) or [contact us](https://developer.apple.com/contact/researchkit/) for guidance.
|
||||
|
||||
|
||||
Contributing software
|
||||
---------------------
|
||||
|
||||
This page assumes you already know how to check out and build the
|
||||
code. Contributions to the ResearchKit framework are expected to comply with the
|
||||
[ResearchKit Contribution Terms and License Policy](#contribution); please familiarize yourself
|
||||
with this policy prior to submitting a pull request. For any contribution, ensure that you own
|
||||
the rights or have permission from the copyright holder. (e.g. code, images, surveys, videos
|
||||
and other content you may include)
|
||||
|
||||
To contribute to ResearchKit:
|
||||
|
||||
1. [Choose or create an issue to work on.](#create)
|
||||
2. [Create a personal fork of the ResearchKit framework.](#fork)
|
||||
3. [Develop your changes in your fork.](#develop)
|
||||
4. [Run the tests.](#test)
|
||||
5. [Submit a pull request.](#request)
|
||||
6. Make any changes requested by the reviewer, and update your pull request as needed.
|
||||
7. Once accepted, your pull request will be merged into master.
|
||||
|
||||
Choosing an issue to work on<a name="create"></a>
|
||||
----------------------------
|
||||
|
||||
To find an issue to work on, either pick something that you need for
|
||||
your app, or select one of the issues from our [issue list](../../issues). Or,
|
||||
consider one of the areas where we'd like to extend ResearchKit:
|
||||
|
||||
* Faster 'get started' to a useful app
|
||||
* More active tasks
|
||||
* Data analysis for active tasks
|
||||
* More consent sections
|
||||
* Back end integrations
|
||||
|
||||
If in doubt, bring your idea up on [researchkit-dev](https://lists.apple.com/mailman/listinfo/researchkit-dev).
|
||||
|
||||
|
||||
Creating a personal fork<a name="fork"></a>
|
||||
------------------------
|
||||
|
||||
On GitHub, it's easy to create a personal fork. Just tap the "Fork"
|
||||
button on the top right, and clone your new repository.
|
||||
|
||||
|
||||
Develop your changes in your fork<a name="develop"></a>
|
||||
---------------------------------
|
||||
|
||||
Develop your changes using your normal development process. If you
|
||||
already have code from an existing project, you may need to adjust its
|
||||
style to more closely match the [ResearchKit framework coding style](./docs-standalone/coding-style-guide.md).
|
||||
|
||||
New components may need to expose new Public or Private
|
||||
headers. Public headers are for APIs that are likely to be a stable
|
||||
part of the interface of the ResearchKit framework. Private headers are for APIs that
|
||||
may need to be accessed from app-side unit tests, or that are more
|
||||
subject to change than the public interface. All other headers should
|
||||
be internal, "Project" headers.
|
||||
|
||||
Please review and ensure that any contributions you make comply with
|
||||
the [ResearchKit Contribution Terms and License Policy](#contribution).
|
||||
|
||||
Add automated tests for your feature, where it is possible to do
|
||||
so. For UI driven components where it is harder to write automated
|
||||
tests, add UI to at least one test application so that the new
|
||||
features can be reviewed and tested. Consider also whether to add new
|
||||
code to other existing demo apps to exercise your feature.
|
||||
|
||||
When adding UI driven components, make sure that they are accessible.
|
||||
Follow the steps outlined in the [Best Practices](../../wiki/best-practices)
|
||||
section under Accessibility. Before submitting the pull request, you should
|
||||
audit your components with Voice Over (or other relevant assistive technologies)
|
||||
enabled.
|
||||
|
||||
Keep changes that fix different issues separate. For bug fixes,
|
||||
separate bugs should be submitted as separate pull requests. A good
|
||||
way to do this is to create a new branch in your fork for each new
|
||||
bug work on.
|
||||
|
||||
Any new user-visible strings should be included in the English
|
||||
`ResearchKit.strings` table so that they can be picked up and
|
||||
localized in the next release cycle.
|
||||
|
||||
|
||||
Run the tests<a name="test"></a>
|
||||
-------------
|
||||
|
||||
All unit tests should pass, and there should be no warnings. Also
|
||||
verify that test apps run on both device and simulator.
|
||||
|
||||
Where your code affects UI presentation, also test:
|
||||
|
||||
* Multiple device form factors (for instance, iPhone 4S, iPhone 5, iPhone 6, iPhone 6 Plus).
|
||||
* Dynamic text, especially at the "Large" setting.
|
||||
* Rotation between portrait and landscape, where appropriate.
|
||||
|
||||
You can use the apps in the `Testing` and `samples` directories to
|
||||
test your changes.
|
||||
|
||||
Submit a pull request<a name="request"></a>
|
||||
---------------------
|
||||
|
||||
The reviewers may request changes. Make the changes, and update your
|
||||
pull request as needed. Reviews will focus on coding style,
|
||||
correctness, and design consistency.
|
||||
|
||||
This process does not take the place of an ethical review, for example
|
||||
by an institutional review board (IRB) or ethics committee.
|
||||
|
||||
After acceptance<a name="after"></a>
|
||||
----------------
|
||||
|
||||
Once your pull request has been accepted, your changes will be merged
|
||||
to master. You are still responsible for your change after it is
|
||||
accepted. Stay in contact, in case bugs are detected that may require
|
||||
your attention.
|
||||
|
||||
When the project is next branched for release, your changes will be
|
||||
incorporated. Queries may come back to you regarding localization,
|
||||
documentation, or other issues during this process.
|
||||
|
||||
|
||||
|
||||
|
||||
Release process
|
||||
-----------------
|
||||
|
||||
The `master` branch is used for work in progress. On `master`:
|
||||
|
||||
* All test apps should build and run error free.
|
||||
* Unit tests should all pass.
|
||||
* Everything should be continuously in working order in English (the
|
||||
base language).
|
||||
|
||||
The project will make periodic releases. When preparing a stable release, we
|
||||
will branch from `master` to a convergence branch. During this process,
|
||||
changes will be made first to the convergence branch, and then merged into
|
||||
`master`. On the convergence branch, changes will be made only to:
|
||||
|
||||
* Fix high priority issues.
|
||||
* Update documentation.
|
||||
* Bring localization up to date.
|
||||
* Ensure good behavior across all supported devices.
|
||||
|
||||
After the converging process is completed, we will merge everything to the
|
||||
`stable` branch and tag with a new release number. The most recent release
|
||||
will be highlighted in the [README](../..).
|
||||
|
||||
|
||||
ResearchKit Contribution Terms and License Policy<a name="contribution"></a>
|
||||
=======================================
|
||||
|
||||
Thank you for your interest in contributing to the ResearchKit
|
||||
community. In order to maintain consistency and license compatibility
|
||||
throughout the project, all contributions must comply with our
|
||||
licensing policy and terms for contributing code to the ResearchKit
|
||||
project:
|
||||
|
||||
1. If you are submitting a patch to the existing codebase, you
|
||||
represent that you have the right to license the patch, including
|
||||
all code and content, to Apple and the community, and agree by
|
||||
submitting the patch that your changes are
|
||||
licensed under the existing license terms of the file you are
|
||||
modifying (i.e., [ResearchKit BSD license](LICENSE)).
|
||||
You confirm that you have added your copyright (name and year) to
|
||||
the relevant files for changes that are more than 10 lines of code.
|
||||
2. If you are submitting a new file for inclusion in the ResearchKit
|
||||
framework (no code or other content is copied from another source), you
|
||||
have included your copyright (name and year) and a copy of the ResearchKit
|
||||
BSD license. By submitting your new file you represent that you have the
|
||||
right to license your file to Apple and the community, and agree that your
|
||||
file submission is licensed under the ResearchKit BSD license.
|
||||
3. If you aren't the author of the patch, you agree that you have
|
||||
the right to submit the patch, and have included the original copyright
|
||||
notices and licensing terms with it, to the extent that they exist.
|
||||
If there wasn't a copyright notice or license, please make a note of it
|
||||
in your response. Generally we can only take in patches that are
|
||||
BSD-licensed in order to maintain license compatibility within the project.
|
||||
@@ -1,28 +0,0 @@
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@@ -1,283 +1,178 @@
|
||||
|
||||

|
||||
|
||||
ResearchKit Framework
|
||||
===========
|
||||
|
||||
    [](https://github.com/ResearchKit/ResearchKit#license) 
|
||||
|
||||
The *ResearchKit™ framework* is an open source software framework that makes it easy to create apps
|
||||
for medical research or for other research projects.
|
||||
|
||||
# Table of Contents
|
||||
|
||||
* [Requirements](#requirements)
|
||||
* [Documentation](#documentation)
|
||||
* [Getting Started](#gettingstarted)
|
||||
* Documentation:
|
||||
* [Programming Guide](http://researchkit.org/docs/docs/Overview/GuideOverview.html)
|
||||
* [Framework Reference](http://researchkit.org/docs/index.html)
|
||||
* [Best Practices](../../wiki/best-practices)
|
||||
* [Contributing to ResearchKit](CONTRIBUTING.md)
|
||||
* [Website](http://researchkit.org) and [Blog](http://researchkit.org/blog.html)
|
||||
* [ResearchKit BSD License](#license)
|
||||
* [Installing](#installation)
|
||||
* [ORKCatalog App](#orkcatalog-app)
|
||||
* [Surveys](#surveys)
|
||||
* [Consent](#consent)
|
||||
* [Active Tasks](#active-tasks)
|
||||
* [Getting Help](#getting-help)
|
||||
* [License](#license)
|
||||
|
||||
Getting More Information
|
||||
========================
|
||||
# Requirements <a name="requirements"></a>
|
||||
|
||||
* Join the [*ResearchKit* Forum](https://forums.developer.apple.com/community/researchkit) for discussing uses of the *ResearchKit framework and* related projects.
|
||||
The *ResearchKit framework* codebase supports iOS and requires Xcode 12.0 or newer. The *ResearchKit framework* has a Base SDK version of 13.0.
|
||||
|
||||
Use Cases
|
||||
===========
|
||||
# Documentation <a name="documentation"></a>
|
||||
|
||||
A task in the *ResearchKit framework* contains a set of steps to present to a user. Everything,
|
||||
whether it’s a *survey*, the *consent process*, or *active tasks*, is represented as a task that can
|
||||
be presented with a task view controller.
|
||||
<img width="1000" alt="ebedded-framework" src="https://github.com/ResearchKit/ResearchKit/assets/29615893/19d6edd3-3d95-4416-9ac4-24ccb35e09c2">
|
||||
|
||||
Surveys
|
||||
-------
|
||||
View the *ResearchKit framework* documentation by setting ResearchKit as your target in Xcode and selecting 'Build Documentation' in the Product menu dropdown.
|
||||
|
||||
|
||||
# Getting Started <a name="gettingstarted"></a>
|
||||
|
||||
* [Website](https://www.researchandcare.org)
|
||||
* [WWDC: ResearchKit and CareKit Reimagined](https://developer.apple.com/videos/play/wwdc2019/217/)
|
||||
|
||||
|
||||
### Install as an embedded framework <a name="installation"></a>
|
||||
|
||||
Download the project source code and drag in ResearchKit.xcodeproj. Then, embed *ResearchKit* framework in your app by adding it to the "Frameworks, Libraries, and Embedded Content" section for your target as shown in the figure below.
|
||||
|
||||
<img width="1000" alt="ebedded-framework" src="https://github.com/ResearchKit/ResearchKit/assets/29615893/7479f313-ecc7-4d94-8c64-c58ae7362a4d">
|
||||
|
||||
### ORKCatalog App <a name="orkcatalog-app"></a>
|
||||
|
||||
The included catalog app demonstrates the different modules that are available in *ResearchKit*. Find the
|
||||
project in ResearchKit's [`samples`](samples) directory.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
# Surveys <a name="surveys"></a>
|
||||
|
||||
The *ResearchKit framework* provides a pre-built user interface for surveys, which can be presented
|
||||
modally on an *iPhone*, *iPod Touch*, or *iPad*. See
|
||||
*[Creating Surveys](http://researchkit.org/docs/docs/Survey/CreatingSurveys.html)* for more
|
||||
information.
|
||||
modally on an *iPhone* or *iPad*. The example below shows the process to present a height question for a participant to answer.
|
||||
|
||||
```swift
|
||||
import ResearchKit
|
||||
import ResearchKitUI
|
||||
|
||||
let sectionHeaderFormItem = ORKFormItem(sectionTitle: "Your question here.")
|
||||
|
||||
Consent
|
||||
----------------
|
||||
let heightQuestionFormItem = ORKFormItem(identifier: "heightQuestionFormItem1", text: nil, answerFormat: ORKAnswerFormat.heightAnswerFormat())
|
||||
heightQuestionFormItem.placeholder = "Tap here"
|
||||
|
||||
The *ResearchKit framework* provides visual consent templates that you can customize to explain the
|
||||
details of your research study and obtain a signature if needed.
|
||||
See *[Obtaining Consent](http://researchkit.org/docs/docs/InformedConsent/InformedConsent.html)* for
|
||||
more information.
|
||||
let formStep = ORKFormStep(identifier: "HeightQuestionIdentifier", title: "Height", text: "Local system")
|
||||
formStep.formItems = [sectionHeaderFormItem, heightQuestionFormItem]
|
||||
|
||||
return formStep
|
||||
```
|
||||
|
||||
Active Tasks
|
||||
------------
|
||||
The height question is presented in the figure below.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
# Consent <a name="consent"></a>
|
||||
|
||||
The *ResearchKit framework* provides classes that you can customize to explain the
|
||||
details of your research study and obtain a signature if needed. Use *ResearchKit's* provided classes to quickly welcome, and inform your participants of what the study entails.
|
||||
|
||||
```swift
|
||||
import ResearchKit
|
||||
import ResearchKitUI
|
||||
|
||||
// Welcome page.
|
||||
let welcomeStep = ORKInstructionStep(identifier: String(describing: Identifier.consentWelcomeInstructionStep))
|
||||
welcomeStep.iconImage = UIImage(systemName: "hand.wave")
|
||||
welcomeStep.title = "Welcome!"
|
||||
welcomeStep.detailText = "Thank you for joining our study. Tap Next to learn more before signing up."
|
||||
|
||||
// Before You Join page.
|
||||
let beforeYouJoinStep = ORKInstructionStep(identifier: String(describing: Identifier.informedConsentInstructionStep))
|
||||
beforeYouJoinStep.iconImage = UIImage(systemName: "doc.text.magnifyingglass")
|
||||
beforeYouJoinStep.title = "Before You Join"
|
||||
|
||||
let sharingHealthDataBodyItem = ORKBodyItem(text: "The study will ask you to share some of your Health data.",
|
||||
detailText: nil,
|
||||
image: UIImage(systemName: "heart.fill"),
|
||||
learnMoreItem: nil,
|
||||
bodyItemStyle: .image)
|
||||
|
||||
let completingTasksBodyItem = ORKBodyItem(text: "You will be asked to complete various tasks over the duration of the study.",
|
||||
detailText: nil,
|
||||
image: UIImage(systemName: "checkmark.circle.fill"),
|
||||
learnMoreItem: nil,
|
||||
bodyItemStyle: .image)
|
||||
|
||||
let signatureBodyItem = ORKBodyItem(text: "Before joining, we will ask you to sign an informed consent document.",
|
||||
detailText: nil,
|
||||
image: UIImage(systemName: "signature"),
|
||||
learnMoreItem: nil,
|
||||
bodyItemStyle: .image)
|
||||
|
||||
let secureDataBodyItem = ORKBodyItem(text: "Your data is kept private and secure.",
|
||||
detailText: nil,
|
||||
image: UIImage(systemName: "lock.fill"),
|
||||
learnMoreItem: nil,
|
||||
bodyItemStyle: .image)
|
||||
|
||||
beforeYouJoinStep.bodyItems = [
|
||||
sharingHealthDataBodyItem,
|
||||
completingTasksBodyItem,
|
||||
signatureBodyItem,
|
||||
secureDataBodyItem
|
||||
]
|
||||
```
|
||||
The consent steps are presented in the figure below.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
Vist the `Obtaining Consent`article in ResearchKit's Documentation for
|
||||
more examples that include signature collection and PDF file storage.
|
||||
|
||||
# Active Tasks <a name="active-tasks"></a>
|
||||
|
||||
Some studies may need data beyond survey questions or the passive data collection capabilities
|
||||
available through use of the *HealthKit* and *CoreMotion* APIs if you are programming for *iOS*.
|
||||
*ResearchKit*'s active tasks invite users to perform activities under semi-controlled conditions,
|
||||
while *iPhone* sensors actively collect data. See
|
||||
*[Active Tasks](http://researchkit.org/docs/docs/ActiveTasks/ActiveTasks.html)* for more
|
||||
information.
|
||||
while *iPhone* sensors actively collect data.
|
||||
ResearchKit active tasks are not diagnostic tools nor medical devices of any kind and output from those active tasks may not be used for diagnosis. Developers and researchers are responsible for complying with all applicable laws and regulations with respect to further development and use of the active tasks.
|
||||
|
||||
Charts
|
||||
------------
|
||||
*ResearchKit* includes a *Charts module*. It features three chart types: a *pie chart* (`ORKPieChartView`), a *line graph chart* (`ORKLineGraphChartView`), and a *discrete graph chart* (`ORKDiscreteGraphChartView`).
|
||||
|
||||
The views in the *Charts module* can be used independently of the rest of *ResearchKit*. They don't automatically connect with any other part of *ResearchKit*: the developer has to supply the data to be displayed through the views' `dataSources`, which allows for maximum flexibility.
|
||||
|
||||
|
||||
Getting Started<a name="gettingstarted"></a>
|
||||
===============
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
The primary *ResearchKit framework* codebase supports *iOS* and requires *Xcode 8.0* or newer. The
|
||||
*ResearchKit framework* has a *Base SDK* version of *8.0*, meaning that apps using the *ResearchKit
|
||||
framework* can run on devices with *iOS 8.0* or newer.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The latest stable version of *ResearchKit framework* can be cloned with
|
||||
|
||||
```
|
||||
git clone -b stable https://github.com/ResearchKit/ResearchKit.git
|
||||
```
|
||||
|
||||
Or, for the latest changes, use the `master` branch:
|
||||
|
||||
```
|
||||
git clone https://github.com/ResearchKit/ResearchKit.git
|
||||
```
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Build the *ResearchKit framework* by opening `ResearchKit.xcodeproj` and running the `ResearchKit`
|
||||
framework target. Optionally, run the unit tests too.
|
||||
|
||||
|
||||
Adding the ResearchKit framework to your App
|
||||
------------------------------
|
||||
|
||||
This walk-through shows how to embed the *ResearchKit framework* in your app as a dynamic framework,
|
||||
and present a simple task view controller.
|
||||
|
||||
### 1. Add the ResearchKit framework to Your Project
|
||||
|
||||
To get started, drag `ResearchKit.xcodeproj` from your checkout into your *iOS* app project
|
||||
in *Xcode*:
|
||||
|
||||
<center>
|
||||
<figure>
|
||||
<img src="../../wiki/AddingResearchKitXcode.png" alt="Adding the ResearchKit framework to your
|
||||
project" align="middle"/>
|
||||
</figure>
|
||||
</center>
|
||||
|
||||
Then, embed the *ResearchKit framework* as a dynamic framework in your app, by adding it to the
|
||||
*Embedded Binaries* section of the *General* pane for your target as shown in the figure below.
|
||||
|
||||
<center>
|
||||
<figure>
|
||||
<img src="../../wiki/AddedBinaries.png" width="100%" alt="Adding the ResearchKit framework to
|
||||
Embedded Binaries" align="middle"/>
|
||||
<figcaption><center>Adding the ResearchKit framework to Embedded Binaries</center></figcaption>
|
||||
</figure>
|
||||
</center>
|
||||
|
||||
Note: You can also import *ResearchKit* into your project using a
|
||||
[dependency manager](./docs-standalone/dependency-management.md) such as *CocoaPods* or *Carthage*.
|
||||
|
||||
### 2. Create a Step
|
||||
|
||||
In this walk-through, we will use the *ResearchKit framework* to modally present a simple
|
||||
single-step task showing a single instruction.
|
||||
|
||||
Create a step for your task by adding some code, perhaps in `viewDidAppear:` of an existing view
|
||||
controller. To keep things simple, we'll use an instruction step (`ORKInstructionStep`) and name
|
||||
the step `myStep`.
|
||||
|
||||
*Objective-C*
|
||||
|
||||
```objc
|
||||
ORKInstructionStep *myStep =
|
||||
[[ORKInstructionStep alloc] initWithIdentifier:@"intro"];
|
||||
myStep.title = @"Welcome to ResearchKit";
|
||||
```
|
||||
|
||||
*Swift*
|
||||
Use predefined tasks provided by *ResearchKit* to guide your participants through specific actions.
|
||||
|
||||
```swift
|
||||
let myStep = ORKInstructionStep(identifier: "intro")
|
||||
myStep.title = "Welcome to ResearchKit"
|
||||
```
|
||||
import ResearchKit
|
||||
import ResearchKitUI
|
||||
import ResearchKitActiveTask
|
||||
|
||||
### 3. Create a Task
|
||||
|
||||
Use the ordered task class (`ORKOrderedTask`) to create a task that contains `myStep`. An ordered
|
||||
task is just a task where the order and selection of later steps does not depend on the results of
|
||||
earlier ones. Name your task `task` and initialize it with `myStep`.
|
||||
|
||||
*Objective-C*
|
||||
|
||||
```objc
|
||||
ORKOrderedTask *task =
|
||||
[[ORKOrderedTask alloc] initWithIdentifier:@"task" steps:@[myStep]];
|
||||
```
|
||||
|
||||
*Swift*
|
||||
|
||||
```swift
|
||||
let task = ORKOrderedTask(identifier: "task", steps: [myStep])
|
||||
```
|
||||
|
||||
### 4. Present the Task
|
||||
|
||||
Create a task view controller (`ORKTaskViewController`) and initialize it with your `task`. A task
|
||||
view controller manages a task and collects the results of each step. In this case, your task view
|
||||
controller simply displays your instruction step.
|
||||
|
||||
*Objective-C*
|
||||
|
||||
```objc
|
||||
ORKTaskViewController *taskViewController =
|
||||
[[ORKTaskViewController alloc] initWithTask:task taskRunUUID:nil];
|
||||
taskViewController.delegate = self;
|
||||
[self presentViewController:taskViewController animated:YES completion:nil];
|
||||
```
|
||||
|
||||
*Swift*
|
||||
|
||||
```swift
|
||||
let taskViewController = ORKTaskViewController(task: task, taskRunUUID: nil)
|
||||
let orderedTask = ORKOrderedTask.dBHLToneAudiometryTask(withIdentifier: "dBHLToneAudiometryTaskIdentifier",
|
||||
intendedUseDescription: nil, options: [])
|
||||
|
||||
let taskViewController = ORKTaskViewController(task: orderedTask, taskRun: nil)
|
||||
taskViewController.delegate = self
|
||||
presentViewController(taskViewController, animated: true, completion: nil)
|
||||
|
||||
present(taskViewController, animated: true)
|
||||
```
|
||||
The dBHL Tone Audiometry task is presented in the figure below.
|
||||
|
||||
The above snippet assumes that your class implements the `ORKTaskViewControllerDelegate` protocol.
|
||||
This has just one required method, which you must implement in order to handle the completion of
|
||||
the task:
|
||||
| | |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
*Objective-C*
|
||||
# Getting Help <a name="getting-help"></a>
|
||||
|
||||
```objc
|
||||
- (void)taskViewController:(ORKTaskViewController *)taskViewController
|
||||
didFinishWithReason:(ORKTaskViewControllerFinishReason)reason
|
||||
error:(NSError *)error {
|
||||
GitHub is our primary forum for ResearchKit. Feel free to open up issues about questions, problems, or ideas.
|
||||
|
||||
ORKTaskResult *taskResult = [taskViewController result];
|
||||
// You could do something with the result here.
|
||||
# License <a name="license"></a>
|
||||
|
||||
// Then, dismiss the task view controller.
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
```
|
||||
|
||||
*Swift*
|
||||
|
||||
```swift
|
||||
func taskViewController(_ taskViewController: ORKTaskViewController,
|
||||
didFinishWith reason: ORKTaskViewControllerFinishReason,
|
||||
error: Error?) {
|
||||
let taskResult = taskViewController.result
|
||||
// You could do something with the result here.
|
||||
|
||||
// Then, dismiss the task view controller.
|
||||
dismiss(true, completion: nil)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
If you now run your app, you should see your first *ResearchKit framework* instruction step:
|
||||
|
||||
<center>
|
||||
<figure>
|
||||
<img src="../../wiki/HelloWorld.png" width="50%" alt="HelloWorld example screenshot" align="middle"/>
|
||||
</figure>
|
||||
</center>
|
||||
|
||||
|
||||
|
||||
What else can the ResearchKit framework do?
|
||||
-----------------------------
|
||||
|
||||
The *ResearchKit* [`ORKCatalog`](samples/ORKCatalog) sample app is a good place to start. Find the
|
||||
project in ResearchKit's [`samples`](samples) directory. This project includes a list of all the
|
||||
types of steps supported by the *ResearchKit framework* in the first tab, and displays a browser for the
|
||||
results of the last completed task in the second tab. The third tab shows some examples from the *Charts module*.
|
||||
|
||||
|
||||
|
||||
License<a name="license"></a>
|
||||
=======
|
||||
|
||||
The source in the *ResearchKit* repository is made available under the following license unless
|
||||
another license is explicitly identified:
|
||||
|
||||
```
|
||||
Copyright (c) 2015 - 2017, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
This project is made available under the terms of a BSD license. See the [LICENSE](LICENSE) file.
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
# ResearchKit Release Notes
|
||||
|
||||
## ResearchKit 1.5 Release Notes
|
||||
|
||||
*ResearchKit 1.5* supports *iOS* and requires *Xcode 8.0* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stabiltiy and performance improvements, *ResearchKit 1.5* includes the following new features and enhancements.
|
||||
|
||||
- **New Active Tasks**
|
||||
|
||||
- **Stroop Test**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *Stroop Test* shows the participant different combinations of text and tint colors on the screen.
|
||||
|
||||
Users must ignore the text and instead select the button that reflects the first letter of the tint color.
|
||||
|
||||
- **Trail Making Test**
|
||||
|
||||
*Contributed by Faraz Hussain.*
|
||||
|
||||
The *Trail Making Test* instructs participants to connect a series of labelled circles and the time to complete the test is recorded.
|
||||
|
||||
- **Range of Motion Test**
|
||||
|
||||
*Contributed by Daren Levy, Dr. Raj Karia, John Guydo.*
|
||||
|
||||
Participants are instructed to follow a series of steps while accelerometer and gyroscope data is captured to measure flexed and extended positions for both the shoulder and knee.
|
||||
|
||||
- **Touch Anywhere Active Task**
|
||||
|
||||
*Contributed by Daren Levy, Dr. Raj Karia, John Guydo*
|
||||
|
||||
Allows the user to get their device in the proper position and then tap the screen to indicate they are ready to begin the next step.
|
||||
|
||||
- **New Steps**
|
||||
|
||||
- **Video Instruction Step**
|
||||
|
||||
*Contributed by [Oliver Schäfer](https://github.com/oliverschaefer).*
|
||||
|
||||
The *Video Instruction Step* provides a step to be used to display a video.
|
||||
|
||||
This step can be used to display videos to users from either a local or remote source.
|
||||
|
||||
- **Other Improvements**
|
||||
|
||||
- **Tone Audiometry Test**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
Updated to include both a left and right button.
|
||||
|
||||
- **Digital Object Identifier**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
Assigns a Digital Object Identifier to the ResearchKit repository on GitHub to use when referencing the framework.
|
||||
|
||||
|
||||
## ResearchKit 1.4 Release Notes
|
||||
|
||||
*ResearchKit 1.4* supports *iOS* and requires *Xcode 8.0* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stabiltiy and performance improvements, *ResearchKit 1.4* includes the following new features and enhancements.
|
||||
|
||||
- **New Active Task**
|
||||
|
||||
- **Hand Tremor Task**
|
||||
|
||||
*Contributed by [Shannon Young](https://github.com/syoung-smallwisdom).*
|
||||
|
||||
The *Hand Tremor Task* asks the participant to hold the device with their most affected hand in various positions while accelerometer and motion data is captured.
|
||||
|
||||
- **Walk Back and Forth Task**
|
||||
|
||||
*Contributed by [Shannon Young](https://github.com/syoung-smallwisdom).*
|
||||
|
||||
The *Walk Back and Forth Task* addresses the concern of researchers/participants who have difficulty locating an unobstructed path for 20 steps.
|
||||
|
||||
Instructs users to walk and turn in a full circle, allowing the tests to be conducted in a smaller space.
|
||||
|
||||
- **New Steps**
|
||||
|
||||
- **Video Capture Step**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *Video Capture Step* provides a step to be used to record video.
|
||||
|
||||
The step can be used as part of a survey to capture video respones as well.
|
||||
|
||||
- **Review Step**
|
||||
|
||||
*Contributed by [Oliver Schäfer](https://github.com/oliverschaefer).*
|
||||
|
||||
The *Review Step* allows a participant to review and modify their answers to a survey.
|
||||
|
||||
The step can be used in the middle of a survey, at the end of a survey, or a standalone module.
|
||||
|
||||
- **Signature Step**
|
||||
|
||||
*Contributed by [Oliver Schäfer](https://github.com/oliverschaefer).*
|
||||
|
||||
The *Signature Step* provides an interface for a participant to sign their name.
|
||||
|
||||
The step can be used for handwriting detection or simply to sign a document.
|
||||
|
||||
- **Table Step**
|
||||
|
||||
*Contributed by [Shannon Young](https://github.com/syoung-smallwisdom).*
|
||||
|
||||
The *Table Step* provides a way to neatly display data in a table.
|
||||
|
||||
- **Other Improvements**
|
||||
|
||||
- **Data Collection Module**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *Data Collection Module* makes it even easier to aggregate data from HealthKit and device sensors.
|
||||
|
||||
- **Tapping Test**
|
||||
|
||||
*Contributed by [Michał Zaborowski](https://github.com/m1entus).*
|
||||
|
||||
The *Tapping Test* is updated to include tap duration as part of the result.
|
||||
|
||||
|
||||
## ResearchKit 1.3 Release Notes
|
||||
|
||||
*ResearchKit 1.3* supports *iOS* and requires *Xcode 7.2* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stability and performance improvements, *ResearchKit 1.3* includes the following new features and enhancements.
|
||||
|
||||
- **New Active Task**
|
||||
|
||||
- **9-Hole Peg Test**
|
||||
|
||||
*Contributed by [Julien Therier](https://github.com/julientherier).*
|
||||
|
||||
The *9-Hole Peg Test task* is used to test upper extremity functionality.
|
||||
|
||||
The test involves putting a variable number of pegs in a hole and subsequently removing them.
|
||||
|
||||
The test is documented in the scientific literature to measure the *[MSFC score in Multiple Sclerosis](http://www.nationalmssociety.org/For-Professionals/Researchers/Resources-for-Researchers/Clinical-Study-Measures/9-Hole-Peg-Test-(9-HPT))* or *[Parkinson's Disease](http://www.ncbi.nlm.nih.gov/pubmed/22020457)*.
|
||||
|
||||
- **Sample App**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *Sample App* (`ORKSample` project on *ResearchKit*'s workspace) serves as a template application that combines different modules from the *ResearchKit framework*.
|
||||
|
||||
- **Account Module**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *Account Module* provides steps to facilitate account creation and login.
|
||||
|
||||
The module includes the following steps:
|
||||
|
||||
1. *Registration*, used to allow the participant to create a new account.
|
||||
2. *Verification*, used to confirm if the participant has verified the provided email address.
|
||||
3. *Login*, used to allow registered users to login.
|
||||
|
||||
- **Passcode with Touch ID**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *Passcode with Touch ID module* provides the ability to secure any *ResearchKit* application with a numeric passcode.
|
||||
|
||||
This module includes a *Keychain Wrapper* that stores the passcode on the device, as well as the option to use *Touch ID* on compatible devices. The passcode module supports 4-digit and 6-digit numeric codes.
|
||||
|
||||
The passcode module provides the following components:
|
||||
|
||||
1. *Passcode creation step*, which can be used as part of onboarding to create a passcode and store it in the keychain.
|
||||
2. *Passcode authentication view controller*, which can be modally presented when appropriate.
|
||||
3. *Passcode modification view controller*, which allows the participant to change their passcode.
|
||||
|
||||
- **Other Improvements**
|
||||
|
||||
- **Optional Form Items**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
Adds the `optional` property to `ORKFormItem`.
|
||||
|
||||
The *Continue/Done* button of form steps is enabled when all of the following conditions are met:
|
||||
|
||||
- At least one form item has an answer.
|
||||
- All the non-optional form items have answers.
|
||||
- All answered form items have valid answers.
|
||||
|
||||
- **Location Question**
|
||||
|
||||
*Contributed by [Quintiles](https://github.com/QuintilesRK).*
|
||||
|
||||
A *Location Question* can be used to request details about the participant's current location or about a specific address.
|
||||
|
||||
The question uses *MapKit* to provide a visual representation for the specified address.
|
||||
|
||||
- **Wait Step**
|
||||
|
||||
*Contributed by [Quintiles](https://github.com/QuintilesRK).*
|
||||
|
||||
The *Wait Step* provides a step to be used in-between steps when additional data processing is required.
|
||||
|
||||
The step supports both indeterminate and determinate progress views, as well as the ability to show text status updates.
|
||||
|
||||
- **Validated Text Answer Format**
|
||||
|
||||
*Contributed by [Quintiles](https://github.com/QuintilesRK).*
|
||||
|
||||
The *Validated Text Answer Format* enhances the existing *Text Answer Format* by providing input validation using a regular expression.
|
||||
|
||||
A valid *NSRegularExpression* object and an *error message* string are required to properly use this answer format.
|
||||
|
||||
|
||||
## ResearchKit 1.2 Release Notes
|
||||
|
||||
*ResearchKit 1.2* supports *iOS* and requires *Xcode 7.0* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stability and performance improvements, *ResearchKit 1.2* includes the following new features and enhancements.
|
||||
|
||||
- **New Active Tasks**
|
||||
|
||||
- **Tower of Hanoi Task**
|
||||
|
||||
*Contributed by [coxy1989](https://github.com/coxy1989).*
|
||||
|
||||
The *[Tower of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi#Applications) task* is frequently used in psychological research on problem solving.
|
||||
|
||||
It is a mathematical puzzle consisting of three rods and a number of disks of different sizes which can slide onto any rod. The puzzle starts with the disks in a stack in ascending order of size on one rod (the smallest at the top).
|
||||
|
||||
The objective of the puzzle is to move the entire stack to another rod, obeying the following rules:
|
||||
|
||||
1. Only one disk can be moved at a time.
|
||||
2. Each move consists of taking the upper disk from one of the stacks and placing it on top of another stack.
|
||||
3. No disk may be placed on top of a smaller disk.
|
||||
|
||||
- **Paced Serial Addition Test Task**
|
||||
|
||||
*Contributed by [Julien Therier](https://github.com/julientherier).*
|
||||
|
||||
The *Paced Serial Addition Test task* provides adaptations of both the *Paced Auditory Serial Addition Test (PASAT)* and the *Paced Visual Serial Addition Test (PVSAT)*.
|
||||
|
||||
The *[PASAT](https://en.wikipedia.org/wiki/Paced_Auditory_Serial_Addition_Test)* is a neuropsychological test used to assess capacity and rate of information processing and sustained and divided attention.
|
||||
|
||||
Both tests are documented in the scientific literature ([Fos et al., 2000](http://www.ncbi.nlm.nih.gov/pubmed/11125707); [Nagels et al., 2005](http://www.ncbi.nlm.nih.gov/pubmed/15823678)) as a measure of the [*Multiple Sclerosis Functional Score*](http://www.nationalmssociety.org/For-Professionals/Researchers/Resources-for-Researchers/Clinical-Study-Measures/Multiple-Sclerosis-Functional-Composite-%28MSFC%29).
|
||||
|
||||
This task generates a series of single digits (for example, 60 of them), at the specific frequency (for example, one new digit every 2 or 3 seconds). The user must add the newly presented digit to the one prior to it.
|
||||
|
||||
- **Timed Walk Task**
|
||||
|
||||
*Contributed by [Julien Therier](https://github.com/julientherier).*
|
||||
|
||||
The *Timed Walk task* measures gait speed and is an adaptation of the [*Timed 25-Foot Walk*](http://www.nationalmssociety.org/For-Professionals/Researchers/Resources-for-Researchers/Clinical-Study-Measures/Timed-25-Foot-Walk-%28T25-FW%29) in the context of *multiple sclerosis*.
|
||||
|
||||
Gait speed has been demonstrated to be a useful and reliable functional measure of walking ability. When administering the *Timed Walk Task*, patients are allowed to use assistive devices (canes, crutches, walkers).
|
||||
|
||||
- **Charts Module**
|
||||
|
||||
*Contributed by [coxy1989](https://github.com/coxy1989) and [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
A *Charts module* has been implemented. It features three chart types: a *pie chart* (`ORKPieChartView`), a *line graph chart* (`ORKLineGraphChartView`), and a *discrete graph chart* (`ORKDiscreteGraphChartView`).
|
||||
|
||||
The views in the *Charts module* can be used independently of the rest of *ResearchKit*. It doesn't automatically connect with any other *ResearchKit* module: the developer has to supply the data to be displayed through the views' `dataSources`, which allows for maximum flexibility.
|
||||
|
||||
- **Other Improvements**
|
||||
|
||||
- **Scale Answer Format**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
*Discrete scales* now support *text choice* labels, and all *scales* support images in place of the minimum and maximum range labels.
|
||||
|
||||
- **Result Predicates**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
The predicate-building methods in `ORKResultPredicate` now use the new `ORKResultSelector` class for unequivocally identifying a *question step result* or a *form item result*.
|
||||
|
||||
This eliminates ambiguity when matching results with the same inner scope identifier. For example, a *form item result* can have the same identifier as a *question step result* or as another *form item result* in a different *form step*, and you can now match them separately.
|
||||
|
||||
|
||||
## ResearchKit 1.1 Release Notes
|
||||
|
||||
*ResearchKit 1.1* supports *iOS* and requires *Xcode 6.3* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stability and performance improvements, *ResearchKit 1.1* includes the following new features and enhancements.
|
||||
|
||||
- **Navigable Ordered Task**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
A new type of *conditional ordered task* (`ORKNavigableOrderedTask`) has been implemented.
|
||||
|
||||
The developer can use the `ORKStepNavigationRule` subclasses to dynamically navigate between the task steps:
|
||||
- `ORKPredicateStepNavigationRule` allows to make conditional jumps by matching previous results (either those of the ongoing task, or those of any previously stored task result tree). You typically use the class methods in the `ORKResultPredicate` class to match answers in the most commonly used result types.
|
||||
- `ORKDirectStepNavigationRule` provides support for unconditional jumps.
|
||||
|
||||
- **New Active Tasks**
|
||||
- **Reaction Time Task**
|
||||
|
||||
*Contributed by [coxy1989](https://github.com/coxy1989).*
|
||||
|
||||
The *Reaction Time Task* is an adaptation of the [*Simple Reaction Time test (SRT)*](http://www.cambridgecognition.com/tests/simple-reaction-time-srt). *SRT* measures reaction time through delivery of a known stimulus to a known location to elicit a known response.
|
||||
|
||||
This test is deployed in a range of research questions across fields including medicine, sports science and psychology.
|
||||
|
||||
Although it classically involves pressing the space bar or clicking a mouse in response to an event on screen, the *ResearchKit* implementation relies on the study participant shaking the device when she sees a blue circle on the screen, which we think is more correlatable to a true stimulus reaction test.
|
||||
|
||||
- **Tone Audiometry Task**
|
||||
|
||||
*Contributed by [Vincent Tourraine](https://github.com/vtourraine).*
|
||||
|
||||
The *Tone Audiometry Task* is an adaptation of the [*Pure Tone Audiometry test (PTA)*](https://en.wikipedia.org/wiki/Pure_tone_audiometry). *PTA* is a key hearing test used to identify hearing threshold levels of an individual, enabling determination of the degree, type and configuration of a hearing loss.
|
||||
|
||||
The *ResearchKit* implementation generates a series of pure sinusoid sounds, with different frequencies and on different channels (left or right). The test starts at the minimum volume and is gradually increased until the participant perceives it and taps a button. At that time, the current sound amplitude, frequency and channel are recorded.
|
||||
|
||||
- **Scale Answer Format Enhancements**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez) and [Bruce Duncan](https://github.com/brucehappy).*
|
||||
|
||||
Support for discrete and continuous *vertical scales* has been added. Some questions, like mood measurement or symptom severity measurement queries may be more naturally presented using a *vertical scale*.
|
||||
|
||||
The *Scale Answer Format* has also been improved by making it usable within forms.
|
||||
|
||||
- **Image Capture Step**
|
||||
|
||||
*Contributed by [Bruce Duncan](https://github.com/brucehappy).*
|
||||
|
||||
An *Image Capture Step* has been added. The researcher can ask the participant to take pictures of relevant body parts. The researcher can provide a body part image template to facilitate the scale and orientation of the taken pictures.
|
||||
|
||||
- **iPad Support**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez) and [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
*iPad support* for all orientations has been implemented.
|
||||
|
||||
- **iPhone Landscape Support**
|
||||
|
||||
*Contributed by [Apple Inc.](https://github.com/researchkit) and [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
*iPhone landscape orientation support* has been implemented.
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:ResearchKit.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Testing/ORKTest/ORKTest.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:samples/ORKCatalog/ORKCatalog.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:samples/ORKSample/ORKSample.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,15 +0,0 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ResearchKit'
|
||||
s.version = '1.5.2'
|
||||
s.summary = 'ResearchKit is an open source software framework that makes it easy to create apps for medical research or for other research projects.'
|
||||
s.homepage = 'https://www.github.com/ResearchKit/ResearchKit'
|
||||
s.documentation_url = 'http://researchkit.github.io/docs/'
|
||||
s.license = { :type => 'BSD', :file => 'LICENSE' }
|
||||
s.author = { 'researchkit.org' => 'http://researchkit.org' }
|
||||
s.source = { :git => 'https://github.com/ResearchKit/ResearchKit.git', :tag => s.version.to_s }
|
||||
s.public_header_files = `./scripts/find_headers.rb --public --private`.split("\n")
|
||||
s.source_files = 'ResearchKit/**/*.{h,m,swift}'
|
||||
s.resources = 'ResearchKit/**/*.{fsh,vsh}', 'ResearchKit/Animations/**/*.m4v', 'ResearchKit/Artwork.xcassets', 'ResearchKit/Localized/*.lproj'
|
||||
s.platform = :ios, '8.2'
|
||||
s.requires_arc = true
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:ResearchKit.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,99 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B183A4731A8535D100C76870"
|
||||
BuildableName = "ResearchKit.framework"
|
||||
BlueprintName = "ResearchKit"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "86CC8E991AC09332001CCD89"
|
||||
BuildableName = "ResearchKitTests.xctest"
|
||||
BlueprintName = "ResearchKitTests"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B183A4731A8535D100C76870"
|
||||
BuildableName = "ResearchKit.framework"
|
||||
BlueprintName = "ResearchKit"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B183A4731A8535D100C76870"
|
||||
BuildableName = "ResearchKit.framework"
|
||||
BlueprintName = "ResearchKit"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B183A4731A8535D100C76870"
|
||||
BuildableName = "ResearchKit.framework"
|
||||
BlueprintName = "ResearchKit"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B18FF3A41A9FE25700C0C3B0"
|
||||
BuildableName = "docs"
|
||||
BlueprintName = "docs"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B18FF3A41A9FE25700C0C3B0"
|
||||
BuildableName = "docs"
|
||||
BlueprintName = "docs"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B18FF3A41A9FE25700C0C3B0"
|
||||
BuildableName = "docs"
|
||||
BlueprintName = "docs"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
// Shared header for accessibility functionality.
|
||||
#import "ORKAccessibilityFunctions.h"
|
||||
#import "ORKLineGraphAccessibilityElement.h"
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKDefines.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKScaleSlider;
|
||||
|
||||
// Used to properly format values from the ORKScaleSlider.
|
||||
ORK_EXTERN NSString *ORKAccessibilityFormatScaleSliderValue(CGFloat value, ORKScaleSlider *slider);
|
||||
ORK_EXTERN NSString *ORKAccessibilityFormatContinuousScaleSliderValue(CGFloat value, ORKScaleSlider *slider);
|
||||
|
||||
// Performs a block on the main thread after a delay. If Voice Over is not running, the block is performed immediately.
|
||||
ORK_EXTERN void ORKAccessibilityPerformBlockAfterDelay(NSTimeInterval delay, void(^block)(void));
|
||||
|
||||
// Convenience for posting an accessibility notification after a delay.
|
||||
ORK_INLINE void ORKAccessibilityPostNotificationAfterDelay(UIAccessibilityNotifications notification, _Nullable id argument, NSTimeInterval delay) {
|
||||
ORKAccessibilityPerformBlockAfterDelay(delay, ^{
|
||||
UIAccessibilityPostNotification(notification, argument);
|
||||
});
|
||||
}
|
||||
|
||||
// Creates a string suitable for Voice Over by joining the variables with ", " and avoiding nil and empty strings.
|
||||
#define ORKAccessibilityStringForVariables(...) _ORKAccessibilityStringForVariables(ORK_NARG(__VA_ARGS__), ##__VA_ARGS__)
|
||||
ORK_EXTERN NSString *_ORKAccessibilityStringForVariables(NSInteger numParameters, NSString *baseString, ...);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
|
||||
#import "ORKScaleSlider.h"
|
||||
#import "ORKScaleSliderView.h"
|
||||
|
||||
#import "ORKAnswerFormat_Internal.h"
|
||||
|
||||
#import "ORKAccessibilityFunctions.h"
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
|
||||
|
||||
NSString *ORKAccessibilityFormatScaleSliderValue(CGFloat value, ORKScaleSlider *slider) {
|
||||
ORKScaleSliderView *sliderView = (ORKScaleSliderView *)[slider ork_superviewOfType:[ORKScaleSliderView class]];
|
||||
if (!slider || !sliderView) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSNumber *normalizedValue = [sliderView.formatProvider normalizedValueForNumber:@(value)];
|
||||
return [sliderView.formatProvider localizedStringForNumber:normalizedValue];
|
||||
}
|
||||
|
||||
NSString *ORKAccessibilityFormatContinuousScaleSliderValue(CGFloat value, ORKScaleSlider *slider) {
|
||||
ORKScaleSliderView *sliderView = (ORKScaleSliderView *)[slider ork_superviewOfType:[ORKScaleSliderView class]];
|
||||
if (!slider || !sliderView) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [sliderView.formatProvider localizedStringForNumber:@(value)];
|
||||
}
|
||||
|
||||
void ORKAccessibilityPerformBlockAfterDelay(NSTimeInterval delay, void(^block)(void)) {
|
||||
if (block == nil) {
|
||||
return;
|
||||
}
|
||||
if (!UIAccessibilityIsVoiceOverRunning()) {
|
||||
delay = 0;
|
||||
}
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
block();
|
||||
});
|
||||
}
|
||||
|
||||
NSString *_ORKAccessibilityStringForVariables(NSInteger numParameters, NSString *baseString, ...) {
|
||||
NSMutableArray *variables = [[NSMutableArray alloc] init];
|
||||
|
||||
NSInteger paramIndex = 0;
|
||||
|
||||
va_list args;
|
||||
va_start(args, baseString);
|
||||
for (__unsafe_unretained NSString *variable = baseString;
|
||||
paramIndex < numParameters;
|
||||
variable = va_arg(args, __unsafe_unretained NSString *), paramIndex++) {
|
||||
|
||||
if ([variable isKindOfClass:[NSString class]] && variable.length > 0) {
|
||||
[variables addObject:variable];
|
||||
}
|
||||
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
return [variables componentsJoinedByString:@", "];
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKLineGraphAccessibilityElement : UIAccessibilityElement
|
||||
|
||||
- (nonnull instancetype)initWithAccessibilityContainer:(nonnull UIView *)container index:(NSInteger)index maxIndex:(NSInteger)maxIndex;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import "ORKLineGraphAccessibilityElement.h"
|
||||
|
||||
|
||||
@interface ORKLineGraphAccessibilityElement()
|
||||
|
||||
@property (assign, nonatomic) NSInteger index;
|
||||
@property (assign, nonatomic) NSInteger maxIndex;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKLineGraphAccessibilityElement
|
||||
|
||||
- (nonnull instancetype)initWithAccessibilityContainer:(nonnull UIView *)container index:(NSInteger)index maxIndex:(NSInteger)maxIndex {
|
||||
self = [super initWithAccessibilityContainer:container];
|
||||
if (self) {
|
||||
self.index = index;
|
||||
self.maxIndex = maxIndex;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGRect)accessibilityFrame {
|
||||
if (self.maxIndex == 0) {
|
||||
return [super accessibilityFrame];
|
||||
}
|
||||
|
||||
CGRect containerFrame = [self.accessibilityContainer frame];
|
||||
CGFloat height = CGRectGetHeight(containerFrame);
|
||||
CGFloat width = CGRectGetWidth(containerFrame) / self.maxIndex;
|
||||
CGFloat x = self.index * width;
|
||||
|
||||
return UIAccessibilityConvertFrameToScreenCoordinates(CGRectMake(x, 0, width, height), self.accessibilityContainer);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIView (ORKAccessibility)
|
||||
|
||||
- (nullable UIView *)ork_superviewOfType:(nullable Class)aClass;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
|
||||
|
||||
@implementation UIView (ORKAccessibility)
|
||||
|
||||
- (UIView *)ork_superviewOfType:(Class)aClass {
|
||||
if (aClass == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
id superview = [self superview];
|
||||
if (superview == nil) {
|
||||
return nil;
|
||||
} else if ([superview isKindOfClass:aClass]) {
|
||||
return superview;
|
||||
}
|
||||
|
||||
return [superview ork_superviewOfType:aClass];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import CoreLocation;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CLLocation (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "CLLocation+ORKJSONDictionary.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation CLLocation (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
CLLocationCoordinate2D coord = self.coordinate;
|
||||
CLLocationDistance altitude = self.altitude;
|
||||
CLLocationAccuracy horizAccuracy = self.horizontalAccuracy;
|
||||
CLLocationAccuracy vertAccuracy = self.verticalAccuracy;
|
||||
CLLocationDirection course = self.course;
|
||||
CLLocationSpeed speed = self.speed;
|
||||
NSDate *timestamp = self.timestamp;
|
||||
CLFloor *floor = self.floor;
|
||||
|
||||
NSMutableDictionary *dictionary = [@{ @"timestamp": ORKStringFromDateISO8601(timestamp) } mutableCopy];
|
||||
|
||||
if (horizAccuracy >= 0) {
|
||||
dictionary[@"coordinate"] = @{ @"latitude": [NSDecimalNumber numberWithDouble:coord.latitude],
|
||||
@"longitude": [NSDecimalNumber numberWithDouble:coord.longitude]};
|
||||
dictionary[@"horizontalAccuracy"] = [NSDecimalNumber numberWithDouble:horizAccuracy];
|
||||
}
|
||||
if (vertAccuracy >= 0) {
|
||||
dictionary[@"altitude"] = [NSDecimalNumber numberWithDouble:altitude];
|
||||
dictionary[@"verticalAccuracy"] = [NSDecimalNumber numberWithDouble:vertAccuracy];
|
||||
}
|
||||
if (course >= 0) {
|
||||
dictionary[@"course"] = [NSDecimalNumber numberWithDouble:course];
|
||||
}
|
||||
if (speed >= 0) {
|
||||
dictionary[@"speed"] = [NSDecimalNumber numberWithDouble:speed];
|
||||
}
|
||||
if (floor) {
|
||||
dictionary[@"floor"] = @(floor.level);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CMAccelerometerData (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "CMAccelerometerData+ORKJSONDictionary.h"
|
||||
|
||||
|
||||
@implementation CMAccelerometerData (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
NSDictionary *dictionary = @{ @"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp],
|
||||
@"x": [NSDecimalNumber numberWithDouble:self.acceleration.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:self.acceleration.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:self.acceleration.z]
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CMDeviceMotion (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "CMDeviceMotion+ORKJSONDictionary.h"
|
||||
|
||||
|
||||
@implementation CMDeviceMotion (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
CMQuaternion attitude = self.attitude.quaternion;
|
||||
CMRotationRate rotationRate = self.rotationRate;
|
||||
CMAcceleration gravity = self.gravity;
|
||||
CMAcceleration userAccel = self.userAcceleration;
|
||||
CMCalibratedMagneticField field = self.magneticField;
|
||||
|
||||
NSDictionary *dictionary = @{@"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp],
|
||||
@"attitude": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:attitude.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:attitude.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:attitude.z],
|
||||
@"w": [NSDecimalNumber numberWithDouble:attitude.w]
|
||||
},
|
||||
@"rotationRate": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:rotationRate.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:rotationRate.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:rotationRate.z]
|
||||
},
|
||||
@"gravity": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:gravity.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:gravity.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:gravity.z]
|
||||
},
|
||||
@"userAcceleration": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:userAccel.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:userAccel.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:userAccel.z]
|
||||
},
|
||||
@"magneticField": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:field.field.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:field.field.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:field.field.z],
|
||||
@"accuracy": [NSDecimalNumber numberWithDouble:field.accuracy]
|
||||
}
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CMMotionActivity (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "CMMotionActivity+ORKJSONDictionary.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
static NSString *const ActivityUnknown = @"unknown";
|
||||
static NSString *const ActivityStationary = @"stationary";
|
||||
static NSString *const ActivityWalking = @"walking";
|
||||
static NSString *const ActivityRunning = @"running";
|
||||
static NSString *const ActivityAutomotive = @"automotive";
|
||||
static NSString *const StartDateKey = @"startDate";
|
||||
static NSString *const EndDateKey = @"endDate";
|
||||
|
||||
static NSString *stringFromActivityConfidence(CMMotionActivityConfidence confidence) {
|
||||
NSDictionary *confidences = @{@(CMMotionActivityConfidenceHigh): @"high",
|
||||
@(CMMotionActivityConfidenceMedium): @"medium",
|
||||
@(CMMotionActivityConfidenceLow): @"low"};
|
||||
return confidences[@(confidence)];
|
||||
}
|
||||
|
||||
static NSArray *activityArray(CMMotionActivity *activity) {
|
||||
NSMutableArray *array = [NSMutableArray array];
|
||||
if (activity.unknown) {
|
||||
[array addObject:ActivityUnknown];
|
||||
}
|
||||
if (activity.stationary) {
|
||||
[array addObject:ActivityStationary];
|
||||
}
|
||||
if (activity.walking) {
|
||||
[array addObject:ActivityWalking];
|
||||
}
|
||||
if (activity.running) {
|
||||
[array addObject:ActivityRunning];
|
||||
}
|
||||
if (activity.automotive) {
|
||||
[array addObject:ActivityAutomotive];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
static NSString *const ActivityKey = @"activity";
|
||||
static NSString *const ConfidenceKey = @"confidence";
|
||||
|
||||
|
||||
@implementation CMMotionActivity (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
return @{ConfidenceKey: stringFromActivityConfidence(self.confidence),
|
||||
ActivityKey: activityArray(self),
|
||||
StartDateKey: ORKStringFromDateISO8601(self.startDate)};
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CMPedometerData (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "CMPedometerData+ORKJSONDictionary.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
@implementation CMPedometerData (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
NSMutableDictionary *dictionary = [@{ @"startDate": ORKStringFromDateISO8601(self.startDate), @"endDate": ORKStringFromDateISO8601(self.endDate) } mutableCopy];
|
||||
for (NSString *key in @[ @"numberOfSteps", @"distance", @"floorsAscended", @"floorsDescended" ]) {
|
||||
[dictionary setValue:[self valueForKey:key] forKey:key];
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import HealthKit;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_OPTIONS(NSInteger, ORKSampleJSONOptions) {
|
||||
ORKSampleIncludeMetadata = 0x1,
|
||||
ORKSampleIncludeSource = 0x2,
|
||||
ORKSampleIncludeUUID = 0x4
|
||||
};
|
||||
|
||||
/**
|
||||
JSON serialization aid for HKSample.
|
||||
*/
|
||||
@interface HKSample (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(nullable HKUnit *)unit;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
JSON serialization aid for HKCorrelation.
|
||||
*/
|
||||
@interface HKCorrelation (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options sampleTypes:(NSArray *)sampleTypes units:(NSArray *)units;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "HKSample+ORKJSONDictionary.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
static NSString *const HKSampleIdentifierKey = @"type"; // For compatibility with Health XML export
|
||||
static NSString *const HKUUIDKey = @"uuid";
|
||||
static NSString *const HKSampleStartDateKey = @"startDate";
|
||||
static NSString *const HKSampleEndDateKey = @"endDate";
|
||||
static NSString *const HKSampleValue = @"value";
|
||||
static NSString *const HKMetadataKey = @"metadata";
|
||||
static NSString *const HKSourceKey = @"source";
|
||||
static NSString *const HKUnitKey = @"unit";
|
||||
static NSString *const HKCorrelatedObjectsKey = @"objects";
|
||||
// static NSString *const HKSourceIdentifierKey = @"sourceBundleIdentifier";
|
||||
|
||||
|
||||
@implementation HKSample (ORKJSONDictionary)
|
||||
|
||||
- (NSMutableDictionary *)ork_JSONMutableDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithCapacity:12];
|
||||
|
||||
// Type identification
|
||||
HKSampleType *sampleType = [self sampleType];
|
||||
mutableDictionary[HKSampleIdentifierKey] = [sampleType identifier];
|
||||
|
||||
// consider adding @"class" : NSStringFromClass(sampleType) ?
|
||||
|
||||
// Start and end dates
|
||||
NSDate *startDate = [self startDate];
|
||||
if (startDate) {
|
||||
mutableDictionary[HKSampleStartDateKey] = ORKStringFromDateISO8601(startDate);
|
||||
}
|
||||
|
||||
NSDate *endDate = [self endDate];
|
||||
if (endDate) {
|
||||
mutableDictionary[HKSampleEndDateKey] = ORKStringFromDateISO8601(endDate);
|
||||
}
|
||||
if (unit) {
|
||||
mutableDictionary[HKUnitKey] = [unit unitString];
|
||||
}
|
||||
if ((options & ORKSampleIncludeUUID)) {
|
||||
NSUUID *uuid = [self UUID];
|
||||
if (uuid) {
|
||||
mutableDictionary[HKUUIDKey] = uuid.UUIDString;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (options & ORKSampleIncludeMetadata) && self.metadata.count > 0) {
|
||||
NSMutableDictionary *metadata = [self.metadata mutableCopy];
|
||||
for (NSString *k in metadata) {
|
||||
id obj = metadata[k];
|
||||
if ([obj isKindOfClass:[NSDate class]]) {
|
||||
metadata[k] = ORKStringFromDateISO8601(obj);
|
||||
}
|
||||
}
|
||||
|
||||
mutableDictionary[HKMetadataKey] = metadata;
|
||||
}
|
||||
|
||||
if (options & ORKSampleIncludeSource) {
|
||||
HKSource *source = [[self sourceRevision] source];
|
||||
if (source.name) {
|
||||
mutableDictionary[HKSourceKey] = source.name;
|
||||
}
|
||||
}
|
||||
|
||||
return mutableDictionary;
|
||||
}
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
return [self ork_JSONMutableDictionaryWithOptions:options unit:unit];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface HKCategorySample (ORKJSONDictionary)
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation HKCategorySample (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
NSMutableDictionary *dictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:unit];
|
||||
|
||||
NSInteger value = self.value;
|
||||
dictionary[HKSampleValue] = @(value);
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface HKQuantitySample (ORKJSONDictionary)
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation HKQuantitySample (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
NSMutableDictionary *dictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:unit];
|
||||
|
||||
HKQuantity *quantity = [self quantity];
|
||||
double value = [quantity doubleValueForUnit:unit];
|
||||
dictionary[HKSampleValue] = @(value);
|
||||
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation HKCorrelation (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options sampleTypes:(NSArray *)sampleTypes units:(NSArray *)units {
|
||||
NSMutableDictionary *mutableDictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:nil];
|
||||
|
||||
// The correlated objects
|
||||
NSMutableArray *correlatedObjects = [NSMutableArray arrayWithCapacity:sampleTypes.count];
|
||||
for (HKSample *sample in self.objects) {
|
||||
NSUInteger idx = [sampleTypes indexOfObject:sample.sampleType];
|
||||
if (idx == NSNotFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[correlatedObjects addObject:[sample ork_JSONDictionaryWithOptions:options unit:units[idx]]];
|
||||
}
|
||||
mutableDictionary[HKCorrelatedObjectsKey] = correlatedObjects;
|
||||
|
||||
return mutableDictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKAccelerometerRecorder` class represents a recorder that requests and collects raw accelerometer data from CoreMotion at a fixed frequency.
|
||||
|
||||
The accelerometer recorder continues to record when the application enters the
|
||||
background by using the background task support provided by UIApplication.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAccelerometerRecorder : ORKRecorder
|
||||
|
||||
/**
|
||||
The frequency of accelerometer data collected from CoreMotion, in hertz (Hz).
|
||||
*/
|
||||
@property (nonatomic, readonly) double frequency;
|
||||
|
||||
/**
|
||||
Returns an initialized accelerometer recorder using the specified frequency.
|
||||
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param frequency The frequency of accelerometer data collected from CoreMotion, in hertz (Hz).
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the accelerometer data should be stored.
|
||||
|
||||
@return An initialized accelerometer recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
frequency:(double)frequency
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAccelerometerRecorder.h"
|
||||
|
||||
#import "ORKDataLogger.h"
|
||||
|
||||
#import "ORKRecorder_Internal.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "CMAccelerometerData+ORKJSONDictionary.h"
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
@interface ORKAccelerometerRecorder () {
|
||||
ORKDataLogger *_logger;
|
||||
NSError *_recordingError;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) CMMotionManager *motionManager;
|
||||
|
||||
@property (nonatomic) NSTimeInterval uptime;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAccelerometerRecorder
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
self.frequency = frequency;
|
||||
self.continuesInBackground = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_logger finishCurrentLog];
|
||||
}
|
||||
|
||||
- (NSString *)recorderType {
|
||||
return @"accel";
|
||||
}
|
||||
|
||||
- (void)setFrequency:(double)frequency {
|
||||
if (frequency <= 0) {
|
||||
_frequency = 1;
|
||||
} else {
|
||||
_frequency = frequency;
|
||||
}
|
||||
}
|
||||
|
||||
- (CMMotionManager *)createMotionManager {
|
||||
return [[CMMotionManager alloc] init];
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
self.motionManager = [self createMotionManager];
|
||||
|
||||
if (!_logger) {
|
||||
NSError *error = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&error];
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.motionManager || !self.motionManager.accelerometerAvailable) {
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder": self}];
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
self.motionManager.accelerometerUpdateInterval = 1.0 / _frequency;
|
||||
|
||||
self.uptime = [NSProcessInfo processInfo].systemUptime;
|
||||
|
||||
[self.motionManager stopAccelerometerUpdates];
|
||||
|
||||
[self.motionManager startAccelerometerUpdatesToQueue:[[NSOperationQueue alloc] init] withHandler:^(CMAccelerometerData *data, NSError *error) {
|
||||
BOOL success = NO;
|
||||
if (data) {
|
||||
success = [_logger append:[data ork_JSONDictionary] error:&error];
|
||||
}
|
||||
if (!success) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_recordingError = error;
|
||||
[self stop];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDictionary *)userInfo {
|
||||
return @{ @"frequency": @(self.frequency) };
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
[self doStopRecording];
|
||||
[_logger finishCurrentLog];
|
||||
|
||||
NSError *error = _recordingError;
|
||||
_recordingError = nil;
|
||||
__block NSURL *fileUrl = nil;
|
||||
[_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
fileUrl = logFileUrl;
|
||||
} error:&error];
|
||||
|
||||
[self reportFileResultWithFile:fileUrl error:error];
|
||||
|
||||
[super stop];
|
||||
}
|
||||
|
||||
- (void)doStopRecording {
|
||||
if (self.isRecording) {
|
||||
[self.motionManager stopAccelerometerUpdates];
|
||||
self.motionManager = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
[super finishRecordingWithError:nil];
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
|
||||
_logger = nil;
|
||||
}
|
||||
|
||||
- (BOOL)isRecording {
|
||||
return self.motionManager.accelerometerActive;
|
||||
}
|
||||
|
||||
- (NSString *)mimeType {
|
||||
return @"application/json";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAccelerometerRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
_frequency = frequency;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKAccelerometerRecorder alloc] initWithIdentifier:self.identifier
|
||||
frequency:self.frequency
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_DOUBLE(aDecoder, frequency);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_DOUBLE(aCoder, frequency);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.frequency == castObject.frequency));
|
||||
}
|
||||
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
return ORKPermissionCoreMotionAccelerometer;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,219 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
@import HealthKit;
|
||||
#import <ResearchKit/ORKStep.h>
|
||||
|
||||
|
||||
@class ORKRecorderConfiguration;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKActiveStep` class is the base class for steps in active tasks, which
|
||||
are steps that collect sensor data in a semi-controlled environment, as opposed
|
||||
to the purely passive data collection enabled by HealthKit, or the more subjective data
|
||||
collected when users fill in surveys.
|
||||
|
||||
In addition to the behaviors of `ORKStep`, active steps have the concept of
|
||||
life cycle, which includes a defined start and finish.
|
||||
|
||||
The ResearchKit framework provides built-in behaviors that allow active steps to play voice prompts, speak a count down, and have a
|
||||
defined duration.
|
||||
|
||||
To present an active step in your app, it's likely that you will subclass `ORKActiveStep` and
|
||||
`ORKActiveStepViewController` to present custom UI and custom
|
||||
prompts. For example subclasses, see `ORKSpatialSpanMemoryStep` or `ORKFitnessStep`.
|
||||
Active steps may also need `ORKResult` subclasses to record their results
|
||||
if these don't come purely from recorders.
|
||||
|
||||
If you develop a new active step subclass, consider contributing your
|
||||
code to the ResearchKit project so that it's available for others to use in
|
||||
their studies.
|
||||
|
||||
See also: `ORKActiveStepViewController`
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKActiveStep : ORKStep
|
||||
|
||||
/**
|
||||
The duration of the step in seconds.
|
||||
|
||||
If the step duration is greater than zero, a built-in timer starts when the
|
||||
step starts. If `shouldStartTimerAutomatically` is set, the timer
|
||||
starts when the step's view appears. When the timer expires, a sound or
|
||||
vibration may be played. If `shouldContinueOnFinish` is set, the step
|
||||
automatically navigates forward when the timer expires.
|
||||
|
||||
The default value of this property is `0`, which disables the built-in timer.
|
||||
|
||||
See also: `ORKActiveStepViewController`
|
||||
*/
|
||||
@property (nonatomic) NSTimeInterval stepDuration;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to show a view with a default timer.
|
||||
|
||||
The default timer UI is not used in any of the current predefined tasks,
|
||||
but it can be displayed in a simple active task that does not require custom
|
||||
UI and needs only a count down timer on screen during data collection.
|
||||
|
||||
Note that this property is ignored if `stepDuration` is `0`.
|
||||
|
||||
The default value of this property is `YES`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldShowDefaultTimer;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to speak the last few seconds in the count down of the
|
||||
duration of a timed step.
|
||||
|
||||
When the value of this property is `YES`, `AVSpeechSynthesizer` is used to synthesize the countdown. Note that this property is ignored if VoiceOver is enabled.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldSpeakCountDown;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to speak the halfway point in the count down of the
|
||||
duration of a timed step.
|
||||
|
||||
When the value of this property is `YES`, `AVSpeechSynthesizer` is used to synthesize the countdown. Note that this property is ignored if VoiceOver is enabled.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldSpeakRemainingTimeAtHalfway;
|
||||
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to start the count down timer automatically when the step starts, or
|
||||
require the user to take some explicit action to start the step, such as tapping a button.
|
||||
|
||||
Usually the explicit action needs to come from custom UI in an
|
||||
`ORKActiveStepViewController` subclass.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldStartTimerAutomatically;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to play a default sound when the step starts.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldPlaySoundOnStart;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to play a default sound when the step finishes.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldPlaySoundOnFinish;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to vibrate when the step starts.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldVibrateOnStart;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to vibrate when the step finishes.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldVibrateOnFinish;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the Next button should double as a skip action before
|
||||
the step finishes.
|
||||
|
||||
When the value of this property is `YES`, the ResearchKit framework hides the skip button and makes the Next button function as a skip button when the step has not yet finished.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldUseNextAsSkipButton;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to transition automatically when the step finishes.
|
||||
|
||||
When the value of this property is `YES`, the active step view controller automatically performs the
|
||||
continue action when the `[ORKActiveStepViewController finish]` method
|
||||
is called.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldContinueOnFinish;
|
||||
|
||||
/**
|
||||
Localized text that represents an instructional voice prompt.
|
||||
|
||||
Instructional speech begins when the step starts. If VoiceOver is active,
|
||||
the instruction is spoken by VoiceOver.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *spokenInstruction;
|
||||
|
||||
/**
|
||||
Localized text that represents an instructional voice prompt for when the step finishes.
|
||||
|
||||
Instructional speech begins when the step finishes. If VoiceOver is active,
|
||||
the instruction is spoken by VoiceOver.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *finishedSpokenInstruction;
|
||||
|
||||
/**
|
||||
An image to be displayed below the instructions for the step.
|
||||
|
||||
The image can be stretched to fit the available space. When choosing a size
|
||||
for this asset, be sure to take into account the variations in device form factors.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) UIImage *image;
|
||||
|
||||
/**
|
||||
An array of recorder configurations that define the parameters for recorders to be
|
||||
run during a step to collect sensor or other data.
|
||||
|
||||
If you want to collect data from sensors while the step is in progress,
|
||||
add one or more recorder configurations to the array. The active step view
|
||||
controller instantiates recorders and collates their results as children
|
||||
of the step result.
|
||||
|
||||
The set of recorder configurations is scanned when populating the
|
||||
`requestedHealthKitTypesForReading` and `requestedPermissions` properties.
|
||||
|
||||
See also: `ORKRecorderConfiguration` and `ORKRecorder`.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSArray<ORKRecorderConfiguration *> *recorderConfigurations;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStep.h"
|
||||
#import "ORKActiveStep_Internal.h"
|
||||
|
||||
#import "ORKActiveStepViewController.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKActiveStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKActiveStepViewController class];
|
||||
}
|
||||
|
||||
- (BOOL)startsFinished {
|
||||
return (_stepDuration == 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasCountDown {
|
||||
return (_stepDuration > 0) && _shouldShowDefaultTimer;
|
||||
}
|
||||
|
||||
- (BOOL)hasTitle {
|
||||
NSString *title = self.title;
|
||||
return (title != nil && title.length > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasText {
|
||||
NSString *text = self.text;
|
||||
return (text != nil && text.length > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasVoice {
|
||||
BOOL hasSpokenInstruction = (_spokenInstruction != nil && _spokenInstruction.length > 0);
|
||||
BOOL hasFinishedSpokenInstruction = (_finishedSpokenInstruction != nil && _finishedSpokenInstruction.length > 0);
|
||||
return (hasSpokenInstruction || hasFinishedSpokenInstruction);
|
||||
}
|
||||
|
||||
- (BOOL)isRestorable {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKActiveStep *step = [super copyWithZone:zone];
|
||||
step.stepDuration = self.stepDuration;
|
||||
step.shouldStartTimerAutomatically = self.shouldStartTimerAutomatically;
|
||||
step.shouldSpeakCountDown = self.shouldSpeakCountDown;
|
||||
step.shouldSpeakRemainingTimeAtHalfway = self.shouldSpeakRemainingTimeAtHalfway;
|
||||
step.shouldShowDefaultTimer = self.shouldShowDefaultTimer;
|
||||
step.shouldPlaySoundOnStart = self.shouldPlaySoundOnStart;
|
||||
step.shouldPlaySoundOnFinish = self.shouldPlaySoundOnFinish;
|
||||
step.shouldVibrateOnStart = self.shouldVibrateOnStart;
|
||||
step.shouldVibrateOnFinish = self.shouldVibrateOnFinish;
|
||||
step.shouldUseNextAsSkipButton = self.shouldUseNextAsSkipButton;
|
||||
step.shouldContinueOnFinish = self.shouldContinueOnFinish;
|
||||
step.spokenInstruction = self.spokenInstruction;
|
||||
step.finishedSpokenInstruction = self.finishedSpokenInstruction;
|
||||
step.recorderConfigurations = [self.recorderConfigurations copy];
|
||||
step.image = self.image;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self ) {
|
||||
ORK_DECODE_DOUBLE(aDecoder, stepDuration);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldStartTimerAutomatically);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldSpeakCountDown);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldSpeakRemainingTimeAtHalfway);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldShowDefaultTimer);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldPlaySoundOnStart);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldPlaySoundOnFinish);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldVibrateOnStart);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldVibrateOnFinish);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldUseNextAsSkipButton);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldContinueOnFinish);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, spokenInstruction, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, finishedSpokenInstruction, NSString);
|
||||
ORK_DECODE_IMAGE(aDecoder, image);
|
||||
ORK_DECODE_OBJ_ARRAY(aDecoder, recorderConfigurations, ORKRecorderConfiguration);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_DOUBLE(aCoder, stepDuration);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldStartTimerAutomatically);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldSpeakCountDown);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldSpeakRemainingTimeAtHalfway);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldShowDefaultTimer);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldPlaySoundOnStart);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldPlaySoundOnFinish);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldVibrateOnStart);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldVibrateOnFinish);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldUseNextAsSkipButton);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldContinueOnFinish);
|
||||
ORK_ENCODE_IMAGE(aCoder, image);
|
||||
ORK_ENCODE_OBJ(aCoder, spokenInstruction);
|
||||
ORK_ENCODE_OBJ(aCoder, finishedSpokenInstruction);
|
||||
ORK_ENCODE_OBJ(aCoder, recorderConfigurations);
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
ORKEqualObjects(self.spokenInstruction, castObject.spokenInstruction) &&
|
||||
ORKEqualObjects(self.finishedSpokenInstruction, castObject.finishedSpokenInstruction) &&
|
||||
ORKEqualObjects(self.recorderConfigurations, castObject.recorderConfigurations) &&
|
||||
ORKEqualObjects(self.image, castObject.image) &&
|
||||
(self.stepDuration == castObject.stepDuration) &&
|
||||
(self.shouldShowDefaultTimer == castObject.shouldShowDefaultTimer) &&
|
||||
(self.shouldStartTimerAutomatically == castObject.shouldStartTimerAutomatically) &&
|
||||
(self.shouldSpeakCountDown == castObject.shouldSpeakCountDown) &&
|
||||
(self.shouldSpeakRemainingTimeAtHalfway == castObject.shouldSpeakRemainingTimeAtHalfway) &&
|
||||
(self.shouldPlaySoundOnStart == castObject.shouldPlaySoundOnStart) &&
|
||||
(self.shouldPlaySoundOnFinish == castObject.shouldPlaySoundOnFinish) &&
|
||||
(self.shouldVibrateOnStart == castObject.shouldVibrateOnStart) &&
|
||||
(self.shouldVibrateOnFinish == castObject.shouldVibrateOnFinish) &&
|
||||
(self.shouldContinueOnFinish == castObject.shouldContinueOnFinish) &&
|
||||
(self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton));
|
||||
}
|
||||
|
||||
- (NSSet<HKObjectType *> *)requestedHealthKitTypesForReading {
|
||||
NSMutableSet<HKObjectType *> *set = [NSMutableSet set];
|
||||
for (ORKRecorderConfiguration *config in self.recorderConfigurations) {
|
||||
NSSet<HKObjectType *> *subset = [config requestedHealthKitTypesForReading];
|
||||
if (subset) {
|
||||
[set unionSet:subset];
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
- (ORKPermissionMask)requestedPermissions {
|
||||
ORKPermissionMask mask = [super requestedPermissions];
|
||||
for (ORKRecorderConfiguration *config in self.recorderConfigurations) {
|
||||
mask |= [config requestedPermissionMask];
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKLabel.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKQuantityLabel : ORKLabel
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKActiveStepQuantityView : UIView
|
||||
|
||||
@property (nonatomic, strong, nullable) NSString *title;
|
||||
@property (nonatomic, strong, nullable) NSString *value;
|
||||
@property (nonatomic, strong, nullable) UIImage *image;
|
||||
@property (nonatomic) BOOL enabled;
|
||||
|
||||
@property (nonatomic, strong, readonly, nullable) UILabel *titleLabel;
|
||||
@property (nonatomic, strong, readonly, nullable) UILabel *valueLabel;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKQuantityPairView : UIView
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKActiveStepQuantityView *leftView;
|
||||
@property (nonatomic, strong, nullable) ORKActiveStepQuantityView *rightView;
|
||||
|
||||
@property (nonatomic, assign) BOOL keylineHidden;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,333 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStepQuantityView.h"
|
||||
|
||||
#import "ORKSubheadlineLabel.h"
|
||||
#import "ORKTintedImageView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@implementation ORKQuantityLabel
|
||||
|
||||
+ (UIFont *)defaultFont {
|
||||
return ORKTimeFontForSize(35);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKActiveStepQuantityView {
|
||||
ORKSubheadlineLabel *_titleLabel;
|
||||
ORKQuantityLabel *_valueLabel;
|
||||
ORKTintedImageView *_imageView;
|
||||
UIView *_valueHolder;
|
||||
|
||||
NSLayoutConstraint *_zeroWidthConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_titleLabel = [ORKSubheadlineLabel new];
|
||||
_titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_valueLabel = [ORKQuantityLabel new];
|
||||
_valueLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_imageView = [ORKTintedImageView new];
|
||||
_imageView.shouldApplyTint = YES;
|
||||
_valueHolder = [UIView new];
|
||||
_titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_valueHolder.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
[self addSubview:_titleLabel];
|
||||
[_valueHolder addSubview:_valueLabel];
|
||||
[_valueHolder addSubview:_imageView];
|
||||
[self addSubview:_valueHolder];
|
||||
|
||||
#if LAYOUT_DEBUG
|
||||
self.backgroundColor = [[UIColor blueColor] colorWithAlphaComponent:0.2];
|
||||
_titleLabel.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.2];
|
||||
_valueLabel.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.2];
|
||||
#endif
|
||||
|
||||
for (UIView *view in @[_titleLabel, _valueLabel, _imageView]) {
|
||||
view.isAccessibilityElement = NO;
|
||||
}
|
||||
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
_enabled = enabled;
|
||||
self.hidden = !enabled;
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
- (void)setTitle:(NSString *)title {
|
||||
_title = title;
|
||||
_titleLabel.text = title;
|
||||
}
|
||||
|
||||
- (void)setValue:(NSString *)value {
|
||||
_value = value;
|
||||
_valueLabel.text = value;
|
||||
}
|
||||
|
||||
- (void)setImage:(UIImage *)image {
|
||||
_image = nil;
|
||||
_imageView.image = image;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
|
||||
const CGFloat TitleBaselineToValueBaseline = 40;
|
||||
const CGFloat ValueBaselineToBottom = 36;
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_titleLabel, _valueLabel, _imageView);
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleLabel]"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleLabel]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_imageView]-10-[_valueLabel]|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_titleLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1.0
|
||||
constant:TitleBaselineToValueBaseline]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_valueLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1.0
|
||||
constant:ValueBaselineToBottom]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:_valueHolder
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:_valueHolder
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeLeft
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeLeft
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeRight
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeRight
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
for (NSLayoutConstraint *constraint in constraints) {
|
||||
constraint.priority = UILayoutPriorityRequired - 2;
|
||||
}
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
|
||||
_zeroWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
_zeroWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
_zeroWidthConstraint.active = !_enabled;
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark Accessibility
|
||||
|
||||
- (BOOL)isAccessibilityElement {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel {
|
||||
return _titleLabel.accessibilityLabel;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityValue {
|
||||
return _valueLabel.accessibilityLabel;
|
||||
}
|
||||
|
||||
- (UIAccessibilityTraits)accessibilityTraits {
|
||||
return [super accessibilityTraits] | UIAccessibilityTraitUpdatesFrequently;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKQuantityPairView {
|
||||
UIView *_metricKeyline;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_leftView = [ORKActiveStepQuantityView new];
|
||||
_rightView = [ORKActiveStepQuantityView new];
|
||||
|
||||
_leftView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_rightView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_metricKeyline = [UIView new];
|
||||
_metricKeyline.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self setKeylineHidden:NO];
|
||||
_metricKeyline.backgroundColor = [UIColor ork_midGrayTintColor];
|
||||
|
||||
[self addSubview:_leftView];
|
||||
[self addSubview:_rightView];
|
||||
[self addSubview:_metricKeyline];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_leftView, _rightView, _metricKeyline);
|
||||
|
||||
// Leave space for the keyline between these views, and then constrain it to be 1px wide and go from top to bottom baseline of metric views.
|
||||
CGFloat scale = [UIScreen mainScreen].scale;
|
||||
NSArray *vertConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_leftView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views];
|
||||
[constraints addObjectsFromArray:vertConstraints];
|
||||
|
||||
NSArray *horizConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_leftView]-s-[_rightView]-|"
|
||||
options:NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom|NSLayoutFormatDirectionLeftToRight
|
||||
metrics:@{ @"s": @(1.0 / scale) }
|
||||
views:views];
|
||||
for (NSLayoutConstraint *constraint in horizConstraints) {
|
||||
constraint.priority = UILayoutPriorityDefaultHigh + 1;
|
||||
}
|
||||
[constraints addObjectsFromArray:horizConstraints];
|
||||
|
||||
// Ensure baseline alignment of title and value
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_leftView.titleLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_rightView.titleLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_leftView.valueLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_rightView.valueLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_leftView][_metricKeyline(==s)]"
|
||||
options:NSLayoutFormatAlignAllTop|NSLayoutFormatDirectionLeftToRight
|
||||
metrics:@{ @"s": @(1.0 / scale) }
|
||||
views:views]];
|
||||
NSLayoutConstraint *keylineBottom = [NSLayoutConstraint constraintWithItem:_metricKeyline
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_leftView.valueLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
[constraints addObject:keylineBottom];
|
||||
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired - 2;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
|
||||
// This constraint should be beaten out by the full-width-coverage and zero-width constraints if only one of the views is enabled.
|
||||
NSLayoutConstraint *equalWidthConstraint = [NSLayoutConstraint constraintWithItem:_leftView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_rightView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
equalWidthConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
[constraints addObject:equalWidthConstraint];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)setKeylineHidden:(BOOL)keylineHidden {
|
||||
_keylineHidden = keylineHidden;
|
||||
_metricKeyline.hidden = keylineHidden;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKActiveStepTimer;
|
||||
|
||||
typedef void (^ORKActiveStepTimerHandler)(ORKActiveStepTimer *timer, BOOL finished);
|
||||
|
||||
@interface ORKActiveStepTimer : NSObject
|
||||
|
||||
- (instancetype)initWithDuration:(NSTimeInterval)duration interval:(NSTimeInterval)interval runtime:(NSTimeInterval)runtime handler:(ORKActiveStepTimerHandler)handler;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval duration;
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval interval;
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval runtime;
|
||||
|
||||
/*
|
||||
Handler callbacks are returned on interval boundaries. The timer automatically
|
||||
pauses itself when finished=YES.
|
||||
|
||||
This handler is retained. Be careful not to create a retain cycle.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) ORKActiveStepTimerHandler handler;
|
||||
|
||||
// Pauses the timer.
|
||||
- (void)pause;
|
||||
|
||||
// Resumes the timer where it left off.
|
||||
- (void)resume;
|
||||
|
||||
// Sets runtime to 0. Stops the timer if it is running.
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,243 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
@import UIKit;
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
|
||||
static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
static mach_timebase_info_data_t sTimebaseInfo;
|
||||
if ( sTimebaseInfo.denom == 0 ) {
|
||||
(void) mach_timebase_info(&sTimebaseInfo);
|
||||
}
|
||||
uint64_t elapsedNano = delta * sTimebaseInfo.numer / sTimebaseInfo.denom;
|
||||
return elapsedNano * 1.0 / NSEC_PER_SEC;
|
||||
}
|
||||
|
||||
|
||||
@implementation ORKActiveStepTimer {
|
||||
uint64_t _startTime;
|
||||
NSTimeInterval _preExistingRuntime;
|
||||
dispatch_queue_t _queue;
|
||||
dispatch_source_t _timer;
|
||||
UIBackgroundTaskIdentifier _backgroundTaskIdentifier;
|
||||
uint32_t _isRunning;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDuration:(NSTimeInterval)duration interval:(NSTimeInterval)interval runtime:(NSTimeInterval)runtime handler:(ORKActiveStepTimerHandler)handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
if (!handler) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Handler is required" userInfo:nil];
|
||||
}
|
||||
|
||||
_duration = duration;
|
||||
_interval = interval;
|
||||
_handler = [handler copy];
|
||||
_preExistingRuntime = runtime;
|
||||
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
||||
|
||||
_queue = dispatch_queue_create("active_step", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self queue_pauseAtFinish:NO];
|
||||
}
|
||||
|
||||
- (NSTimeInterval)runtime {
|
||||
__block NSTimeInterval runtime = 0;
|
||||
dispatch_sync(_queue, ^{
|
||||
runtime = [self queue_runtime];
|
||||
});
|
||||
return MIN(runtime,_duration);
|
||||
}
|
||||
|
||||
- (void)pause {
|
||||
dispatch_sync(_queue, ^{
|
||||
[self queue_pauseAtFinish:NO];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)resume {
|
||||
dispatch_sync(_queue, ^{
|
||||
[self queue_resume];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
dispatch_sync(_queue, ^{
|
||||
[self queue_reset];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSTimeInterval)queue_runtime {
|
||||
NSTimeInterval runtime = _preExistingRuntime;
|
||||
if (_timer != NULL) {
|
||||
uint64_t now = mach_absolute_time();
|
||||
runtime += timeIntervalFromMachTime(now - _startTime);
|
||||
}
|
||||
return runtime;
|
||||
}
|
||||
|
||||
- (void)setDuration:(NSTimeInterval)duration {
|
||||
dispatch_sync(_queue, ^{
|
||||
_duration = duration;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)hiqueue_event {
|
||||
dispatch_sync(_queue, ^{
|
||||
[self queue_event];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)queue_event {
|
||||
[self queue_assertBackgroundTask];
|
||||
|
||||
NSTimeInterval runtime = [self queue_runtime];
|
||||
BOOL finished = (runtime >= _duration);
|
||||
if (finished) {
|
||||
[self queue_pauseAtFinish:YES];
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_handler(self, finished);
|
||||
dispatch_sync(_queue, ^{
|
||||
|
||||
// If the timer is still NULL here, we can safely release the background task.
|
||||
if (_timer == NULL) {
|
||||
[self queue_releaseBackgroundTask];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)queue_clearTimer {
|
||||
if (_timer != NULL) {
|
||||
|
||||
dispatch_source_cancel(_timer);
|
||||
_timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)queue_releaseBackgroundTask {
|
||||
if (_backgroundTaskIdentifier == UIBackgroundTaskInvalid) {
|
||||
return;
|
||||
}
|
||||
UIBackgroundTaskIdentifier identifier = _backgroundTaskIdentifier;
|
||||
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[[UIApplication sharedApplication] endBackgroundTask:identifier];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)queue_assertBackgroundTask {
|
||||
if (_backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
|
||||
return;
|
||||
}
|
||||
_backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
||||
// This is guaranteed to be called synchronously on the main queue, switch to our queue to invalidate the identifier
|
||||
dispatch_sync(_queue, ^{
|
||||
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)queue_resume {
|
||||
if (_timer != NULL) {
|
||||
// Already resumed
|
||||
return;
|
||||
}
|
||||
if ([self queue_runtime] >= _duration) {
|
||||
// Already finished. Fire one event to indicate.
|
||||
[self queue_event];
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to run in the background if we can, so voice can be played, etc.
|
||||
assert(_backgroundTaskIdentifier == UIBackgroundTaskInvalid);
|
||||
|
||||
[self queue_assertBackgroundTask];
|
||||
|
||||
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
|
||||
0, 0, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0));
|
||||
if (_timer == NULL) {
|
||||
assert(0);
|
||||
return;
|
||||
}
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
dispatch_source_set_event_handler(_timer, ^{
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf hiqueue_event];
|
||||
});
|
||||
|
||||
NSTimeInterval timeUntilNextFire = (floor(_preExistingRuntime / _interval) + 1)*_interval - _preExistingRuntime;
|
||||
|
||||
_startTime = mach_absolute_time();
|
||||
dispatch_source_set_timer(_timer,
|
||||
dispatch_time(DISPATCH_TIME_NOW, timeUntilNextFire * NSEC_PER_SEC),
|
||||
_interval * NSEC_PER_SEC,
|
||||
0.05 * NSEC_PER_SEC);
|
||||
dispatch_resume(_timer);
|
||||
}
|
||||
|
||||
- (void)queue_pauseAtFinish:(BOOL)atFinish {
|
||||
if (_timer == NULL) {
|
||||
// Not running
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t now = mach_absolute_time();
|
||||
[self queue_clearTimer];
|
||||
_preExistingRuntime += timeIntervalFromMachTime(now - _startTime);
|
||||
_startTime = 0;
|
||||
|
||||
if (!atFinish) {
|
||||
// If we are atFinish, the task will be released after the handler completes
|
||||
[self queue_releaseBackgroundTask];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)queue_reset {
|
||||
[self queue_clearTimer];
|
||||
_preExistingRuntime = 0;
|
||||
[self queue_releaseBackgroundTask];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKActiveStep;
|
||||
@class ORKCountdownLabel;
|
||||
@class ORKTextButton;
|
||||
|
||||
@interface ORKActiveStepTimerView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKCountdownLabel *countDownLabel;
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKTextButton *startTimerButton;
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKActiveStep *step;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStepTimerView.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKCountdownLabel.h"
|
||||
#import "ORKSurveyAnswerCellForText.h"
|
||||
#import "ORKSurveyAnswerCellForNumber.h"
|
||||
#import "ORKTextButton.h"
|
||||
#import "ORKVoiceEngine.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
|
||||
#import "ORKActiveStep_Internal.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@implementation ORKActiveStepTimerView {
|
||||
BOOL _started;
|
||||
BOOL _registeredForNotifications;
|
||||
|
||||
NSLayoutConstraint *_countDownLabelBottomToStartTimerButtonTopConstraint;
|
||||
NSLayoutConstraint *_countDownLabelZeroHeightConstraint;
|
||||
NSLayoutConstraint *_startTimerButtonZeroHeightConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
// Count Down
|
||||
{
|
||||
_countDownLabel = [ORKCountdownLabel new];
|
||||
_countDownLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_countDownLabel.text = @" ";
|
||||
|
||||
[self addSubview:_countDownLabel];
|
||||
}
|
||||
// Count down start button
|
||||
{
|
||||
_startTimerButton = [ORKTextButton new];
|
||||
[_startTimerButton setTitle:ORKLocalizedString(@"BUTTON_START_TIMER", nil) forState:UIControlStateNormal];
|
||||
[_startTimerButton addTarget:self action:@selector(startTimerButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||||
_startTimerButton.exclusiveTouch = YES;
|
||||
|
||||
[self addSubview:_startTimerButton];
|
||||
}
|
||||
|
||||
_countDownLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
ORKActiveStepViewController *viewController = self.activeStepViewController;
|
||||
if (viewController) {
|
||||
[self updateDisplay:viewController];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRegisteredForNotifications:(BOOL)registered {
|
||||
if (registered == _registeredForNotifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
registered = _registeredForNotifications;
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
if (registered) {
|
||||
[notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
} else {
|
||||
[notificationCenter removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow {
|
||||
[self setRegisteredForNotifications:(self.window != nil)];
|
||||
}
|
||||
|
||||
- (void)setStep:(ORKActiveStep *)step {
|
||||
_step = step;
|
||||
_countDownLabel.hidden = !(_step.hasCountDown);
|
||||
BOOL hasTimerButton = (_step.hasCountDown && _step.shouldStartTimerAutomatically == NO);
|
||||
_startTimerButton.hidden = !hasTimerButton;
|
||||
_startTimerButton.alpha = 1;
|
||||
|
||||
[_countDownLabel setCountDownValue:(NSInteger)[_step stepDuration]];
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
- (void)startTimerButtonTapped:(id)sender {
|
||||
[self.activeStepViewController start];
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, _countDownLabel);
|
||||
}
|
||||
|
||||
- (void)updateDisplay:(ORKActiveStepViewController *)viewController {
|
||||
NSInteger countDownValue = (NSInteger)round(viewController.timeRemaining);
|
||||
[_countDownLabel setCountDownValue:countDownValue];
|
||||
}
|
||||
|
||||
- (void)resetStep:(ORKActiveStepViewController *)viewController {
|
||||
self.step = (ORKActiveStep *)viewController.step;
|
||||
}
|
||||
|
||||
- (void)startStep:(ORKActiveStepViewController *)viewController {
|
||||
_startTimerButton.alpha = 0;
|
||||
}
|
||||
|
||||
- (void)suspendStep:(ORKActiveStepViewController *)viewController {
|
||||
}
|
||||
|
||||
- (void)resumeStep:(ORKActiveStepViewController *)viewController {
|
||||
self.step = (ORKActiveStep *)viewController.step;
|
||||
if ([viewController timerActive]) {
|
||||
_startTimerButton.alpha = 0;
|
||||
[self updateDisplay:viewController];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishStep:(ORKActiveStepViewController *)viewController {
|
||||
}
|
||||
|
||||
static const CGFloat CountDownLabelToButtonMargin = 2.0;
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_countDownLabel, _startTimerButton);
|
||||
ORKEnableAutoLayoutForViews(views.allValues);
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
|
||||
for (UIView *view in views.allValues) {
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:view
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:view
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
}
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_countDownLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
_countDownLabelBottomToStartTimerButtonTopConstraint = [NSLayoutConstraint constraintWithItem:_startTimerButton
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_countDownLabel
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:CountDownLabelToButtonMargin];
|
||||
[constraints addObject:_countDownLabelBottomToStartTimerButtonTopConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_startTimerButton
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
|
||||
_countDownLabelZeroHeightConstraint = [NSLayoutConstraint constraintWithItem:_countDownLabel
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
_startTimerButtonZeroHeightConstraint = [NSLayoutConstraint constraintWithItem:_startTimerButton
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
_countDownLabelZeroHeightConstraint.active = _countDownLabel.hidden;
|
||||
_startTimerButtonZeroHeightConstraint.active = (_countDownLabel.hidden || _startTimerButton.hidden);
|
||||
_countDownLabelBottomToStartTimerButtonTopConstraint.constant =
|
||||
(_countDownLabel.hidden || _startTimerButton.hidden) ? 0.0 : CountDownLabelToButtonMargin;
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKStepViewController.h>
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKActiveStepViewController` class is the base class for displaying `ORKActiveStep`
|
||||
subclasses. The predefined active tasks defined in `ORKOrderedTask` all make use
|
||||
of subclasses of `ORKActiveStep`, paired with `ORKActiveStepViewController`
|
||||
subclasses.
|
||||
|
||||
An active step view controller is typically instantiated by a task view controller
|
||||
when it encounters an active step in a task. Active steps generally include some form of sensor-driven data collection, or involve some highly interactive content, such as a cognitive task or game.
|
||||
|
||||
Examples of active step view controller subclasses include `ORKWalkingTaskStepViewController`,
|
||||
`ORKCountdownStepViewController`, `ORKSpatialSpanMemoryStepViewController`,
|
||||
`ORKFitnessStepViewController`, and `ORKAudioStepViewController`.
|
||||
|
||||
The primary feature that active step view controllers enable is life cycle. After an active step is presented, it can be started to start a timer. When the timer expires, the
|
||||
step is considered finished. Some steps may have the concept of suspend and resume, such as when
|
||||
the app is put in the background, and during which data recording is temporarily paused.
|
||||
These life cycle methods generally apply to any recorders being used to record
|
||||
data from the device's sensors, but they should also be applied to any UI
|
||||
being displayed to clearly indicate when data is being collected
|
||||
for the task.
|
||||
|
||||
When you develop a new active step, you should subclass `ORKActiveStepViewController`
|
||||
and define your specific UI. When subclassing, pay special attention to the life cycle
|
||||
methods, `start`, `finish`, `suspend`, and `resume`. Also, be sure to test for
|
||||
the expected behavior when the user suspends and resumes the app, during task
|
||||
save and restore, and during UIKit's UI state restoration.
|
||||
|
||||
See also: `ORKActiveStep`.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKActiveStepViewController : ORKStepViewController <ORKRecorderDelegate>
|
||||
|
||||
/// @name UI Customization
|
||||
|
||||
/**
|
||||
The custom view for the active step.
|
||||
|
||||
Attach a custom view here, and implement `sizeThatFits:` or
|
||||
use `intrinsicContentSize` or provide constraints that request the size needed for
|
||||
the custom view within the active step's layout.
|
||||
|
||||
Custom views can be used for visual instructions with animation,
|
||||
or for getting interactive input.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) UIView *customView;
|
||||
|
||||
/**
|
||||
The image view for the active step. (read-only)
|
||||
|
||||
The image view is created on demand when this property is accessed, and is a
|
||||
shortcut to display an image in the custom area of an active task (that is, instead of
|
||||
using `customView`).
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) UIImageView *imageView;
|
||||
|
||||
/// @name Data collection
|
||||
|
||||
/**
|
||||
The array of recorders currently in use by the active step. (read-only)
|
||||
|
||||
Recorders are generated when the step starts, based on the recorder
|
||||
configurations of the step. Each recorder is an instance of `ORKRecorder`, and
|
||||
is created by the active step view controller using the array of recorder
|
||||
configurations in the step.
|
||||
|
||||
See also: `ORKRecorderConfiguration` and `ORKActiveStep`.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) NSArray *recorders;
|
||||
|
||||
/// @name Active step life cycle
|
||||
|
||||
/**
|
||||
A Boolean value that indicates whether the step has finished. (read-only)
|
||||
|
||||
If the step is considered finished, the Continue button is enabled and the Skip
|
||||
button is hidden. When the step is not finished, the Continue button is disabled and the Skip
|
||||
button is visible.
|
||||
|
||||
In addition, when a step is finished, all recorders are stopped.
|
||||
*/
|
||||
@property (nonatomic, assign, readonly, getter=isFinished) BOOL finished;
|
||||
|
||||
/**
|
||||
A Boolean value that indicates whether the step has started. (read-only)
|
||||
|
||||
If the step has not yet started, recorders are not yet running, and time
|
||||
is not counted against the `duration` of the step.
|
||||
*/
|
||||
@property (nonatomic, assign, readonly, getter=isStarted) BOOL started;
|
||||
|
||||
/**
|
||||
Tells the view controller that the active step has finished.
|
||||
|
||||
This method is an override point for subclasses, called by the base class when
|
||||
the step has just finished.
|
||||
|
||||
The default implementation does nothing except in the case of steps that have countdown
|
||||
enabled. When countdown is enabled in a step, the view controller attempts to navigate automatically to the next step, if so configured.
|
||||
*/
|
||||
- (void)stepDidFinish;
|
||||
|
||||
/**
|
||||
A Boolean value that indicates whether to suspend the step if the app is not
|
||||
active or the screen is off.
|
||||
|
||||
Active steps that require the screen in order to work should suspend
|
||||
recording when the app goes into the background. Other active steps require
|
||||
the step to continue while the app is in the background. For example, the fitness check
|
||||
active step continues to collect data while the screen is off, and continues
|
||||
to give voice prompts.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL suspendIfInactive;
|
||||
|
||||
/**
|
||||
Starts the active step.
|
||||
|
||||
Call this method to start the timer on the active step, if there is one, and
|
||||
to start any data recording.
|
||||
|
||||
When you start the step, recorders are instantiated based on their configurations and then started. All
|
||||
timers should start, and the UI should show users that the step is in progress.
|
||||
|
||||
This method does nothing if the step has already started.
|
||||
|
||||
Subclasses should super when overriding this method.
|
||||
*/
|
||||
- (void)start;
|
||||
|
||||
/**
|
||||
Suspends the active step.
|
||||
|
||||
Call this method to suspend data recording and the step's timer.
|
||||
|
||||
This method may called automatically when the app is suspended.
|
||||
The view controller can be configured not to suspend even if the app
|
||||
goes into the background (for more information, see `suspendIfInactive`).
|
||||
|
||||
Subclasses should call super when overriding this method.
|
||||
*/
|
||||
- (void)suspend;
|
||||
|
||||
/**
|
||||
Resumes the active step.
|
||||
|
||||
Call this method when the step should be resumed. Calls to this method should
|
||||
be paired with previous calls to `suspend`.
|
||||
|
||||
This method may be called automatically when the app is resumed. The view
|
||||
controller can be configured not to suspend even if the app
|
||||
goes into the background (for more information, see `suspendIfInactive`).
|
||||
|
||||
When the step is resumed, the UI should resume at the point where the user left off,
|
||||
or, if that does not make sense for the particular step, to the most recent
|
||||
suitable point.
|
||||
|
||||
Subclasses should call super when overriding this method.
|
||||
*/
|
||||
- (void)resume;
|
||||
|
||||
/**
|
||||
Finishes the active step.
|
||||
|
||||
Call this method to finish the active step. If the active step is configured with
|
||||
a timer, this method is called automatically when the timer expires.
|
||||
|
||||
Finishing the active step stops all data recording and stops any timers. In steps that have the
|
||||
`shouldContinueOnFinish` property set, forward navigation to the next step
|
||||
may ensue.
|
||||
|
||||
This method does nothing if the step has already finished.
|
||||
|
||||
Subclasses should call super when overriding this method.
|
||||
*/
|
||||
- (void)finish;
|
||||
|
||||
/// @name Recorder life cycle
|
||||
|
||||
/**
|
||||
Tells the view controller that the set of recorders changed.
|
||||
|
||||
This method is usually called by the active step view controller in response
|
||||
to `start` or `resume`.
|
||||
|
||||
Subclasses may override this method.
|
||||
*/
|
||||
- (void)recordersDidChange;
|
||||
|
||||
/**
|
||||
Tells the view controller that the recorders are about to start.
|
||||
|
||||
This method is called by the active step view controller after instantiating
|
||||
the recorders, but before starting them.
|
||||
|
||||
Subclasses may override this method.
|
||||
*/
|
||||
- (void)recordersWillStart;
|
||||
|
||||
/**
|
||||
Tells the view controller that the recorders are about to stop.
|
||||
|
||||
This method is called by the active step view controller before
|
||||
stopping the recorders.
|
||||
|
||||
Subclasses may override this method.
|
||||
*/
|
||||
- (void)recordersWillStop;
|
||||
|
||||
/**
|
||||
Tells the view controller that the step has been loaded and is about to start.
|
||||
|
||||
This method is called by the active step view controller just after the step
|
||||
has been set. The base implementation instantiates the recorders and timer but
|
||||
does not start them.
|
||||
|
||||
Subclasses may override this method, but must also call super.
|
||||
*/
|
||||
- (void)prepareStep;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,505 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStepViewController.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKActiveStepTimerView.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKNavigationContainerView.h"
|
||||
#import "ORKStepHeaderView_Internal.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
#import "ORKVoiceEngine.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKTaskViewController_Internal.h"
|
||||
#import "ORKRecorder_Internal.h"
|
||||
|
||||
#import "ORKActiveStep_Internal.h"
|
||||
#import "ORKResult.h"
|
||||
#import "ORKTask.h"
|
||||
|
||||
#import "ORKAccessibility.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@interface ORKActiveStepViewController () {
|
||||
ORKActiveStepView *_activeStepView;
|
||||
ORKActiveStepTimer *_activeStepTimer;
|
||||
|
||||
NSArray *_recorderResults;
|
||||
|
||||
SystemSoundID _alertSound;
|
||||
NSURL *_alertSoundURL;
|
||||
BOOL _hasSpokenHalfwayCountdown;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) NSArray *recorders;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKActiveStepViewController
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
_recorderResults = [NSArray new];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
_timerUpdateInterval = 1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
if (self.suspendIfInactive) {
|
||||
[self suspend];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
if (self.suspendIfInactive) {
|
||||
[self resume];
|
||||
}
|
||||
}
|
||||
|
||||
- (ORKActiveStep *)activeStep {
|
||||
NSAssert(self.step == nil || [self.step isKindOfClass:[ORKActiveStep class]], @"Step should be a subclass of an ORKActiveStep");
|
||||
return (ORKActiveStep *)self.step;
|
||||
}
|
||||
|
||||
- (ORKActiveStepView *)activeStepView {
|
||||
return _activeStepView;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
_activeStepView = [[ORKActiveStepView alloc] initWithFrame:self.view.bounds];
|
||||
_activeStepView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[_activeStepView setCustomView:_customView];
|
||||
[self updateContinueButtonItem];
|
||||
_activeStepView.headerView.learnMoreButtonItem = self.learnMoreButtonItem;
|
||||
_activeStepView.continueSkipContainer.skipButtonItem = self.skipButtonItem;
|
||||
_activeStepView.continueSkipContainer.continueEnabled = _finished;
|
||||
[self.view addSubview:_activeStepView];
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[activeStepView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:@{@"activeStepView": _activeStepView}]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide][activeStepView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:@{@"activeStepView": _activeStepView,
|
||||
@"topLayoutGuide": self.topLayoutGuide}]];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
|
||||
[self prepareStep];
|
||||
}
|
||||
|
||||
- (void)stepDidChange {
|
||||
[super stepDidChange];
|
||||
_activeStepView.activeStep = [self activeStep];
|
||||
[self updateContinueButtonItem];
|
||||
|
||||
|
||||
[self prepareStep];
|
||||
}
|
||||
|
||||
- (UIView *)customViewContainer {
|
||||
__unused UIView *view = [self view];
|
||||
return _activeStepView.customViewContainer;
|
||||
}
|
||||
|
||||
- (ORKTintedImageView *)imageView {
|
||||
__unused UIView *view = [self view];
|
||||
return _activeStepView.imageView;
|
||||
}
|
||||
|
||||
- (void)setCustomView:(UIView *)customView {
|
||||
_customView = customView;
|
||||
[_activeStepView setStepView:_customView];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
|
||||
[self.taskViewController setRegisteredScrollView:_activeStepView];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
|
||||
// Wait for animation complete
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if(self.started){
|
||||
// Should call resume instead of start when the task has been started.
|
||||
[self resume];
|
||||
} else if ([[self activeStep] shouldStartTimerAutomatically]) {
|
||||
[self start];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
|
||||
[self suspend];
|
||||
}
|
||||
|
||||
- (void)updateContinueButtonItem {
|
||||
_activeStepView.continueSkipContainer.continueButtonItem = self.continueButtonItem;
|
||||
}
|
||||
|
||||
- (void)setContinueButtonItem:(UIBarButtonItem *)continueButtonItem {
|
||||
[super setContinueButtonItem:continueButtonItem];
|
||||
[self updateContinueButtonItem];
|
||||
}
|
||||
|
||||
- (void)setLearnMoreButtonItem:(UIBarButtonItem *)learnMoreButtonItem {
|
||||
[super setLearnMoreButtonItem:learnMoreButtonItem];
|
||||
_activeStepView.headerView.learnMoreButtonItem = self.learnMoreButtonItem;
|
||||
}
|
||||
|
||||
- (void)setSkipButtonItem:(UIBarButtonItem *)skipButtonItem {
|
||||
[super setSkipButtonItem:skipButtonItem];
|
||||
_activeStepView.continueSkipContainer.skipButtonItem = skipButtonItem;
|
||||
}
|
||||
|
||||
- (void)setFinished:(BOOL)finished {
|
||||
_finished = finished;
|
||||
_activeStepView.continueSkipContainer.continueEnabled = finished;
|
||||
}
|
||||
|
||||
- (ORKStepResult *)result {
|
||||
ORKStepResult *sResult = [super result];
|
||||
if (_recorderResults) {
|
||||
sResult.results = [sResult.results arrayByAddingObjectsFromArray:_recorderResults] ? : _recorderResults;
|
||||
}
|
||||
return sResult;
|
||||
}
|
||||
|
||||
#pragma mark - transition
|
||||
|
||||
- (void)recordersDidChange {
|
||||
}
|
||||
|
||||
- (void)recordersWillStart {
|
||||
}
|
||||
|
||||
- (void)recordersWillStop {
|
||||
}
|
||||
|
||||
- (void)prepareRecorders {
|
||||
// Stop any existing recorders
|
||||
[self recordersWillStop];
|
||||
for (ORKRecorder *recorder in self.recorders) {
|
||||
recorder.delegate = nil;
|
||||
[recorder stop];
|
||||
}
|
||||
NSMutableArray *recorders = [NSMutableArray array];
|
||||
|
||||
for (ORKRecorderConfiguration * provider in self.activeStep.recorderConfigurations) {
|
||||
// If the outputDirectory is nil, recorders which require one will generate an error.
|
||||
// We start them anyway, because we don't know which recorders will require an outputDirectory.
|
||||
ORKRecorder *recorder = [provider recorderForStep:self.step
|
||||
outputDirectory:self.outputDirectory];
|
||||
recorder.configuration = provider;
|
||||
recorder.delegate = self;
|
||||
|
||||
[recorders addObject:recorder];
|
||||
}
|
||||
self.recorders = recorders;
|
||||
|
||||
[self recordersDidChange];
|
||||
}
|
||||
|
||||
- (void)setOutputDirectory:(NSURL *)outputDirectory {
|
||||
[super setOutputDirectory:outputDirectory];
|
||||
[self prepareStep];
|
||||
}
|
||||
|
||||
- (void)prepareStep {
|
||||
if (self.activeStep == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.finished = [[self activeStep] startsFinished];
|
||||
|
||||
ORK_Log_Debug(@"%@", self);
|
||||
_activeStepView.activeStep = self.activeStep;
|
||||
|
||||
if ([self.activeStep hasCountDown]) {
|
||||
ORKActiveStepTimerView *timerView = [ORKActiveStepTimerView new];
|
||||
_activeStepView.activeCustomView = timerView;
|
||||
} else {
|
||||
_activeStepView.activeCustomView = nil;
|
||||
}
|
||||
_activeStepView.activeCustomView.activeStepViewController = self;
|
||||
[_activeStepView.activeCustomView resetStep:self];
|
||||
[self resetTimer];
|
||||
|
||||
[self prepareRecorders];
|
||||
}
|
||||
|
||||
- (void)startRecorders {
|
||||
[self recordersWillStart];
|
||||
// Start recorders
|
||||
for (ORKRecorder *recorder in self.recorders) {
|
||||
[recorder viewController:self willStartStepWithView:self.customViewContainer];
|
||||
[recorder start];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopRecorders {
|
||||
[self recordersWillStop];
|
||||
for (ORKRecorder *recorder in self.recorders) {
|
||||
[recorder stop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)playSound {
|
||||
if (_alertSoundURL == nil) {
|
||||
_alertSoundURL = [NSURL URLWithString:@"/System/Library/Audio/UISounds/short_low_high.caf"];
|
||||
AudioServicesCreateSystemSoundID((__bridge CFURLRef)(_alertSoundURL), &_alertSound);
|
||||
}
|
||||
AudioServicesPlaySystemSound(_alertSound);
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
self.started = YES;
|
||||
[self startTimer];
|
||||
[_activeStepView.activeCustomView startStep:self];
|
||||
|
||||
[self startRecorders];
|
||||
|
||||
if (self.activeStep.shouldVibrateOnStart) {
|
||||
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
|
||||
if (self.activeStep.shouldPlaySoundOnStart) {
|
||||
[self playSound];
|
||||
}
|
||||
|
||||
// Start speech
|
||||
if (self.activeStep.hasVoice && self.activeStep.spokenInstruction) {
|
||||
// Let VO speak "Step x of y" before the instruction.
|
||||
// If VO is not running, the text is spoken immediately.
|
||||
ORKAccessibilityPerformBlockAfterDelay(1.5, ^{
|
||||
[[ORKVoiceEngine sharedVoiceEngine] speakText:self.activeStep.spokenInstruction];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)suspend {
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
if (self.finished || !self.started) {
|
||||
return;
|
||||
}
|
||||
|
||||
[_activeStepTimer pause];
|
||||
[_activeStepView.activeCustomView suspendStep:self];
|
||||
|
||||
[self stopRecorders];
|
||||
}
|
||||
|
||||
- (void)resume {
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
if (self.finished || !self.started) {
|
||||
return;
|
||||
}
|
||||
|
||||
[_activeStepTimer resume];
|
||||
[self prepareRecorders];
|
||||
[self startRecorders];
|
||||
[_activeStepView.activeCustomView resumeStep:self];
|
||||
}
|
||||
|
||||
- (void)finish {
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
if (self.finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.finished = YES;
|
||||
[_activeStepTimer pause];
|
||||
[_activeStepView.activeCustomView finishStep:self];
|
||||
[self stopRecorders];
|
||||
if (self.activeStep.shouldPlaySoundOnFinish) {
|
||||
[self playSound];
|
||||
}
|
||||
if (self.activeStep.shouldVibrateOnFinish) {
|
||||
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
if (self.activeStep.hasVoice && self.activeStep.finishedSpokenInstruction) {
|
||||
[[ORKVoiceEngine sharedVoiceEngine] speakText:self.activeStep.finishedSpokenInstruction];
|
||||
}
|
||||
if (!self.activeStep.startsFinished) {
|
||||
if (self.activeStep.shouldContinueOnFinish) {
|
||||
[self goForward];
|
||||
}
|
||||
}
|
||||
[self stepDidFinish];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
AudioServicesDisposeSystemSoundID(_alertSound);
|
||||
NSNotificationCenter *nfc = [NSNotificationCenter defaultCenter];
|
||||
[nfc removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
|
||||
[nfc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
}
|
||||
|
||||
#pragma mark - timers
|
||||
|
||||
- (void)resetTimer {
|
||||
[_activeStepTimer reset];
|
||||
_activeStepTimer = nil;
|
||||
}
|
||||
|
||||
- (void)startTimer {
|
||||
[self resetTimer];
|
||||
|
||||
NSTimeInterval stepDuration = self.activeStep.stepDuration;
|
||||
|
||||
if (stepDuration > 0) {
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
_activeStepTimer = [[ORKActiveStepTimer alloc] initWithDuration:stepDuration
|
||||
interval:_timerUpdateInterval
|
||||
runtime:0
|
||||
handler:^(ORKActiveStepTimer *timer, BOOL finished) {
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf countDownTimerFired:timer finished:finished];
|
||||
}];
|
||||
[_activeStepTimer resume];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished {
|
||||
if (finished) {
|
||||
[self finish];
|
||||
}
|
||||
NSInteger countDownValue = (NSInteger)round(timer.duration - timer.runtime);
|
||||
ORKActiveStepCustomView *customView = _activeStepView.activeCustomView;
|
||||
[customView updateDisplay:self];
|
||||
|
||||
|
||||
ORKVoiceEngine *voice = [ORKVoiceEngine sharedVoiceEngine];
|
||||
|
||||
if (!finished && self.activeStep.shouldSpeakCountDown) {
|
||||
// Speak entire countdown if VO is running.
|
||||
if (UIAccessibilityIsVoiceOverRunning()) {
|
||||
[voice speakInt:countDownValue];
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 < countDownValue && countDownValue <= 3) {
|
||||
[voice speakInt:countDownValue];
|
||||
}
|
||||
}
|
||||
|
||||
BOOL isHalfway = !_hasSpokenHalfwayCountdown && timer.runtime > timer.duration / 2.0;
|
||||
if (!finished && self.activeStep.shouldSpeakRemainingTimeAtHalfway && !UIAccessibilityIsVoiceOverRunning() && isHalfway) {
|
||||
_hasSpokenHalfwayCountdown = YES;
|
||||
NSString *text = [NSString localizedStringWithFormat:ORKLocalizedString(@"COUNTDOWN_SPOKEN_REMAINING_%@", nil), @(countDownValue)];
|
||||
[voice speakText:text];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)timerActive {
|
||||
return (_activeStepTimer != nil);
|
||||
}
|
||||
|
||||
- (NSTimeInterval)timeRemaining {
|
||||
if (_activeStepTimer == nil) {
|
||||
return self.activeStep.stepDuration;
|
||||
}
|
||||
return _activeStepTimer.duration - _activeStepTimer.runtime;
|
||||
}
|
||||
|
||||
#pragma mark - action handlers
|
||||
|
||||
- (void)stepDidFinish {
|
||||
}
|
||||
|
||||
#pragma mark - ORKRecorderDelegate
|
||||
|
||||
- (void)recorder:(ORKRecorder *)recorder didCompleteWithResult:(ORKResult *)result {
|
||||
_recorderResults = [_recorderResults arrayByAddingObject:result];
|
||||
[self notifyDelegateOnResultChange];
|
||||
}
|
||||
|
||||
- (void)recorder:(ORKRecorder *)recorder didFailWithError:(NSError *)error {
|
||||
if (error) {
|
||||
ORKStrongTypeOf(self.delegate) strongDelegate = self.delegate;
|
||||
if ([strongDelegate respondsToSelector:@selector(stepViewController:recorder:didFailWithError:)]) {
|
||||
[strongDelegate stepViewController:self recorder:recorder didFailWithError:error];
|
||||
}
|
||||
|
||||
// If the recorder returns an error indicating that file write failed, and the output directory was nil,
|
||||
// we consider it a fatal error and fail the step. Otherwise, developers might be confused to get
|
||||
// no output, just because they did not set an output directory.
|
||||
if ([error.domain isEqualToString:NSCocoaErrorDomain] &&
|
||||
error.code == NSFileWriteInvalidFileNameError &&
|
||||
self.outputDirectory == nil) {
|
||||
[strongDelegate stepViewControllerDidFail:self withError:error];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static NSString *const _ORKFinishedRestoreKey = @"finished";
|
||||
static NSString *const _ORKRecorderResultsRestoreKey = @"recorderResults";
|
||||
|
||||
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
|
||||
[super encodeRestorableStateWithCoder:coder];
|
||||
|
||||
[coder encodeBool:_finished forKey:_ORKFinishedRestoreKey];
|
||||
[coder encodeObject:_recorderResults forKey:_ORKRecorderResultsRestoreKey];
|
||||
}
|
||||
|
||||
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
|
||||
[super decodeRestorableStateWithCoder:coder];
|
||||
|
||||
self.finished = [coder decodeBoolForKey:_ORKFinishedRestoreKey];
|
||||
_recorderResults = [coder decodeObjectOfClass:[NSArray class] forKey:_ORKRecorderResultsRestoreKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStepViewController.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKActiveStepTimer;
|
||||
@class ORKActiveStepView;
|
||||
|
||||
@interface ORKActiveStepViewController ()
|
||||
|
||||
/**
|
||||
The customViewContainer allows custom view to be its subview.
|
||||
|
||||
@note When ORKTouchRecorder is present, its gesture recognizer attaches to customViewContainer.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) UIView *customViewContainer;
|
||||
|
||||
@property (nonatomic, strong, readonly, nullable) ORKActiveStepView *activeStepView;
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval timeRemaining;
|
||||
@property (nonatomic, readonly) BOOL timerActive;
|
||||
@property (nonatomic, assign) NSTimeInterval timerUpdateInterval;
|
||||
|
||||
@property (nonatomic, assign, getter=isStarted) BOOL started;
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished; // Let subclass receive timer fires
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification;
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification;
|
||||
|
||||
- (void)stopRecorders;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStep.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKActiveStep ()
|
||||
|
||||
// Convenience methods.
|
||||
- (BOOL)startsFinished;
|
||||
- (BOOL)hasCountDown;
|
||||
- (BOOL)hasTitle;
|
||||
- (BOOL)hasText;
|
||||
- (BOOL)hasVoice;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKAudioContentView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, copy, nullable) UIColor *keyColor;
|
||||
@property (nonatomic, copy, nullable) UIColor *alertColor;
|
||||
|
||||
@property (nonatomic, assign) BOOL failed;
|
||||
@property (nonatomic, assign, getter=isFinished) BOOL finished;
|
||||
@property (nonatomic, assign) NSTimeInterval timeLeft;
|
||||
|
||||
@property (nonatomic, assign) CGFloat alertThreshold;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSArray *samples;
|
||||
|
||||
// Samples should be in the range of (0, 1).
|
||||
- (void)addSample:(NSNumber *)sample;
|
||||
- (void)removeAllSamples;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,402 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioContentView.h"
|
||||
|
||||
#import "ORKHeadlineLabel.h"
|
||||
#import "ORKLabel.h"
|
||||
|
||||
#import "ORKAccessibility.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
// The central blue region.
|
||||
static const CGFloat GraphViewBlueZoneHeight = 170;
|
||||
|
||||
// The two bands at top and bottom which are "loud" each have this height.
|
||||
static const CGFloat GraphViewRedZoneHeight = 25;
|
||||
|
||||
|
||||
@interface ORKAudioGraphView : UIView
|
||||
|
||||
@property (nonatomic, strong) UIColor *keyColor;
|
||||
@property (nonatomic, strong) UIColor *alertColor;
|
||||
|
||||
@property (nonatomic, copy) NSArray *values;
|
||||
|
||||
@property (nonatomic) CGFloat alertThreshold;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
static const CGFloat ValueLineWidth = 4.5;
|
||||
static const CGFloat ValueLineMargin = 1.5;
|
||||
|
||||
|
||||
@implementation ORKAudioGraphView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setUpConstraints];
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
_values = @[ @(0.2), @(0.6), @(0.55), @(0.1), @(0.75), @(0.7) ];
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
|
||||
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:CGFLOAT_MAX];
|
||||
heightConstraint.priority = UILayoutPriorityFittingSizeLevel;
|
||||
|
||||
[NSLayoutConstraint activateConstraints:@[heightConstraint]];
|
||||
}
|
||||
|
||||
- (void)setValues:(NSArray *)values {
|
||||
_values = [values copy];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setKeyColor:(UIColor *)keyColor {
|
||||
_keyColor = [keyColor copy];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setAlertColor:(UIColor *)alertColor {
|
||||
_alertColor = [alertColor copy];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setAlertThreshold:(CGFloat)alertThreshold {
|
||||
_alertThreshold = alertThreshold;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGRect bounds = self.bounds;
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
|
||||
CGContextFillRect(context, bounds);
|
||||
|
||||
CGFloat scale = self.window.screen.scale;
|
||||
|
||||
CGFloat midY = CGRectGetMidY(bounds);
|
||||
CGFloat maxX = CGRectGetMaxX(bounds);
|
||||
CGFloat halfHeight = bounds.size.height / 2;
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
UIBezierPath *centerLine = [UIBezierPath new];
|
||||
[centerLine moveToPoint:(CGPoint){.x = 0, .y = midY}];
|
||||
[centerLine addLineToPoint:(CGPoint){.x = maxX, .y = midY}];
|
||||
|
||||
CGContextSetLineWidth(context, 1.0 / scale);
|
||||
[_keyColor setStroke];
|
||||
CGFloat lengths[2] = {3, 3};
|
||||
CGContextSetLineDash(context, 0, lengths, 2);
|
||||
|
||||
[centerLine stroke];
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
CGFloat lineStep = ValueLineMargin + ValueLineWidth;
|
||||
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
CGFloat x = maxX - lineStep / 2;
|
||||
CGContextSetLineWidth(context, ValueLineWidth);
|
||||
CGContextSetLineCap(context, kCGLineCapRound);
|
||||
|
||||
UIBezierPath *path1 = [UIBezierPath new];
|
||||
path1.lineCapStyle = kCGLineCapRound;
|
||||
path1.lineWidth = ValueLineWidth;
|
||||
UIBezierPath *path2 = [path1 copy];
|
||||
|
||||
for (NSNumber *value in [_values reverseObjectEnumerator]) {
|
||||
CGFloat floatValue = value.doubleValue;
|
||||
|
||||
UIBezierPath *path = nil;
|
||||
if (floatValue > _alertThreshold) {
|
||||
path = path1;
|
||||
[_alertColor setStroke];
|
||||
} else {
|
||||
path = path2;
|
||||
[_keyColor setStroke];
|
||||
}
|
||||
[path moveToPoint:(CGPoint){.x = x, .y = midY - floatValue*halfHeight}];
|
||||
[path addLineToPoint:(CGPoint){.x = x, .y = midY + floatValue*halfHeight}];
|
||||
|
||||
x -= lineStep;
|
||||
|
||||
if (x < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[_alertColor setStroke];
|
||||
[path1 stroke];
|
||||
|
||||
[_keyColor setStroke];
|
||||
[path2 stroke];
|
||||
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKAudioTimerLabel : ORKLabel
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioTimerLabel
|
||||
|
||||
+ (UIFont *)defaultFont {
|
||||
UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];
|
||||
UIFontDescriptor *alternativeDescriptor = ORKFontDescriptorForLightStylisticAlternative(descriptor);
|
||||
return [UIFont fontWithDescriptor:alternativeDescriptor size:[alternativeDescriptor pointSize] + 4];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKAudioContentView ()
|
||||
|
||||
@property (nonatomic, strong) ORKHeadlineLabel *alertLabel;
|
||||
@property (nonatomic, strong) UILabel *timerLabel;
|
||||
@property (nonatomic, strong) ORKAudioGraphView *graphView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioContentView {
|
||||
NSMutableArray *_samples;
|
||||
UIColor *_keyColor;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.layoutMargins = ORKStandardFullScreenLayoutMarginsForView(self);
|
||||
|
||||
self.alertLabel = [ORKHeadlineLabel new];
|
||||
_alertLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.timerLabel = [ORKAudioTimerLabel new];
|
||||
_timerLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_timerLabel.textAlignment = NSTextAlignmentRight;
|
||||
self.graphView = [ORKAudioGraphView new];
|
||||
_graphView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
self.alertColor = [UIColor ork_redColor];
|
||||
|
||||
[self addSubview:_alertLabel];
|
||||
[self addSubview:_timerLabel];
|
||||
[self addSubview:_graphView];
|
||||
|
||||
_timerLabel.text = @"06:00";
|
||||
_alertLabel.text = ORKLocalizedString(@"AUDIO_TOO_LOUD_LABEL", nil);
|
||||
|
||||
self.alertThreshold = GraphViewBlueZoneHeight / ((GraphViewRedZoneHeight * 2) + GraphViewBlueZoneHeight);
|
||||
|
||||
[self updateGraphSamples];
|
||||
[self applyKeyColor];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[self applyKeyColor];
|
||||
}
|
||||
|
||||
- (void)setFailed:(BOOL)failed {
|
||||
_failed = failed;
|
||||
_alertLabel.text = failed ? ORKLocalizedString(@"AUDIO_GENERIC_ERROR_LABEL", nil) : ORKLocalizedString(@"AUDIO_TOO_LOUD_LABEL", nil);
|
||||
[self updateAlertLabelHidden];
|
||||
}
|
||||
|
||||
- (void)setFinished:(BOOL)finished {
|
||||
_finished = finished;
|
||||
[self updateAlertLabelHidden];
|
||||
}
|
||||
|
||||
- (void)applyKeyColor {
|
||||
UIColor *keyColor = [self keyColor];
|
||||
_timerLabel.textColor = keyColor;
|
||||
_graphView.keyColor = keyColor;
|
||||
}
|
||||
|
||||
- (UIColor *)keyColor {
|
||||
return _keyColor ? : [self tintColor];
|
||||
}
|
||||
|
||||
- (void)setKeyColor:(UIColor *)keyColor {
|
||||
_keyColor = keyColor;
|
||||
[self applyKeyColor];
|
||||
}
|
||||
|
||||
- (void)setAlertColor:(UIColor *)alertColor {
|
||||
_alertColor = alertColor;
|
||||
_alertLabel.textColor = alertColor;
|
||||
_graphView.alertColor = alertColor;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_timerLabel, _alertLabel, _graphView);
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_graphView]-[_alertLabel]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_alertLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
const CGFloat sideMargin = self.layoutMargins.left + (2 * ORKStandardLeftMarginForTableViewCell(self));
|
||||
const CGFloat innerMargin = 2;
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-sideMargin-[_graphView]-innerMargin-[_timerLabel]-sideMargin-|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:@{@"sideMargin": @(sideMargin), @"innerMargin": @(innerMargin)}
|
||||
views:views]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_graphView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:(GraphViewBlueZoneHeight + GraphViewRedZoneHeight * 2)]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)setAlertThreshold:(CGFloat)alertThreshold {
|
||||
_alertThreshold = alertThreshold;
|
||||
_graphView.alertThreshold = alertThreshold;
|
||||
[self updateGraphSamples];
|
||||
}
|
||||
|
||||
- (void)setTimeLeft:(NSTimeInterval)timeLeft {
|
||||
_timeLeft = timeLeft;
|
||||
[self updateTimerLabel];
|
||||
}
|
||||
|
||||
- (void)updateTimerLabel {
|
||||
static NSDateComponentsFormatter *formatter = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSDateComponentsFormatter *formatter = [NSDateComponentsFormatter new];
|
||||
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
|
||||
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
|
||||
formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond;
|
||||
});
|
||||
|
||||
NSString *string = [formatter stringFromTimeInterval:MAX(round(_timeLeft),0)];
|
||||
_timerLabel.text = string;
|
||||
_timerLabel.hidden = (string == nil);
|
||||
}
|
||||
|
||||
- (void)updateGraphSamples {
|
||||
_graphView.values = _samples;
|
||||
[self updateAlertLabelHidden];
|
||||
}
|
||||
|
||||
- (void)updateAlertLabelHidden {
|
||||
NSNumber *sample = _samples.lastObject;
|
||||
BOOL show = (!_finished && (sample.doubleValue > _alertThreshold)) || _failed;
|
||||
|
||||
if (_alertLabel.hidden && show) {
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, _alertLabel.text);
|
||||
}
|
||||
_alertLabel.hidden = !show;
|
||||
}
|
||||
|
||||
- (void)setSamples:(NSArray *)samples {
|
||||
_samples = [samples mutableCopy];
|
||||
[self updateGraphSamples];
|
||||
}
|
||||
|
||||
- (void)addSample:(NSNumber *)sample {
|
||||
NSAssert(sample != nil, @"Sample should be non-nil");
|
||||
if (!_samples) {
|
||||
_samples = [NSMutableArray array];
|
||||
}
|
||||
[_samples addObject:sample];
|
||||
// Try to keep around 250 samples
|
||||
if (_samples.count > 500) {
|
||||
_samples = [[_samples subarrayWithRange:(NSRange){250, _samples.count - 250}] mutableCopy];
|
||||
}
|
||||
[self updateGraphSamples];
|
||||
}
|
||||
|
||||
- (void)removeAllSamples {
|
||||
_samples = nil;
|
||||
[self updateGraphSamples];
|
||||
}
|
||||
|
||||
#pragma mark Accessibility
|
||||
|
||||
- (BOOL)isAccessibilityElement {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel {
|
||||
NSString *timerAxString = _timerLabel.isHidden ? nil : _timerLabel.accessibilityLabel;
|
||||
NSString *alertAxString = _alertLabel.isHidden ? nil : _alertLabel.accessibilityLabel;
|
||||
return ORKAccessibilityStringForVariables(ORKLocalizedString(@"AX_AUDIO_BAR_GRAPH", nil), timerAxString, alertAxString);
|
||||
}
|
||||
|
||||
- (UIAccessibilityTraits)accessibilityTraits {
|
||||
return [super accessibilityTraits] | UIAccessibilityTraitUpdatesFrequently;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
|
||||
This software is based on the original source by Matt Gallagher.
|
||||
http://www.cocoawithlove.com/2010/10/ios-tone-generator-introduction-to.html
|
||||
|
||||
Copyright (c) 2009-2011 Matt Gallagher. All rights reserved.
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In
|
||||
no event will the authors be held liable for any damages arising from the use
|
||||
of this software. Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim
|
||||
that you wrote the original software. If you use this software in a product,
|
||||
an acknowledgment in the product documentation would be appreciated but is
|
||||
not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
@import AVFoundation;
|
||||
#import "ORKTypes.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKAudioGenerator` class represents an audio tone generator.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudioGenerator : NSObject
|
||||
|
||||
/**
|
||||
Plays a tone at a specific frequency in stereo.
|
||||
|
||||
The sound is a "pure" sinusoid tone.
|
||||
|
||||
@param frequency The audio frequency in hertz.
|
||||
*/
|
||||
- (void)playSoundAtFrequency:(double)frequency;
|
||||
|
||||
/**
|
||||
Plays a tone at a specific frequency on a specific channel, with a fade-in effect.
|
||||
|
||||
The sound is a "pure" sinusoid tone.
|
||||
The fade-in effect is applied linearly for the peak amplitude, from a 0 to 1 factor.
|
||||
|
||||
@param frequency The audio frequency in hertz.
|
||||
@param channel The audio channel (left or right).
|
||||
@param duration The fade-in duration.
|
||||
*/
|
||||
- (void)playSoundAtFrequency:(double)frequency
|
||||
onChannel:(ORKAudioChannel)channel
|
||||
fadeInDuration:(NSTimeInterval)duration;
|
||||
|
||||
/**
|
||||
Stops the audio being played.
|
||||
*/
|
||||
- (void)stop;
|
||||
|
||||
/**
|
||||
Returns the peak audio volume being currently played, in decibels (dB).
|
||||
|
||||
@return The current audio volume in decibels.
|
||||
*/
|
||||
- (double)volumeInDecibels;
|
||||
|
||||
/**
|
||||
Returns the peak audio volume amplitude being currently played (from 0 to 1).
|
||||
|
||||
@return The current audio volume amplitude.
|
||||
*/
|
||||
- (double)volumeAmplitude;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,283 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
|
||||
This software is based on the original source by Matt Gallagher.
|
||||
http://www.cocoawithlove.com/2010/10/ios-tone-generator-introduction-to.html
|
||||
|
||||
Copyright (c) 2009-2011 Matt Gallagher. All rights reserved.
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In
|
||||
no event will the authors be held liable for any damages arising from the use
|
||||
of this software. Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim
|
||||
that you wrote the original software. If you use this software in a product,
|
||||
an acknowledgment in the product documentation would be appreciated but is
|
||||
not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioGenerator.h"
|
||||
|
||||
@import AudioToolbox;
|
||||
|
||||
|
||||
@interface ORKAudioGenerator () {
|
||||
@public
|
||||
AudioComponentInstance _toneUnit;
|
||||
double _frequency;
|
||||
double _theta;
|
||||
ORKAudioChannel _activeChannel;
|
||||
BOOL _playsStereo;
|
||||
double _fadeInFactor;
|
||||
NSTimeInterval _fadeInDuration;
|
||||
}
|
||||
|
||||
- (void)setupAudioSession;
|
||||
- (void)createToneUnit;
|
||||
- (void)play;
|
||||
- (void)handleInterruption:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
const double ORKSineWaveToneGeneratorAmplitudeDefault = 0.03f;
|
||||
const double ORKSineWaveToneGeneratorSampleRateDefault = 44100.0f;
|
||||
|
||||
OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) {
|
||||
// Fixed amplitude is good enough for our purposes
|
||||
const double amplitude = ORKSineWaveToneGeneratorAmplitudeDefault;
|
||||
|
||||
// Get the tone parameters out of the view controller
|
||||
ORKAudioGenerator *audioGenerator = (__bridge ORKAudioGenerator *)inRefCon;
|
||||
double theta = audioGenerator->_theta;
|
||||
double theta_increment = 2.0 * M_PI * audioGenerator->_frequency / ORKSineWaveToneGeneratorSampleRateDefault;
|
||||
|
||||
double fadeInFactor = audioGenerator->_fadeInFactor;
|
||||
|
||||
// This is a mono tone generator so we only need the first buffer
|
||||
Float32 *bufferActive = (Float32 *)ioData->mBuffers[audioGenerator->_activeChannel].mData;
|
||||
Float32 *bufferNonActive = (Float32 *)ioData->mBuffers[1 - audioGenerator->_activeChannel].mData;
|
||||
|
||||
// Generate the samples
|
||||
for (UInt32 frame = 0; frame < inNumberFrames; frame++) {
|
||||
double bufferValue = sin(theta) * amplitude * pow(10, 2 * fadeInFactor - 2);
|
||||
bufferActive[frame] = bufferValue;
|
||||
if (audioGenerator->_playsStereo) {
|
||||
bufferNonActive[frame] = bufferValue;
|
||||
} else {
|
||||
bufferNonActive[frame] = 0;
|
||||
}
|
||||
|
||||
theta += theta_increment;
|
||||
if (theta > 2.0 * M_PI) {
|
||||
theta -= 2.0 * M_PI;
|
||||
}
|
||||
|
||||
fadeInFactor += 1.0 / (ORKSineWaveToneGeneratorSampleRateDefault * audioGenerator->_fadeInDuration);
|
||||
if (fadeInFactor >= 1) {
|
||||
fadeInFactor = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the theta back in the view controller
|
||||
audioGenerator->_theta = theta;
|
||||
audioGenerator->_fadeInFactor = fadeInFactor;
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
@implementation ORKAudioGenerator
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self setupAudioSession];
|
||||
|
||||
// Automatically stop and then restart audio playback when the app resigns active.
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stop];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
if (_toneUnit) {
|
||||
__unused OSErr error = AudioOutputUnitStart(_toneUnit);
|
||||
NSAssert1(error == noErr, @"Error starting unit: %hd", error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
if (_toneUnit) {
|
||||
__unused OSErr error = AudioOutputUnitStop(_toneUnit);
|
||||
NSAssert1(error == noErr, @"Error stopping unit: %hd", error);
|
||||
}
|
||||
}
|
||||
|
||||
- (double)volumeInDecibels {
|
||||
return 20 * log(self.volumeAmplitude);
|
||||
}
|
||||
|
||||
- (double)volumeAmplitude {
|
||||
return ORKSineWaveToneGeneratorAmplitudeDefault * pow(10, 2 * _fadeInFactor - 2);
|
||||
}
|
||||
|
||||
- (void)playSoundAtFrequency:(double)playFrequency {
|
||||
_frequency = playFrequency;
|
||||
_fadeInFactor = 0;
|
||||
_fadeInDuration = 0.5;
|
||||
_playsStereo = YES;
|
||||
|
||||
[self play];
|
||||
}
|
||||
|
||||
- (void)playSoundAtFrequency:(double)playFrequency
|
||||
onChannel:(ORKAudioChannel)playChannel
|
||||
fadeInDuration:(NSTimeInterval)duration {
|
||||
_frequency = playFrequency;
|
||||
_activeChannel = playChannel;
|
||||
_fadeInFactor = 0;
|
||||
_fadeInDuration = duration;
|
||||
_playsStereo = NO;
|
||||
|
||||
[self play];
|
||||
}
|
||||
|
||||
- (void)play {
|
||||
if (!_toneUnit) {
|
||||
[self createToneUnit];
|
||||
|
||||
// Stop changing parameters on the unit
|
||||
OSErr error = AudioUnitInitialize(_toneUnit);
|
||||
NSAssert1(error == noErr, @"Error initializing unit: %hd", error);
|
||||
|
||||
// Start playback
|
||||
error = AudioOutputUnitStart(_toneUnit);
|
||||
NSAssert1(error == noErr, @"Error starting unit: %hd", error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (_toneUnit) {
|
||||
AudioOutputUnitStop(_toneUnit);
|
||||
AudioUnitUninitialize(_toneUnit);
|
||||
AudioComponentInstanceDispose(_toneUnit);
|
||||
_toneUnit = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupAudioSession {
|
||||
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
||||
BOOL ok;
|
||||
NSError *setCategoryError = nil;
|
||||
ok = [audioSession setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError];
|
||||
NSAssert1(ok, @"Audio error %@", setCategoryError);
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleInterruption:)
|
||||
name:AVAudioSessionInterruptionNotification
|
||||
object:audioSession];
|
||||
}
|
||||
|
||||
- (void)createToneUnit {
|
||||
// Configure the search parameters to find the default playback output unit
|
||||
// (called the kAudioUnitSubType_RemoteIO on iOS but
|
||||
// kAudioUnitSubType_DefaultOutput on Mac OS X)
|
||||
AudioComponentDescription defaultOutputDescription;
|
||||
defaultOutputDescription.componentType = kAudioUnitType_Output;
|
||||
defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
defaultOutputDescription.componentFlags = 0;
|
||||
defaultOutputDescription.componentFlagsMask = 0;
|
||||
|
||||
// Get the default playback output unit
|
||||
AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
|
||||
NSAssert(defaultOutput, @"Can't find default output");
|
||||
|
||||
// Create a new unit based on this that we'll use for output
|
||||
OSErr error = AudioComponentInstanceNew(defaultOutput, &_toneUnit);
|
||||
NSAssert1(_toneUnit, @"Error creating unit: %hd", error);
|
||||
|
||||
// Set our tone rendering function on the unit
|
||||
AURenderCallbackStruct input;
|
||||
input.inputProc = ORKAudioGeneratorRenderTone;
|
||||
input.inputProcRefCon = (__bridge void *)(self);
|
||||
error = AudioUnitSetProperty(_toneUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&input,
|
||||
sizeof(input));
|
||||
NSAssert1(error == noErr, @"Error setting callback: %hd", error);
|
||||
|
||||
// Set the format to 32 bit, single channel, floating point, linear PCM
|
||||
const int four_bytes_per_float = 4;
|
||||
const int eight_bits_per_byte = 8;
|
||||
AudioStreamBasicDescription streamFormat;
|
||||
streamFormat.mSampleRate = ORKSineWaveToneGeneratorSampleRateDefault;
|
||||
streamFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
streamFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
|
||||
streamFormat.mBytesPerPacket = four_bytes_per_float;
|
||||
streamFormat.mFramesPerPacket = 1;
|
||||
streamFormat.mBytesPerFrame = four_bytes_per_float;
|
||||
streamFormat.mChannelsPerFrame = 2;
|
||||
streamFormat.mBitsPerChannel = four_bytes_per_float * eight_bits_per_byte;
|
||||
error = AudioUnitSetProperty (_toneUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&streamFormat,
|
||||
sizeof(AudioStreamBasicDescription));
|
||||
NSAssert1(error == noErr, @"Error setting stream format: %hd", error);
|
||||
}
|
||||
|
||||
- (void)handleInterruption:(id)sender {
|
||||
[self stop];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2016, Sage Bionetworks
|
||||
Copyright (c) 2016, Apple Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKStepNavigationRule.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudioLevelNavigationRule : ORKStepNavigationRule
|
||||
|
||||
/*
|
||||
The `init` and `new` methods are unavailable.
|
||||
|
||||
`ORKStepNavigationRule` classes should be initialized with custom designated initializers on each
|
||||
subclass.
|
||||
*/
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Returns an initialized direct-step navigation rule using the specified destination step identifier.
|
||||
|
||||
@param audioLevelStepIdentifier The identifier of the step with the audio file to check.
|
||||
@param destinationStepIdentifier The identifier of the destination step if audio test passes.
|
||||
@param recordingSettings Use key AVNumberOfChannelsKey to sepcify the number of recording channels.
|
||||
@return A audio level step navigation rule.
|
||||
*/
|
||||
- (instancetype)initWithAudioLevelStepIdentifier:(NSString *)audioLevelStepIdentifier
|
||||
destinationStepIdentifier:(NSString *)destinationStepIdentifier
|
||||
recordingSettings:(NSDictionary *)recordingSettings NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new direct-step navigation rule initialized from data in a given unarchiver.
|
||||
|
||||
@param aDecoder The coder from which to initialize the step navigation rule.
|
||||
|
||||
@return A new direct-step navigation rule.
|
||||
*/
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *audioLevelStepIdentifier;
|
||||
@property (nonatomic, copy, readonly) NSString *destinationStepIdentifier;
|
||||
@property (nonatomic, copy, readonly) NSDictionary *recordingSettings;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2016, Sage Bionetworks
|
||||
Copyright (c) 2016, Apple Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioLevelNavigationRule.h"
|
||||
|
||||
#import "ORKResult.h"
|
||||
#import "ORKResultPredicate.h"
|
||||
#import "ORKStepNavigationRule_Internal.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
|
||||
Float32 const VolumeThreshold = 0.45;
|
||||
UInt16 const LinearPCMBitDepth = 16;
|
||||
Float32 const MaxAmplitude = 32767.0;
|
||||
Float32 const VolumeClamp = 60.0;
|
||||
|
||||
|
||||
@interface ORKAudioLevelNavigationRule ()
|
||||
|
||||
@property (nonatomic, copy, readwrite) NSString *audioLevelStepIdentifier;
|
||||
@property (nonatomic, copy, readwrite) NSString *destinationStepIdentifier;
|
||||
@property (nonatomic, copy, readwrite) NSDictionary *recordingSettings;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioLevelNavigationRule
|
||||
|
||||
+ (instancetype)new {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithAudioLevelStepIdentifier:(NSString *)audioLevelStepIdentifier
|
||||
destinationStepIdentifier:(NSString *)destinationStepIdentifier
|
||||
recordingSettings:(NSDictionary *)recordingSettings
|
||||
{
|
||||
ORKThrowInvalidArgumentExceptionIfNil(audioLevelStepIdentifier);
|
||||
ORKThrowInvalidArgumentExceptionIfNil(destinationStepIdentifier);
|
||||
ORKThrowInvalidArgumentExceptionIfNil(recordingSettings);
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_audioLevelStepIdentifier = [audioLevelStepIdentifier copy];
|
||||
_destinationStepIdentifier = [destinationStepIdentifier copy];
|
||||
_recordingSettings = [recordingSettings copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark NSSecureCoding
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, audioLevelStepIdentifier, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, destinationStepIdentifier, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, recordingSettings, NSDictionary);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_OBJ(aCoder, audioLevelStepIdentifier);
|
||||
ORK_ENCODE_OBJ(aCoder, destinationStepIdentifier);
|
||||
ORK_ENCODE_OBJ(aCoder, recordingSettings);
|
||||
}
|
||||
|
||||
#pragma mark NSCopying
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) rule = [[[self class] allocWithZone:zone] initWithAudioLevelStepIdentifier:self.audioLevelStepIdentifier destinationStepIdentifier:self.destinationStepIdentifier recordingSettings:self.recordingSettings];
|
||||
return rule;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame
|
||||
&& ORKEqualObjects(self.audioLevelStepIdentifier, castObject.audioLevelStepIdentifier)
|
||||
&& ORKEqualObjects(self.destinationStepIdentifier, castObject.destinationStepIdentifier)
|
||||
&& ORKEqualObjects(self.recordingSettings, castObject.recordingSettings));
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return _audioLevelStepIdentifier.hash ^ _destinationStepIdentifier.hash ^ _recordingSettings.hash;
|
||||
}
|
||||
|
||||
#pragma mark - Required overrides
|
||||
|
||||
- (NSString *)identifierForDestinationStepWithTaskResult:(ORKTaskResult *)taskResult {
|
||||
|
||||
// Get the result file
|
||||
ORKStepResult *stepResult = (ORKStepResult *)[taskResult resultForIdentifier:self.audioLevelStepIdentifier];
|
||||
ORKFileResult *audioLevelResult = (ORKFileResult *)[stepResult.results firstObject];
|
||||
|
||||
// Check the volume
|
||||
if ((audioLevelResult.fileURL != nil) && [self checkAudioLevelFromSoundFile:audioLevelResult.fileURL]) {
|
||||
// Returning nil will drop through to the next step (which should be the the step that has the instructions
|
||||
// for moving to a quieter room).
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self.destinationStepIdentifier;
|
||||
}
|
||||
|
||||
- (BOOL)checkAudioLevelFromSoundFile:(NSURL *)fileURL {
|
||||
// Setup reader
|
||||
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
|
||||
if (urlAsset.tracks.count == 0) {
|
||||
NSLog(@"No tracks found for urlAsset: %@", fileURL);
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:urlAsset error:&error];
|
||||
AVAssetTrack *track = [urlAsset.tracks objectAtIndex:0];
|
||||
NSDictionary *outputSettings = @{AVFormatIDKey: @(kAudioFormatLinearPCM),
|
||||
AVLinearPCMBitDepthKey: @(LinearPCMBitDepth),
|
||||
AVLinearPCMIsBigEndianKey: @(NO),
|
||||
AVLinearPCMIsFloatKey: @(NO),
|
||||
AVLinearPCMIsNonInterleaved: @(NO)};
|
||||
AVAssetReaderTrackOutput *output = [[AVAssetReaderTrackOutput alloc] initWithTrack:track outputSettings:outputSettings];
|
||||
[reader addOutput:output];
|
||||
|
||||
// Setup initial values - Assume 2 channels if not in recording settings
|
||||
const UInt32 channelCount = (UInt32)[self.recordingSettings[AVNumberOfChannelsKey] unsignedIntegerValue] ? : 2;
|
||||
const UInt32 bytesPerSample = 2 * channelCount;
|
||||
|
||||
// setup criteria block - Use a high-pass filter and a rolling average of the amplitude
|
||||
// normalized to be < 1
|
||||
__block Float32 rollingAvg = 0;
|
||||
__block UInt64 totalCount = 0;
|
||||
void (^processVolume)(Float32) = ^(Float32 amplitude) {
|
||||
if (amplitude != 0) {
|
||||
Float32 dB = 20 * log10(ABS(amplitude) / MaxAmplitude);
|
||||
float clampedValue = MAX(dB / VolumeClamp, -1) + 1;
|
||||
totalCount++;
|
||||
rollingAvg = (rollingAvg * (totalCount - 1) + clampedValue) / totalCount;
|
||||
}
|
||||
};
|
||||
|
||||
// While there are samples to read and the number of samples above the decibel threshold
|
||||
// is less than the total number of allowed samples over the limit, keep going
|
||||
[reader startReading];
|
||||
while (reader.status == AVAssetReaderStatusReading) {
|
||||
|
||||
AVAssetReaderTrackOutput *trackOutput = (AVAssetReaderTrackOutput *)[reader.outputs objectAtIndex:0];
|
||||
CMSampleBufferRef sampleBufferRef = [trackOutput copyNextSampleBuffer];
|
||||
|
||||
if (sampleBufferRef) {
|
||||
CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef);
|
||||
size_t length = CMBlockBufferGetDataLength(blockBufferRef);
|
||||
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, data.mutableBytes);
|
||||
|
||||
SInt16 *samples = (SInt16 *) data.mutableBytes;
|
||||
UInt64 sampleCount = length / bytesPerSample;
|
||||
for (UInt32 i = 0; i < sampleCount ; i++) {
|
||||
Float32 left = (Float32) *samples++;
|
||||
processVolume(left);
|
||||
if (channelCount == 2) {
|
||||
Float32 right = (Float32) *samples++;
|
||||
processVolume(right);
|
||||
}
|
||||
}
|
||||
|
||||
CMSampleBufferInvalidate(sampleBufferRef);
|
||||
CFRelease(sampleBufferRef);
|
||||
}
|
||||
}
|
||||
|
||||
return rollingAvg > VolumeThreshold;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
@import AVFoundation;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The ORKAudioRecorder class represents a recorder that uses the app's
|
||||
`AVAudioSession` object to record audio.
|
||||
|
||||
To ensure audio recording continues when a task enters the background,
|
||||
add the `audio` tag to `UIBackgroundModes` in your app's `Info.plist` file.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudioRecorder : ORKRecorder
|
||||
|
||||
/**
|
||||
The default audio format settings.
|
||||
|
||||
If no settings are specified, the audio configuration is
|
||||
MPEG4 AAC, 2 channels, 16 bit, 44.1 kHz, AVAudioQualityMin.
|
||||
*/
|
||||
+ (NSDictionary *)defaultRecorderSettings;
|
||||
|
||||
/**
|
||||
Audio format settings
|
||||
|
||||
Settings for the recording session.
|
||||
Passed to AVAudioRecorder`'s `-initWithURL:settings:error:`
|
||||
For information on the settings available for an audio recorder, see "AV Foundation Audio Settings Constants".
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSDictionary *recorderSettings;
|
||||
|
||||
/**
|
||||
Returns an initialized audio recorder using the specified settings, step, and output directory.
|
||||
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param recorderSettings The settings for the recording session.
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the audio output should be stored.
|
||||
|
||||
@return An initialized audio recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
recorderSettings:(nullable NSDictionary *)recorderSettings
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Reference to the audio recorder being used.
|
||||
|
||||
The value of this property is used in the audio task in order to display recorded volume in real time during the task.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) AVAudioRecorder *audioRecorder;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,341 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioRecorder.h"
|
||||
|
||||
#import "ORKRecorder_Internal.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@interface ORKAudioRecorder ()
|
||||
|
||||
@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
|
||||
|
||||
@property (nonatomic, copy) NSDictionary *recorderSettings;
|
||||
|
||||
@property (nonatomic, copy) NSString *savedSessionCategory;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioRecorder
|
||||
|
||||
- (void)dealloc {
|
||||
ORK_Log_Debug(@"Remove audiorecorder %p", self);
|
||||
[_audioRecorder stop];
|
||||
_audioRecorder = nil;
|
||||
}
|
||||
|
||||
+ (NSDictionary *)defaultRecorderSettings {
|
||||
return @{AVFormatIDKey : @(kAudioFormatMPEG4AAC),
|
||||
AVEncoderAudioQualityKey : @(AVAudioQualityMin),
|
||||
AVNumberOfChannelsKey : @(2),
|
||||
AVSampleRateKey : @(44100.0)};
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
recorderSettings:(NSDictionary *)recorderSettings
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
|
||||
self.continuesInBackground = YES;
|
||||
if (!recorderSettings) {
|
||||
recorderSettings = [[self class] defaultRecorderSettings];
|
||||
}
|
||||
if (![recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"recorderSettings should be a dictionary" userInfo:recorderSettings];
|
||||
}
|
||||
self.recorderSettings = recorderSettings;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)restoreSavedAudioSessionCategory {
|
||||
if (_savedSessionCategory) {
|
||||
NSError *error;
|
||||
if (![[AVAudioSession sharedInstance] setCategory:_savedSessionCategory error:&error]) {
|
||||
ORK_Log_Error(@"Failed to restore the audio session category: %@", [error localizedDescription]);
|
||||
}
|
||||
_savedSessionCategory = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
if (self.outputDirectory == nil) {
|
||||
@throw [NSException exceptionWithName:NSDestinationInvalidException reason:@"audioRecorder requires an output directory" userInfo:nil];
|
||||
}
|
||||
// Only create the file when we should actually start recording.
|
||||
if (!_audioRecorder) {
|
||||
|
||||
NSError *error = nil;
|
||||
NSURL *soundFileURL = [self recordingFileURL];
|
||||
if (![self recreateFileWithError:&error]) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
||||
_savedSessionCategory = audioSession.category;
|
||||
if (![audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
ORK_Log_Debug(@"Create audioRecorder %p", self);
|
||||
_audioRecorder = [[AVAudioRecorder alloc]
|
||||
initWithURL:soundFileURL
|
||||
settings:self.recorderSettings
|
||||
error:&error];
|
||||
if (!_audioRecorder) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
if (!_audioRecorder.recording) {
|
||||
[_audioRecorder prepareToRecord];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
if (!_audioRecorder.recording) {
|
||||
[_audioRecorder prepareToRecord];
|
||||
[_audioRecorder record];
|
||||
}
|
||||
#endif
|
||||
[super start];
|
||||
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (!_audioRecorder) {
|
||||
// Error has already been returned.
|
||||
return;
|
||||
}
|
||||
|
||||
[self doStopRecording];
|
||||
|
||||
NSURL *fileUrl = [self recordingFileURL];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[[self recordingFileURL] path]]) {
|
||||
fileUrl = nil;
|
||||
}
|
||||
|
||||
[self reportFileResultWithFile:fileUrl error:nil];
|
||||
|
||||
[super stop];
|
||||
}
|
||||
|
||||
- (BOOL)isRecording {
|
||||
return _audioRecorder.recording;
|
||||
}
|
||||
|
||||
- (NSString *)mimeType {
|
||||
NSDictionary *recorderSettings = [self recorderSettings];
|
||||
unsigned int recorderFormat = ((NSNumber *)recorderSettings[AVFormatIDKey]).unsignedIntValue;
|
||||
|
||||
NSString *contentType = @"audio";
|
||||
switch (recorderFormat) {
|
||||
case kAudioFormatLinearPCM: {
|
||||
int numBits = ((NSNumber *)recorderSettings[AVLinearPCMBitDepthKey]).intValue ? : 16;
|
||||
contentType = [NSString stringWithFormat:@"audio/L%d", numBits];
|
||||
break;
|
||||
}
|
||||
case kAudioFormatAC3: {
|
||||
contentType = @"audio/ac3";
|
||||
break;
|
||||
}
|
||||
case kAudioFormatMPEG4AAC:
|
||||
case kAudioFormatMPEG4CELP:
|
||||
case kAudioFormatMPEG4HVXC:
|
||||
case kAudioFormatMPEG4TwinVQ:
|
||||
case kAudioFormatAppleLossless: {
|
||||
contentType = @"audio/m4a";
|
||||
break;
|
||||
}
|
||||
case kAudioFormatULaw: {
|
||||
contentType = @"audio/basic";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return contentType;
|
||||
}
|
||||
|
||||
- (NSString *)recorderType {
|
||||
return @"audio";
|
||||
}
|
||||
|
||||
- (void)doStopRecording {
|
||||
if (self.isRecording) {
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
[_audioRecorder stop];
|
||||
|
||||
[self applyFileProtection:ORKFileProtectionComplete toFileAtURL:[self recordingFileURL]];
|
||||
#endif
|
||||
[self restoreSavedAudioSessionCategory];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
|
||||
[super finishRecordingWithError:error];
|
||||
}
|
||||
|
||||
- (NSString *)extension {
|
||||
NSDictionary *recorderSettings = [self recorderSettings];
|
||||
unsigned int recorderFormat = ((NSNumber *)recorderSettings[AVFormatIDKey]).unsignedIntValue;
|
||||
|
||||
NSString *extension = @"au";
|
||||
switch (recorderFormat) {
|
||||
case kAudioFormatLinearPCM:
|
||||
{
|
||||
extension = @"pcm";
|
||||
break;
|
||||
}
|
||||
case kAudioFormatAC3: {
|
||||
extension = @"ac3";
|
||||
break;
|
||||
}
|
||||
case kAudioFormatMPEG4AAC:
|
||||
case kAudioFormatMPEG4CELP:
|
||||
case kAudioFormatMPEG4HVXC:
|
||||
case kAudioFormatMPEG4TwinVQ:
|
||||
case kAudioFormatAppleLossless: {
|
||||
extension = @"m4a";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
- (NSURL *)recordingFileURL {
|
||||
return [[self recordingDirectoryURL] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [self logName], [self extension]]];
|
||||
}
|
||||
|
||||
- (BOOL)recreateFileWithError:(NSError **)error {
|
||||
NSURL *url = [self recordingFileURL];
|
||||
if (!url) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteInvalidFileNameError userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"ERROR_RECORDER_NO_OUTPUT_DIRECTORY", nil)}];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
|
||||
if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([fileManager fileExistsAtPath:[url path]]) {
|
||||
if (![fileManager removeItemAtPath:[url path] error:error]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
[fileManager createFileAtPath:[url path] contents:nil attributes:nil];
|
||||
[fileManager setAttributes:@{NSFileProtectionKey: ORKFileProtectionFromMode(ORKFileProtectionCompleteUnlessOpen)} ofItemAtPath:[url path] error:error];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[_audioRecorder stop];
|
||||
_audioRecorder = nil;
|
||||
[super reset];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
recorderSettings:(NSDictionary *)recorderSettings {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
if (recorderSettings && ![recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"recorderSettings should be a dictionary" userInfo:recorderSettings];
|
||||
}
|
||||
_recorderSettings = recorderSettings;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKAudioRecorder alloc] initWithIdentifier:self.identifier
|
||||
recorderSettings:self.recorderSettings
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, recorderSettings, NSDictionary);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_OBJ(aCoder, recorderSettings);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
ORKEqualObjects(self.recorderSettings, castObject.recorderSettings));
|
||||
}
|
||||
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
return ORKPermissionAudioRecording;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudioStep : ORKActiveStep
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioStep.h"
|
||||
|
||||
#import "ORKAudioStepViewController.h"
|
||||
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKAudioStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKAudioStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
NSTimeInterval const ORKAudioTaskMinimumDuration = 5.0;
|
||||
|
||||
if ( self.stepDuration < ORKAudioTaskMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKAudioTaskMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)startsFinished {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudioStepViewController : ORKActiveStepViewController
|
||||
|
||||
@property (nonatomic, assign) CGFloat alertThreshold;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioStepViewController.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKAudioContentView.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKAudioRecorder.h"
|
||||
|
||||
#import "ORKAudioStep.h"
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
@import AVFoundation;
|
||||
|
||||
|
||||
@interface ORKAudioStepViewController ()
|
||||
|
||||
@property (nonatomic, strong) AVAudioRecorder *avAudioRecorder;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioStepViewController {
|
||||
ORKAudioContentView *_audioContentView;
|
||||
ORKAudioRecorder *_audioRecorder;
|
||||
ORKActiveStepTimer *_timer;
|
||||
NSError *_audioRecorderError;
|
||||
}
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
// Continue audio recording in the background
|
||||
self.suspendIfInactive = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setAlertThreshold:(CGFloat)alertThreshold {
|
||||
_alertThreshold = alertThreshold;
|
||||
if (self.isViewLoaded && alertThreshold > 0) {
|
||||
_audioContentView.alertThreshold = alertThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
_audioContentView = [ORKAudioContentView new];
|
||||
_audioContentView.timeLeft = self.audioStep.stepDuration;
|
||||
|
||||
if (self.alertThreshold > 0) {
|
||||
_audioContentView.alertThreshold = self.alertThreshold;
|
||||
}
|
||||
|
||||
self.activeStepView.activeCustomView = _audioContentView;
|
||||
}
|
||||
|
||||
- (void)audioRecorderDidChange {
|
||||
_audioRecorder.audioRecorder.meteringEnabled = YES;
|
||||
[self setAvAudioRecorder:_audioRecorder.audioRecorder];
|
||||
}
|
||||
|
||||
- (void)recordersDidChange {
|
||||
ORKAudioRecorder *audioRecorder = nil;
|
||||
for (ORKRecorder *recorder in self.recorders) {
|
||||
if ([recorder isKindOfClass:[ORKAudioRecorder class]]) {
|
||||
audioRecorder = (ORKAudioRecorder *)recorder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_audioRecorder = audioRecorder;
|
||||
[self audioRecorderDidChange];
|
||||
}
|
||||
|
||||
- (ORKAudioStep *)audioStep {
|
||||
return (ORKAudioStep *)self.step;
|
||||
}
|
||||
|
||||
- (void)doSample {
|
||||
if (_audioRecorderError) {
|
||||
return;
|
||||
}
|
||||
[_avAudioRecorder updateMeters];
|
||||
float value = [_avAudioRecorder averagePowerForChannel:0];
|
||||
// Assume value is in range roughly -60dB to 0dB
|
||||
float clampedValue = MAX(value / 60.0, -1) + 1;
|
||||
[_audioContentView addSample:@(clampedValue)];
|
||||
_audioContentView.timeLeft = [_timer duration] - [_timer runtime];
|
||||
}
|
||||
|
||||
- (void)startNewTimerIfNeeded {
|
||||
if (!_timer) {
|
||||
NSTimeInterval duration = self.audioStep.stepDuration;
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
_timer = [[ORKActiveStepTimer alloc] initWithDuration:duration interval:duration / 100 runtime:0 handler:^(ORKActiveStepTimer *timer, BOOL finished) {
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf doSample];
|
||||
if (finished) {
|
||||
[strongSelf finish];
|
||||
}
|
||||
}];
|
||||
[_timer resume];
|
||||
}
|
||||
_audioContentView.finished = NO;
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
[super start];
|
||||
[self audioRecorderDidChange];
|
||||
[_timer reset];
|
||||
_timer = nil;
|
||||
[self startNewTimerIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
- (void)suspend {
|
||||
[super suspend];
|
||||
[_timer pause];
|
||||
if (_avAudioRecorder) {
|
||||
[_audioContentView addSample:@(0)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resume {
|
||||
[super resume];
|
||||
[self audioRecorderDidChange];
|
||||
[self startNewTimerIfNeeded];
|
||||
[_timer resume];
|
||||
}
|
||||
|
||||
- (void)finish {
|
||||
if (_audioRecorderError) {
|
||||
return;
|
||||
}
|
||||
[super finish];
|
||||
[_timer reset];
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
- (void)stepDidFinish {
|
||||
_audioContentView.finished = YES;
|
||||
}
|
||||
|
||||
- (void)setAvAudioRecorder:(AVAudioRecorder *)recorder {
|
||||
_avAudioRecorder = nil;
|
||||
_avAudioRecorder = recorder;
|
||||
}
|
||||
|
||||
- (void)recorder:(ORKRecorder *)recorder didFailWithError:(NSError *)error {
|
||||
[super recorder:recorder didFailWithError:error];
|
||||
_audioRecorderError = error;
|
||||
_audioContentView.failed = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKCountdownStep` class represents a step that displays a label and a
|
||||
countdown for a time equal to its duration.
|
||||
|
||||
To use the countdown step, set the `duration` property, incorporate it into a
|
||||
task, and present the task with a task view controller.
|
||||
|
||||
The countdown step is used in most of ResearchKit's predefined active tasks.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKCountdownStep : ORKActiveStep
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKCountdownStep.h"
|
||||
|
||||
#import "ORKCountdownStepViewController.h"
|
||||
|
||||
|
||||
@implementation ORKCountdownStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKCountdownStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
self.stepDuration = 5.0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
|
||||
[super validateParameters];
|
||||
|
||||
NSTimeInterval const ORKCountdownStepMinimumDuration = 3.0;
|
||||
|
||||
if ( self.stepDuration < ORKCountdownStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKCountdownStepMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKCountdownStepViewController` class represents the step view controller that corresponds to an `ORKCountdownStep`.
|
||||
|
||||
It is not usually necessary to instantiate this view controller directly.
|
||||
Instead, add a countdown step to a task, and present the task in a task
|
||||
view controller.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKCountdownStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,275 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKCountdownStepViewController.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKLabel.h"
|
||||
#import "ORKSubheadlineLabel.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
|
||||
#import "ORKActiveStep.h"
|
||||
#import "ORKResult.h"
|
||||
|
||||
#import "ORKAccessibility.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@interface ORKCountDownViewLabel : ORKLabel
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKCountDownViewLabel
|
||||
+ (UIFont *)defaultFont {
|
||||
return ORKThinFontWithSize(56);
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKCountdownView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, strong) ORKSubheadlineLabel *textLabel;
|
||||
@property (nonatomic, strong) ORKCountDownViewLabel *timeLabel;
|
||||
@property (nonatomic, strong) UIView *progressView;
|
||||
|
||||
- (void)startAnimateWithDuration:(NSTimeInterval)duration;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKCountdownView {
|
||||
CAShapeLayer *_circleLayer;
|
||||
}
|
||||
|
||||
static const CGFloat ProgressIndicatorDiameter = 104.0;
|
||||
static const CGFloat ProgressIndicatorOuterMargin = 1.0;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_textLabel = [ORKSubheadlineLabel new];
|
||||
_textLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_textLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_textLabel.text = ORKLocalizedString(@"COUNTDOWN_LABEL", nil);
|
||||
[self addSubview:_textLabel];
|
||||
|
||||
_timeLabel = [ORKCountDownViewLabel new];
|
||||
_timeLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_timeLabel.textAlignment = NSTextAlignmentCenter;
|
||||
[self addSubview:_timeLabel];
|
||||
|
||||
_progressView = [UIView new];
|
||||
_progressView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_progressView];
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self setUpConstraints];
|
||||
|
||||
_circleLayer = [CAShapeLayer layer];
|
||||
static const CGFloat ProgressIndicatorRadius = ProgressIndicatorDiameter / 2;
|
||||
_circleLayer.path = [[UIBezierPath bezierPathWithArcCenter:CGPointMake(ProgressIndicatorRadius + ProgressIndicatorOuterMargin, ProgressIndicatorRadius + ProgressIndicatorOuterMargin)
|
||||
radius:ProgressIndicatorRadius
|
||||
startAngle:M_PI + M_PI_2
|
||||
endAngle:-M_PI_2
|
||||
clockwise:NO] CGPath];
|
||||
_circleLayer.fillColor = [UIColor clearColor].CGColor;
|
||||
_circleLayer.strokeColor = self.tintColor.CGColor;
|
||||
_circleLayer.lineWidth = 1;
|
||||
|
||||
[_progressView.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
|
||||
[_progressView.layer addSublayer:_circleLayer];
|
||||
|
||||
_textLabel.isAccessibilityElement = NO;
|
||||
_timeLabel.isAccessibilityElement = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
|
||||
NSDictionary *metrics = @{@"d": @(ProgressIndicatorDiameter + 2 * ProgressIndicatorOuterMargin)};
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_textLabel, _timeLabel, _progressView);
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_textLabel]-(>=0)-[_progressView(==d)]|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing | NSLayoutFormatAlignAllCenterX
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_textLabel]-(>=0)-|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_progressView(==d)]-(>=0)-|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timeLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
// Constant required in order to give appearance of vertical centering (compensating for leading on font)
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timeLabel
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1.0
|
||||
constant:-3.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_textLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1.0
|
||||
constant:16.0 - ProgressIndicatorOuterMargin]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
_circleLayer.strokeColor = self.tintColor.CGColor;
|
||||
}
|
||||
|
||||
- (void)startAnimateWithDuration:(NSTimeInterval)duration {
|
||||
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
animation.duration = duration * 2;
|
||||
animation.removedOnCompletion = YES;
|
||||
animation.values = @[ @(1.0), @(0.0), @(0.0) ];
|
||||
animation.keyTimes = @[ @(0.0), @(0.5), @(1.0) ];
|
||||
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
[_circleLayer addAnimation:animation forKey:@"drawCircleAnimation"];
|
||||
}
|
||||
|
||||
#pragma mark Accessibility
|
||||
|
||||
- (BOOL)isAccessibilityElement {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel {
|
||||
return ORKAccessibilityStringForVariables(_textLabel.accessibilityLabel, _timeLabel.accessibilityLabel);
|
||||
}
|
||||
|
||||
- (UIAccessibilityTraits)accessibilityTraits {
|
||||
return [super accessibilityTraits] | UIAccessibilityTraitUpdatesFrequently;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKCountdownStepViewController ()
|
||||
|
||||
@property (nonatomic, strong) ORKCountdownView *countdownView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKCountdownStepViewController {
|
||||
NSInteger _countDown;
|
||||
}
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
self.suspendIfInactive = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setStep:(ORKStep *)step {
|
||||
[super setStep:step];
|
||||
_countDown = round([(ORKActiveStep *)step stepDuration]);
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.learnMoreButtonItem = nil;
|
||||
|
||||
_countdownView = [[ORKCountdownView alloc] init];
|
||||
_countdownView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.activeStepView.activeCustomView = _countdownView;
|
||||
|
||||
[self updateCountdownLabel];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @(_countDown).stringValue);
|
||||
[_countdownView startAnimateWithDuration:[(ORKActiveStep *)self.step stepDuration]];
|
||||
}
|
||||
|
||||
- (void)updateCountdownLabel {
|
||||
_countdownView.timeLabel.text = ORKLocalizedStringFromNumber(@(_countDown));
|
||||
}
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished {
|
||||
_countDown = MAX((_countDown - 1), 0);
|
||||
[self updateCountdownLabel];
|
||||
|
||||
if (UIAccessibilityIsVoiceOverRunning()) {
|
||||
if (finished) {
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIAccessibilityAnnouncementDidFinishNotification
|
||||
object:nil
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityAnnouncementDidFinishNotification object:nil];
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}];
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, ORKLocalizedString(@"AX_ANNOUNCE_BEGIN_TASK", nil));
|
||||
} else {
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @(_countDown).stringValue);
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}
|
||||
} else {
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,582 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKTypes.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKDataLogger;
|
||||
@class HKUnit;
|
||||
|
||||
/**
|
||||
The `ORKDataLoggerDelegate` protocol defines methods that the delegate of an `ORKDataLogger` object uses to handle data being logged to disk.
|
||||
*/
|
||||
@protocol ORKDataLoggerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
Tells the delegate when a log file rollover occurs.
|
||||
|
||||
@param dataLogger The data logger providing the notification.
|
||||
@param fileUrl The URL of the newly renamed log file.
|
||||
*/
|
||||
- (void)dataLogger:(ORKDataLogger *)dataLogger finishedLogFile:(NSURL *)fileUrl;
|
||||
|
||||
@optional
|
||||
/**
|
||||
Tells the delegate if the number of bytes in completed logs changes.
|
||||
|
||||
When files are removed or added, or marked as uploaded or unmarked, this delegate method is called a short time later. Multiple directory changes
|
||||
are rolled up into a single delegate callback.
|
||||
|
||||
@param dataLogger The data logger providing the notification.
|
||||
*/
|
||||
- (void)dataLoggerByteCountsDidChange:(ORKDataLogger *)dataLogger;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol ORKDataLoggerExtendedDelegate <ORKDataLoggerDelegate>
|
||||
|
||||
@optional
|
||||
/**
|
||||
Tells the delegate that the maximum current log file lifetime changed.
|
||||
@param dataLogger Source of this event.
|
||||
*/
|
||||
- (void)dataLoggerThresholdsDidChange:(ORKDataLogger *)dataLogger;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@class ORKLogFormatter;
|
||||
|
||||
/**
|
||||
The `ORKDataLogger` class is an internal component used by some `ORKRecorder`
|
||||
subclasses for writing data to disk during tasks. An `ORKDataLogger` object manages one log as a set of files in a directory.
|
||||
|
||||
The current log file is at `directory/logName`.
|
||||
Historic log files are at `directory/logName-(timestamp)-(count)`
|
||||
where timestamp is of the form `YYYYMMddHHmmss` (Zulu) and indicates the time
|
||||
the log finished (that is, was rolled over). If more than one rollover occurs within
|
||||
one second, additional log files may be created with increasing `count`.
|
||||
|
||||
The user is responsible for managing the historic log files, but the `ORKDataLogger` class
|
||||
provides tools for enumerating them (in sorted order).
|
||||
|
||||
The data logger contains a concept of whether a file has been uploaded, which
|
||||
is tracked using file attributes. This feature can facilitate a workflow in which
|
||||
log files are archived and queued for upload before actually sending them to
|
||||
a server. When archived and ready for upload, the files could be marked uploaded
|
||||
by the `ORKDataLogger`. When the upload is complete and the data has been handed
|
||||
off downstream, the files can then be deleted. If the upload fails, the uploaded
|
||||
files can have that flag cleared, to indicate that they should be included
|
||||
in the next archiving attempt.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKDataLogger : NSObject
|
||||
|
||||
/**
|
||||
Returns a data logger with an `ORKJSONLogFormatter`.
|
||||
|
||||
@param url The URL of the directory in which to place log files.
|
||||
@param logName The prefix on the log file name in an ASCII string. Note that the string must not contain the hyphen character ("-"), because a hyphen is used as a separator in the log naming scheme.
|
||||
@param delegate The initial delegate. May be `nil`.
|
||||
*/
|
||||
+ (ORKDataLogger *)JSONDataLoggerWithDirectory:(NSURL *)url logName:(NSString *)logName delegate:(nullable id<ORKDataLoggerDelegate>)delegate;
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Returns an initialized data logger using the specified URL, log name, formatter, and delegate.
|
||||
|
||||
@param url The URL of the directory in which to place log files
|
||||
@param logName The prefix on the log file name in an ASCII string. Note that
|
||||
the string must not contain the hyphen character ("-"), because a hyphen is used as a separator in the log naming scheme.
|
||||
@param formatter The type of formatter to use for the log, such as `ORKJSONLogFormatter`.
|
||||
@param delegate The initial delegate. May be `nil`.
|
||||
|
||||
@return An initialized data logger.
|
||||
*/
|
||||
- (instancetype)initWithDirectory:(NSURL *)url logName:(NSString *)logName formatter:(ORKLogFormatter *)formatter delegate:(nullable id<ORKDataLoggerDelegate>)delegate NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/// The delegate to be notified when file sizes change or the log rolls over.
|
||||
@property (weak, nullable) id<ORKDataLoggerDelegate> delegate;
|
||||
|
||||
/// The log formatter being used.
|
||||
@property (strong, readonly) ORKLogFormatter *logFormatter;
|
||||
|
||||
/**
|
||||
The maximum current log file size.
|
||||
|
||||
When the current log reaches this size, it is automatically rolled over.
|
||||
*/
|
||||
@property size_t maximumCurrentLogFileSize;
|
||||
|
||||
/**
|
||||
The maximum current log file lifetime.
|
||||
|
||||
When the current log file has been active this long, it is rolled over.
|
||||
*/
|
||||
@property NSTimeInterval maximumCurrentLogFileLifetime;
|
||||
|
||||
/// The number of bytes of log data that are not marked uploaded, excluding the current file. This value is lazily updated.
|
||||
@property unsigned long long pendingBytes;
|
||||
|
||||
/// The number of bytes of log data that are marked uploaded. This value is lazily updated.
|
||||
@property unsigned long long uploadedBytes;
|
||||
|
||||
/// The file protection mode to use for newly created files.
|
||||
@property (assign) ORKFileProtectionMode fileProtectionMode;
|
||||
|
||||
/// The prefix on the log file names.
|
||||
@property (copy, readonly) NSString *logName;
|
||||
|
||||
/// Forces a roll-over now.
|
||||
- (void)finishCurrentLog;
|
||||
|
||||
/// The current log file's location.
|
||||
- (NSURL *)currentLogFileURL;
|
||||
|
||||
/**
|
||||
Enumerates the URLs of completed log files, sorted to put the oldest first.
|
||||
|
||||
Takes a snapshot of the current directory's relevant files, sorts them,
|
||||
and enumerates them. Errors can occur if changes are being made to the filesystem other
|
||||
than through this object.
|
||||
|
||||
@param block The block to call during enumeration.
|
||||
@param error Any error detected during the enumeration.
|
||||
|
||||
@return `YES` if the enumeration was successful; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogs:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Enumerates the URLs of completed log files not yet marked uploaded,
|
||||
sorted to put the oldest first.
|
||||
|
||||
This method takes a snapshot of the current directory's completed nonuploaded log files, sorts them,
|
||||
and then enumerates them. Errors can occur if changes are being made to the filesystem other
|
||||
than through this object.
|
||||
|
||||
@param block The block to call during enumeration.
|
||||
@param error Any error detected during the enumeration.
|
||||
|
||||
@return `YES` if the enumeration was successful; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Enumerates the URLs of completed log files not already marked uploaded,
|
||||
sorted to put the oldest first.
|
||||
|
||||
Takes a snapshot of the current directory's completed uploaded log files, sorts them,
|
||||
and then enumerates them. Errors can occur if changes are being made to the filesystem other
|
||||
than through this object.
|
||||
|
||||
@param block The block to call during enumeration.
|
||||
@param error Any error detected during the enumeration.
|
||||
|
||||
@return `YES` if the enumeration was successful; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogsAlreadyUploaded:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Appends an object to the log file, which is formatted with `logFormatter`.
|
||||
|
||||
The default log formatter expects NSData; call canAcceptLogObjectOfClass: on `logFormatter` to determine if it will accept this object.
|
||||
|
||||
Note that the current log file is created and opened lazily when a request to
|
||||
log data is made. If an attempt is made to log data and there is no access due
|
||||
to file protection, the log is immediately rolled over and a new file created.
|
||||
|
||||
@param object Should be an object of a class that is accepted by the logFormatter.
|
||||
@param error Error output, if the append fails.
|
||||
|
||||
@return `YES` if appending succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)append:(id)object error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Appends multiple objects to the log file.
|
||||
|
||||
This method formats and appends all the objects at once. Using this method may have efficiency
|
||||
and atomicity gains for error handling, compared to making multiple calls to `append:error`.
|
||||
|
||||
@param objects An array of objects of a class that is accepted by the logFormatter.
|
||||
@param error Error output, if the append fails.
|
||||
|
||||
@return `YES` if appending succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Checks whether a file has been marked as uploaded.
|
||||
|
||||
@param url The URL to check.
|
||||
|
||||
@return `YES` if the uploaded attribute has been set on the file and the file exists; otherwise,
|
||||
`NO`.
|
||||
*/
|
||||
- (BOOL)isFileUploadedAtURL:(NSURL *)url;
|
||||
|
||||
/**
|
||||
Marks or unmarks a file as uploaded.
|
||||
|
||||
This method uses an extended attribute on the filesystem to mark a file as uploaded.
|
||||
This is intended for book-keeping use only and to track which files have already
|
||||
been attached to a pending upload. When the upload is sufficiently complete,
|
||||
the file should be removed.
|
||||
|
||||
@param uploaded A Boolean value that indicates whether to mark the file uploaded or not uploaded.
|
||||
@param url The URL to mark.
|
||||
@param error The error that occurred, if the operation fails.
|
||||
|
||||
@return `YES` if adding or removing the attribute succeeded; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Removes files if they are marked uploaded.
|
||||
|
||||
If a file is in the list, but is no longer marked uploaded, this method does not remove the file. This workflow lets you unmark files selectively if they could not be added
|
||||
to the archive, and later call `removeUploadedFiles:withError:` to remove only
|
||||
the files that are still marked uploaded.
|
||||
|
||||
@param fileURLs The array of files that should be removed.
|
||||
@param error The error that occurred, if the operation fails.
|
||||
|
||||
@return `YES` if removing the files succeeded; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Removes all files managed by this logger (files that have the `logName` prefix).
|
||||
|
||||
@param error The error that occurred, if operation fails.
|
||||
|
||||
@return `YES` if removing the files succeeded.; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeAllFilesWithError:(NSError * _Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKLogFormatter` class represents the base (default) log formatter, which appends data
|
||||
blindly to a log file.
|
||||
|
||||
A log formatter is used by a data logger to format objects
|
||||
for output to the log, and to begin a new log file and end an existing log file.
|
||||
`ORKLogFormatter` accepts NSData and has neither a header nor a footer.
|
||||
|
||||
A log formatter should ensure that the log is always in a valid state, so that
|
||||
even if the app is killed, the log is still readable.
|
||||
*/
|
||||
@interface ORKLogFormatter : NSObject
|
||||
|
||||
/**
|
||||
Returns a Boolean value that indicates whether the log formatter can serialize the specified type of object.
|
||||
|
||||
@param c The class of object to serialize.
|
||||
|
||||
@return `YES` if the log formatter can serialize this object class; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)canAcceptLogObjectOfClass:(Class)c;
|
||||
|
||||
/**
|
||||
Returns a Boolean value that indicates whether the log formatter can serialize the specified type of object.
|
||||
|
||||
@param object The object to serialize.
|
||||
|
||||
@return `YES` if the log formatter can serialize `object`; otherwise, `NO`
|
||||
*/
|
||||
- (BOOL)canAcceptLogObject:(id)object;
|
||||
|
||||
/**
|
||||
Begins a new log file on the specified file handle.
|
||||
|
||||
For example, may write a header or opening stanza of a new log file.
|
||||
|
||||
@param fileHandle The file handle to which to write.
|
||||
@param error The error output, on failure.
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Appends the specified object to the log file.
|
||||
|
||||
@param object The object to write.
|
||||
@param fileHandle The file handle to which to write.
|
||||
@param error The error output, on failure.
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Appends the specified objects to the log file.
|
||||
|
||||
@param objects The objects to write.
|
||||
@param fileHandle The file handle to which to write.
|
||||
@param error The error output, on failure.
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKJSONLogFormatter` class represents a log formatter for producing JSON output.
|
||||
|
||||
The JSON log formatter accepts `NSDictionary` objects for serialization.
|
||||
The JSON output is a dictionary that contains one key, `items`,
|
||||
which contains the array of logged items. The log itself does not contain
|
||||
any timestamp information, so the items should include such fields,
|
||||
if desired.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKJSONLogFormatter : ORKLogFormatter
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@class ORKJSONDataLogger;
|
||||
@class ORKDataLoggerManager;
|
||||
|
||||
/**
|
||||
The `ORKDataLoggerManagerDelegate` protocol defines methods a delegate can implement to receive notifications
|
||||
when the data loggers managed by a `ORKDataLoggerManager` reach a certain file size threshold.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@protocol ORKDataLoggerManagerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
Called by the data logger manager when the total size of files
|
||||
that are not marked uploaded has reached a threshold.
|
||||
|
||||
@param dataLoggerManager The manager that produced the notification.
|
||||
@param pendingUploadBytes The number of bytes managed by all the loggers, which
|
||||
have not yet been marked uploaded.
|
||||
*/
|
||||
- (void)dataLoggerManager:(ORKDataLoggerManager *)dataLoggerManager pendingUploadBytesReachedThreshold:(unsigned long long)pendingUploadBytes;
|
||||
|
||||
/**
|
||||
Called by the data logger manager when the total size of files
|
||||
managed by any of the loggers has reached a threshold.
|
||||
|
||||
@param dataLoggerManager The manager that produced the notification.
|
||||
@param totalBytes The total number of bytes of all files managed.
|
||||
*/
|
||||
- (void)dataLoggerManager:(ORKDataLoggerManager *)dataLoggerManager totalBytesReachedThreshold:(unsigned long long)totalBytes;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKDataLoggerManager` class represents a manager for multiple `ORKDataLogger` instances,
|
||||
which tracks the total size of log files produced and can notify its delegate
|
||||
when file sizes reach configurable thresholds.
|
||||
|
||||
The `ORKDataLoggerManager` class is an internal component used by some `ORKRecorder`
|
||||
subclasses for writing data to disk during tasks.
|
||||
|
||||
This manager can be used to organize the `ORKDataLogger` logs in a directory,
|
||||
and keep track of the total number of bytes stored on disk by each logger. The
|
||||
delegate can be informed if either the number of bytes pending upload, or the total
|
||||
number of bytes, exceeds configurable thresholds.
|
||||
|
||||
The configuration of the loggers and their thresholds is persisted in a
|
||||
configuration file in the log directory.
|
||||
|
||||
If the number of bytes pending upload exceeds the threshold, the natural action is to
|
||||
upload them. A block-based enumeration is provided for enumerating all the logs
|
||||
pending upload. Use `enumerateLogsNeedingUpload:error:`, and when a log has been
|
||||
processed for upload, use the logger to mark it uploaded.
|
||||
|
||||
When the upload succeeds (or at least is successfully queued), the uploaded files
|
||||
can be removed across all the loggers by calling `removeUploadedFiles:error:`
|
||||
|
||||
If the total number of bytes exceeds the threshold, the natural action is to remove log
|
||||
files that have been marked uploaded, and then remove old log files until the
|
||||
threshold is no longer exceeded. You can do this by calling `removeOldAndUploadedLogsToThreshold:error:`
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKDataLoggerManager : NSObject <ORKDataLoggerDelegate>
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Returns an initialized data logger manager using the specified directory and delegate.
|
||||
|
||||
Designated initializer.
|
||||
|
||||
@param directory The file URL of the directory where the data loggers should coexist.
|
||||
@param delegate The delegate to receive notifications.
|
||||
|
||||
@return An initialized data logger manager.
|
||||
*/
|
||||
- (instancetype)initWithDirectory:(NSURL *)directory delegate:(nullable id<ORKDataLoggerManagerDelegate>)delegate NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/// The delegate of the data logger manager.
|
||||
@property (weak, nullable) id<ORKDataLoggerManagerDelegate> delegate;
|
||||
|
||||
/// The threshold for delegate callback for total bytes not marked uploaded.
|
||||
@property unsigned long long pendingUploadBytesThreshold;
|
||||
|
||||
/// The threshold for delegate callback for total bytes of completed logs.
|
||||
@property unsigned long long totalBytesThreshold;
|
||||
|
||||
/// The total number of bytes of files not marked as pending upload.
|
||||
@property unsigned long long pendingUploadBytes;
|
||||
|
||||
/// The total number of bytes for all the loggers.
|
||||
@property unsigned long long totalBytes;
|
||||
|
||||
/**
|
||||
Adds a data logger with a JSON log format to the directory.
|
||||
|
||||
This method throws an exception if a logger already exists with the specified log name.
|
||||
|
||||
@param logName The log name prefix for the data logger.
|
||||
|
||||
@return The `ORKDataLogger` object that was added.
|
||||
*/
|
||||
- (ORKDataLogger *)addJSONDataLoggerForLogName:(NSString *)logName;
|
||||
|
||||
/**
|
||||
Adds a data logger with a particular formatter to the directory.
|
||||
|
||||
@param logName The log name prefix for the data logger.
|
||||
@param formatter The log formatter instance to use for this logger.
|
||||
|
||||
@return The `ORKDataLogger` object that was added, or the existing one if one already existed for
|
||||
that log name.
|
||||
*/
|
||||
- (ORKDataLogger *)addDataLoggerForLogName:(NSString *)logName formatter:(ORKLogFormatter *)formatter;
|
||||
|
||||
/**
|
||||
Retrieves the already existing data logger for a log name.
|
||||
|
||||
@param logName The log name prefix for the data logger.
|
||||
|
||||
@return The `ORKDataLogger` object that was retrieved, or `nil` if one already existed for that log name.
|
||||
*/
|
||||
- (nullable ORKDataLogger *)dataLoggerForLogName:(NSString *)logName;
|
||||
|
||||
/**
|
||||
Removes a data logger.
|
||||
|
||||
@param logger The logger to remove.
|
||||
*/
|
||||
- (void)removeDataLogger:(ORKDataLogger *)logger;
|
||||
|
||||
/// Returns the set of log names of the data loggers managed by this object.
|
||||
- (NSArray<NSString *> *)logNames;
|
||||
|
||||
/**
|
||||
Enumerates all the logs that need upload across all data loggers, sorted from oldest to first.
|
||||
|
||||
Before sorting the logs, this method fetches all the data loggers' logs that need upload.
|
||||
|
||||
@param block The block to call during enumeration.
|
||||
@param error The error, on failure.
|
||||
|
||||
@return `YES` if the enumeration succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(ORKDataLogger *dataLogger, NSURL *logFileUrl, BOOL *stop))block error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Unmarks the set of uploaded files.
|
||||
|
||||
Use this method to indicate that the specified files should no longer be marked uploaded (for example, because
|
||||
the upload did not succeed).
|
||||
|
||||
@param fileURLs The array of file URLs that should no longer be marked uploaded.
|
||||
@param error The error, on failure.
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Removes a set of uploaded files.
|
||||
|
||||
This method is analogous to a similar method in `ORKDataLogger`, but it accepts an array of files
|
||||
that may relate to any of the data loggers. It is an error to pass a URL which would not
|
||||
belong to one of the loggers managed by this manager.
|
||||
|
||||
@param fileURLs The array of file URLs that should be removed.
|
||||
@param error The error, on failure.
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Removes old and uploaded logs to bring total bytes down to a threshold.
|
||||
|
||||
This method removes uploaded logs first, followed by the oldest log files across
|
||||
all of the data loggers, until the total usage falls below a threshold.
|
||||
|
||||
@param bytes The threshold down to which to remove old log files. File removal stops when the total bytes managed by all the data loggers reaches this threshold.
|
||||
@param error The error, on failure.
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError * _Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKDataLogger (Tests)
|
||||
|
||||
/// The file handle to which to write
|
||||
- (nullable NSFileHandle *)fileHandle;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface NSURL (ORKDataLogger)
|
||||
|
||||
- (BOOL)ork_isUploaded;
|
||||
- (BOOL)ork_setUploaded:(BOOL)uploaded error:(NSError * _Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class CMDeviceMotion;
|
||||
|
||||
@protocol ORKDeviceMotionRecorderDelegate <ORKRecorderDelegate>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)deviceMotionRecorderDidUpdateWithMotion:(CMDeviceMotion *)motion;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
The `ORKDeviceMotionRecorder` class represents a recorder that requests and collects device motion data from CoreMotion at a fixed frequency.
|
||||
|
||||
To ensure that the motion recorder continues to record when the app enters the
|
||||
background, use the background task support provided by `UIApplication`.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKDeviceMotionRecorder : ORKRecorder
|
||||
|
||||
/**
|
||||
The frequency of motion data collection from CoreMotion in hertz (Hz).
|
||||
*/
|
||||
@property (nonatomic, readonly) double frequency;
|
||||
|
||||
/**
|
||||
Returns an initialized device motion recorder using the specified frequency.
|
||||
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param frequency The frequency of motion data collection from CoreMotion in hertz (Hz).
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the device motion data should be stored.
|
||||
|
||||
@return An initialized motion data recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
frequency:(double)frequency
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKDeviceMotionRecorder.h"
|
||||
|
||||
#import "ORKDataLogger.h"
|
||||
|
||||
#import "ORKRecorder_Internal.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "CMDeviceMotion+ORKJSONDictionary.h"
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
@interface ORKDeviceMotionRecorder () {
|
||||
ORKDataLogger *_logger;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) CMMotionManager *motionManager;
|
||||
|
||||
@property (nonatomic) NSTimeInterval uptime;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKDeviceMotionRecorder
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
frequency:(double)frequency
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
self.frequency = frequency;
|
||||
self.continuesInBackground = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_logger finishCurrentLog];
|
||||
}
|
||||
|
||||
- (void)setFrequency:(double)frequency {
|
||||
if (frequency <= 0) {
|
||||
_frequency = 1;
|
||||
} else {
|
||||
_frequency = frequency;
|
||||
}
|
||||
}
|
||||
|
||||
- (CMMotionManager *)createMotionManager {
|
||||
return [[CMMotionManager alloc] init];
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (!_logger) {
|
||||
NSError *error = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&error];
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.motionManager = [self createMotionManager];
|
||||
self.motionManager.deviceMotionUpdateInterval = 1.0 / _frequency;
|
||||
|
||||
self.uptime = [NSProcessInfo processInfo].systemUptime;
|
||||
|
||||
[self.motionManager stopDeviceMotionUpdates];
|
||||
|
||||
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *data, NSError *error) {
|
||||
BOOL success = NO;
|
||||
if (data) {
|
||||
success = [_logger append:[data ork_JSONDictionary] error:&error];
|
||||
id delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(deviceMotionRecorderDidUpdateWithMotion:)]) {
|
||||
[delegate deviceMotionRecorderDidUpdateWithMotion:data];
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self finishRecordingWithError:error];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)recorderType {
|
||||
return @"deviceMotion";
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
[self doStopRecording];
|
||||
[_logger finishCurrentLog];
|
||||
|
||||
NSError *error = nil;
|
||||
__block NSURL *fileUrl = nil;
|
||||
[_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
fileUrl = logFileUrl;
|
||||
} error:&error];
|
||||
|
||||
[self reportFileResultWithFile:fileUrl error:error];
|
||||
|
||||
[super stop];
|
||||
}
|
||||
|
||||
- (void)doStopRecording {
|
||||
if (self.isRecording) {
|
||||
[self.motionManager stopDeviceMotionUpdates];
|
||||
self.motionManager = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
[super finishRecordingWithError:error];
|
||||
}
|
||||
|
||||
- (BOOL)isRecording {
|
||||
return self.motionManager.deviceMotionActive;
|
||||
}
|
||||
|
||||
- (NSString *)mimeType {
|
||||
return @"application/json";
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
|
||||
_logger = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKDeviceMotionRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)freq {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
_frequency = freq;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKDeviceMotionRecorder alloc] initWithIdentifier:self.identifier
|
||||
frequency:self.frequency
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_DOUBLE(aDecoder, frequency);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_DOUBLE(aCoder, frequency);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.frequency == castObject.frequency));
|
||||
}
|
||||
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
return ORKPermissionCoreMotionAccelerometer;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKFitnessContentView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, assign, getter=isFinished) BOOL finished;
|
||||
|
||||
@property (nonatomic) BOOL hasHeartRate;
|
||||
@property (nonatomic) BOOL hasDistance;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *heartRate;
|
||||
@property (nonatomic) double distanceInMeters;
|
||||
|
||||
@property (nonatomic, strong, nullable) UIImage *image;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval timeLeft;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,346 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKFitnessContentView.h"
|
||||
|
||||
#import "ORKActiveStepQuantityView.h"
|
||||
#import "ORKTintedImageView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
@import CoreMotion;
|
||||
@import HealthKit;
|
||||
|
||||
|
||||
// #define LAYOUT_TEST 1
|
||||
// #define LAYOUT_DEBUG 1
|
||||
|
||||
@interface ORKFitnessContentView () {
|
||||
ORKQuantityLabel *_timerLabel;
|
||||
ORKQuantityPairView *_quantityPairView;
|
||||
UIView *_imageSpacer1;
|
||||
UIView *_imageSpacer2;
|
||||
ORKTintedImageView *_imageView;
|
||||
NSLengthFormatter *_lengthFormatter;
|
||||
NSLayoutConstraint *_imageRatioConstraint;
|
||||
NSLayoutConstraint *_topConstraint;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKFitnessContentView
|
||||
|
||||
- (ORKActiveStepQuantityView *)distanceView {
|
||||
return _quantityPairView.leftView;
|
||||
}
|
||||
|
||||
- (ORKActiveStepQuantityView *)heartRateView {
|
||||
return _quantityPairView.rightView;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_timerLabel = [ORKQuantityLabel new];
|
||||
_quantityPairView = [ORKQuantityPairView new];
|
||||
_imageSpacer1 = [UIView new];
|
||||
_imageSpacer1.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_imageSpacer2 = [UIView new];
|
||||
_imageSpacer2.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_imageSpacer1];
|
||||
[self addSubview:_imageSpacer2];
|
||||
[self heartRateView].image = [UIImage imageNamed:@"heart-fitness" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
|
||||
[self updateLengthFormatter];
|
||||
_imageView = [ORKTintedImageView new];
|
||||
_imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_imageView.shouldApplyTint = YES;
|
||||
_timerLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_quantityPairView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self updateKeylineVisible];
|
||||
|
||||
_timerLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
|
||||
_imageView.isAccessibilityElement = NO;
|
||||
|
||||
self.hasHeartRate = _hasHeartRate;
|
||||
self.hasDistance = _hasDistance;
|
||||
|
||||
#if LAYOUT_TEST
|
||||
self.timeLeft = 60 * 5;
|
||||
self.hasHeartRate = YES;
|
||||
self.hasDistance = YES;
|
||||
self.distanceInMeters = 100;
|
||||
self.heartRate = @"22";
|
||||
#endif
|
||||
#if LAYOUT_DEBUG
|
||||
self.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.2];
|
||||
_quantityPairView.backgroundColor = [[UIColor orangeColor] colorWithAlphaComponent:0.2];
|
||||
#endif
|
||||
|
||||
[self setDistanceInMeters:0];
|
||||
[self heartRateView].title = ORKLocalizedString(@"FITNESS_HEARTRATE_TITLE", nil);
|
||||
|
||||
[self addSubview:_quantityPairView];
|
||||
[self addSubview:_imageView];
|
||||
[self addSubview:_timerLabel];
|
||||
[self setUpConstraints];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(localeDidChange:) name:NSCurrentLocaleDidChangeNotification object:nil];
|
||||
|
||||
[self tintColorDidChange];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)updateLengthFormatter {
|
||||
_lengthFormatter = [NSLengthFormatter new];
|
||||
_lengthFormatter.numberFormatter.maximumFractionDigits = 1;
|
||||
_lengthFormatter.numberFormatter.maximumSignificantDigits = 3;
|
||||
}
|
||||
|
||||
- (void)localeDidChange:(NSNotification *)notification {
|
||||
[self updateLengthFormatter];
|
||||
[self setDistanceInMeters:_distanceInMeters];
|
||||
}
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat CaptionBaselineToTimerTop = ORKGetMetricForWindow(ORKScreenMetricCaptionBaselineToFitnessTimerTop, window);
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
_topConstraint.constant = (CaptionBaselineToTimerTop - CaptionBaselineToStepViewTop);
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_timerLabel, _imageView, _quantityPairView, _imageSpacer1, _imageSpacer2);
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_timerLabel][_imageSpacer1(>=0)][_imageView]"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
_topConstraint = [NSLayoutConstraint constraintWithItem:_timerLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
[constraints addObject:_topConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timerLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timerLabel
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_imageView][_imageSpacer2(>=0)][_quantityPairView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer2
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_imageSpacer2
|
||||
attribute:NSLayoutAttributeHeight
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
NSLayoutConstraint *imageSpacerHeightConstraint = [NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
imageSpacerHeightConstraint.priority = UILayoutPriorityDefaultLow - 1;
|
||||
[constraints addObject:imageSpacerHeightConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_quantityPairView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
}
|
||||
|
||||
- (void)setImage:(UIImage *)image {
|
||||
_image = image;
|
||||
_imageView.image = image;
|
||||
|
||||
_imageRatioConstraint.active = NO;
|
||||
|
||||
CGSize size = image.size;
|
||||
if (size.width > 0 && size.height > 0) {
|
||||
_imageRatioConstraint = [NSLayoutConstraint constraintWithItem:_imageView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_imageView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:size.height / size.width
|
||||
constant:0.0];
|
||||
_imageRatioConstraint.active = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHasDistance:(BOOL)hasDistance {
|
||||
_hasDistance = hasDistance;
|
||||
[self distanceView].enabled = _hasDistance;
|
||||
[self updateKeylineVisible];
|
||||
}
|
||||
|
||||
- (void)setHasHeartRate:(BOOL)hasHeartRate {
|
||||
_hasHeartRate = hasHeartRate;
|
||||
[self heartRateView].enabled = _hasHeartRate;
|
||||
[self updateKeylineVisible];
|
||||
}
|
||||
|
||||
- (void)setHeartRate:(NSString *)heartRate {
|
||||
_heartRate = heartRate;
|
||||
[self heartRateView].value = heartRate;
|
||||
}
|
||||
|
||||
- (void)updateKeylineVisible {
|
||||
[_quantityPairView setKeylineHidden:!(_hasDistance && _hasHeartRate)];
|
||||
}
|
||||
|
||||
- (void)setDistanceInMeters:(double)distanceInMeters {
|
||||
_distanceInMeters = distanceInMeters;
|
||||
double displayDistance = _distanceInMeters;
|
||||
NSString *distanceString = nil;
|
||||
NSLengthFormatterUnit unit;
|
||||
NSString *unitString = [_lengthFormatter unitStringFromMeters:displayDistance usedUnit:&unit];
|
||||
|
||||
switch (unit) {
|
||||
case NSLengthFormatterUnitCentimeter:
|
||||
case NSLengthFormatterUnitMillimeter:
|
||||
unit = NSLengthFormatterUnitMeter;
|
||||
// Force showing 0 meters if the distance is sufficiently short to be displayed in cm or mm
|
||||
unitString = [_lengthFormatter unitStringFromValue:0 unit:NSLengthFormatterUnitMeter];
|
||||
displayDistance = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Use HealthKit to convert the unit, so we can use the number formatter directly.
|
||||
HKUnit *hkUnit = [HKUnit unitFromLengthFormatterUnit:unit];
|
||||
double conversionFactor = 1.0;
|
||||
if ([hkUnit isNull] && (unit == NSLengthFormatterUnitYard)) {
|
||||
hkUnit = [HKUnit footUnit];
|
||||
conversionFactor = 1.0 / 3.0;
|
||||
}
|
||||
HKQuantity *quantity = [HKQuantity quantityWithUnit:[HKUnit meterUnit] doubleValue:displayDistance];
|
||||
distanceString = [_lengthFormatter.numberFormatter stringFromNumber:@([quantity doubleValueForUnit:hkUnit]*conversionFactor)];
|
||||
|
||||
[self distanceView].title = [NSString localizedStringWithFormat:ORKLocalizedString(@"FITNESS_DISTANCE_TITLE_FORMAT", nil), unitString];
|
||||
[self distanceView].value = distanceString;
|
||||
}
|
||||
|
||||
- (void)setTimeLeft:(NSTimeInterval)timeLeft {
|
||||
_timeLeft = timeLeft;
|
||||
[self updateTimerLabel];
|
||||
}
|
||||
|
||||
- (void)updateTimerLabel {
|
||||
static NSDateComponentsFormatter *formatter = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
formatter = [NSDateComponentsFormatter new];
|
||||
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
|
||||
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
|
||||
formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond;
|
||||
});
|
||||
|
||||
NSString *labelString = [formatter stringFromTimeInterval:MAX(round(_timeLeft),0)];
|
||||
_timerLabel.text = labelString;
|
||||
_timerLabel.hidden = (labelString == nil);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Fitness step.
|
||||
|
||||
Displays usual header, a counting-up timer, read outs for distance and/or
|
||||
heart rate if corresponding recorders are attached.
|
||||
|
||||
Also displays an image during the task.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKFitnessStep : ORKActiveStep
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKFitnessStep.h"
|
||||
|
||||
#import "ORKFitnessStepViewController.h"
|
||||
|
||||
|
||||
@implementation ORKFitnessStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKFitnessStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
NSTimeInterval const ORKFitnessStepMinimumDuration = 5.0;
|
||||
|
||||
if (self.stepDuration < ORKFitnessStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"rest duration cannot be shorter than %@ seconds.", @(ORKFitnessStepMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKFitnessStep *step = [super copyWithZone:zone];
|
||||
return step;
|
||||
}
|
||||
|
||||
- (BOOL)startsFinished {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Step view controller corresponding to `ORKFitnessStep`.
|
||||
|
||||
Observes countdown timer, heart rate, and distance, and relays these to the
|
||||
screen during the task.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKFitnessStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKFitnessStepViewController.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKFitnessContentView.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKHealthQuantityTypeRecorder.h"
|
||||
#import "ORKPedometerRecorder.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKFitnessStep.h"
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@interface ORKFitnessStepViewController () <ORKHealthQuantityTypeRecorderDelegate, ORKPedometerRecorderDelegate> {
|
||||
NSInteger _intendedSteps;
|
||||
ORKFitnessContentView *_contentView;
|
||||
NSNumberFormatter *_hrFormatter;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKFitnessStepViewController
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
self.suspendIfInactive = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKFitnessStep *)fitnessStep {
|
||||
return (ORKFitnessStep *)self.step;
|
||||
}
|
||||
|
||||
- (void)stepDidChange {
|
||||
[super stepDidChange];
|
||||
_hrFormatter = [[NSNumberFormatter alloc] init];
|
||||
_hrFormatter.numberStyle = kCFNumberFormatterNoStyle;
|
||||
_contentView.timeLeft = self.fitnessStep.stepDuration;
|
||||
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
_contentView = [ORKFitnessContentView new];
|
||||
_contentView.image = self.fitnessStep.image;
|
||||
_contentView.timeLeft = self.fitnessStep.stepDuration;
|
||||
self.activeStepView.activeCustomView = _contentView;
|
||||
self.activeStepView.stepViewFillsAvailableSpace = YES;
|
||||
}
|
||||
|
||||
- (void)updateHeartRateWithQuantity:(HKQuantitySample *)quantity unit:(HKUnit *)unit {
|
||||
if (quantity != nil) {
|
||||
_contentView.hasHeartRate = YES;
|
||||
}
|
||||
if (quantity) {
|
||||
_contentView.heartRate = [_hrFormatter stringFromNumber:@([quantity.quantity doubleValueForUnit:unit])];
|
||||
} else {
|
||||
_contentView.heartRate = @"--";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateDistance:(double)distanceInMeters {
|
||||
_contentView.hasDistance = YES;
|
||||
_contentView.distanceInMeters = distanceInMeters;
|
||||
|
||||
}
|
||||
|
||||
- (void)recordersDidChange {
|
||||
[super recordersDidChange];
|
||||
|
||||
ORKPedometerRecorder *pedometerRecorder = nil;
|
||||
ORKHealthQuantityTypeRecorder *heartRateRecorder = nil;
|
||||
for (ORKRecorder *recorder in self.recorders) {
|
||||
if ([recorder isKindOfClass:[ORKPedometerRecorder class]]) {
|
||||
pedometerRecorder = (ORKPedometerRecorder *)recorder;
|
||||
} else if ([recorder isKindOfClass:[ORKHealthQuantityTypeRecorder class]]) {
|
||||
ORKHealthQuantityTypeRecorder *rec1 = (ORKHealthQuantityTypeRecorder *)recorder;
|
||||
if ([[[rec1 quantityType] identifier] isEqualToString:HKQuantityTypeIdentifierHeartRate]) {
|
||||
heartRateRecorder = (ORKHealthQuantityTypeRecorder *)recorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (heartRateRecorder == nil) {
|
||||
_contentView.hasHeartRate = NO;
|
||||
}
|
||||
_contentView.heartRate = @"--";
|
||||
_contentView.hasDistance = (pedometerRecorder != nil);
|
||||
_contentView.distanceInMeters = 0;
|
||||
|
||||
}
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished {
|
||||
_contentView.timeLeft = finished ? 0 : (timer.duration - timer.runtime);
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}
|
||||
|
||||
#pragma mark - ORKHealthQuantityTypeRecorderDelegate
|
||||
|
||||
- (void)healthQuantityTypeRecorderDidUpdate:(ORKHealthQuantityTypeRecorder *)healthQuantityTypeRecorder {
|
||||
if ([[healthQuantityTypeRecorder.quantityType identifier] isEqualToString:HKQuantityTypeIdentifierHeartRate]) {
|
||||
[self updateHeartRateWithQuantity:healthQuantityTypeRecorder.lastSample unit:healthQuantityTypeRecorder.unit];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - ORKPedometerRecorderDelegate
|
||||
|
||||
- (void)pedometerRecorderDidUpdate:(ORKPedometerRecorder *)pedometerRecorder {
|
||||
double distanceInMeters = pedometerRecorder.totalDistance;
|
||||
[self updateDistance:distanceInMeters];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKHealthQuantityTypeRecorder;
|
||||
|
||||
@protocol ORKHealthQuantityTypeRecorderDelegate <ORKRecorderDelegate>
|
||||
|
||||
@optional
|
||||
- (void)healthQuantityTypeRecorderDidUpdate:(ORKHealthQuantityTypeRecorder *)healthQuantityTypeRecorder;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKHealthQuantityTypeRecorder` class represents a recorder for collecting real time sample data from HealthKit, such as heart rate, during
|
||||
an active task.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHealthQuantityTypeRecorder : ORKRecorder
|
||||
|
||||
@property (nonatomic, copy, readonly) HKQuantityType *quantityType;
|
||||
|
||||
@property (nonatomic, copy, readonly) HKUnit *unit;
|
||||
|
||||
@property (nonatomic, copy, readonly, nullable) HKQuantitySample *lastSample;
|
||||
|
||||
/**
|
||||
Returns an initialized health quantity type recorder using the specified quantity type and unit.
|
||||
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param quantityType The quantity type that should be collected during the active task.
|
||||
@param unit The unit for the data that should be collected and serialized.
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the HealthKit data should be stored.
|
||||
|
||||
@return An initialized health quantity type recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
healthQuantityType:(HKQuantityType *)quantityType
|
||||
unit:(HKUnit *)unit
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,370 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHealthQuantityTypeRecorder.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKDataLogger.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "HKSample+ORKJSONDictionary.h"
|
||||
|
||||
|
||||
@interface ORKHealthQuantityTypeRecorder () {
|
||||
ORKDataLogger *_logger;
|
||||
BOOL _isRecording;
|
||||
HKHealthStore *_healthStore;
|
||||
NSPredicate *_samplePredicate;
|
||||
HKObserverQuery *_observerQuery;
|
||||
/// Either the HKQueryAnchor object *or* NSUInteger value are tracked since the initializer for
|
||||
/// iOS 8 and iOS 9 use different objects. Only one will actually be referenced in the initalizer.
|
||||
HKQueryAnchor *_anchor;
|
||||
NSUInteger _anchorValue;
|
||||
HKQuantitySample *_lastSample;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __IPHONE_10_0
|
||||
/// Add a protocol defining the initializer for iOS 8 apps. This signature was deprecated in iOS 9
|
||||
/// and deleted in iOS 10.
|
||||
@interface HKAnchoredObjectQuery (iOS8)
|
||||
- (instancetype)initWithType:(HKSampleType *)type
|
||||
predicate:(NSPredicate *)predicate
|
||||
anchor:(NSUInteger)anchor
|
||||
limit:(NSUInteger)limit
|
||||
completionHandler:(void (^)(HKAnchoredObjectQuery *query,
|
||||
NSArray<__kindof HKSample *> *results,
|
||||
NSUInteger newAnchor,
|
||||
NSError *error))handler NS_DEPRECATED_IOS(8_0, 9_0);
|
||||
@end
|
||||
#endif
|
||||
|
||||
@implementation ORKHealthQuantityTypeRecorder
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
healthQuantityType:(HKQuantityType *)quantityType
|
||||
unit:(HKUnit *)unit
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
NSParameterAssert(quantityType != nil);
|
||||
NSParameterAssert(unit != nil);
|
||||
// Quantity type and unit are immutable, so should be equivalent to -copy
|
||||
_quantityType = quantityType;
|
||||
_unit = unit;
|
||||
self.continuesInBackground = YES;
|
||||
_anchorValue = HKAnchoredObjectQueryNoAnchor;
|
||||
_anchor = [HKQueryAnchor anchorFromValue:_anchorValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_logger finishCurrentLog];
|
||||
}
|
||||
|
||||
- (void)updateMostRecentSample:(HKQuantitySample *)sample {
|
||||
[self willChangeValueForKey:@"lastSample"];
|
||||
_lastSample = sample;
|
||||
[self didChangeValueForKey:@"lastSample"];
|
||||
|
||||
id<ORKHealthQuantityTypeRecorderDelegate> delegate = (id<ORKHealthQuantityTypeRecorderDelegate>)self.delegate;
|
||||
if (delegate && [delegate respondsToSelector:@selector(healthQuantityTypeRecorderDidUpdate:)]) {
|
||||
[delegate healthQuantityTypeRecorderDidUpdate:self];
|
||||
}
|
||||
}
|
||||
|
||||
static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
|
||||
- (void)query_logResults:(NSArray *)results withAnchor:(HKQueryAnchor*)newAnchor anchorValue:(NSUInteger)anchorValue {
|
||||
|
||||
NSUInteger resultCount = results.count;
|
||||
if (resultCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do conversion to dictionary on whatever queue we happen to be on.
|
||||
NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:resultCount];
|
||||
[results enumerateObjectsUsingBlock:^(HKQuantitySample *sample, NSUInteger idx, BOOL *stop) {
|
||||
[dictionaries addObject:[sample ork_JSONDictionaryWithOptions:ORKSampleIncludeSource|ORKSampleIncludeMetadata unit:_unit]];
|
||||
}];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self updateMostRecentSample:results.lastObject];
|
||||
|
||||
NSError *error = nil;
|
||||
if (![_logger appendObjects:dictionaries error:&error]) {
|
||||
// Logger writes are unrecoverable
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
_anchor = newAnchor;
|
||||
_anchorValue = anchorValue;
|
||||
|
||||
if (resultCount == _HealthAnchoredQueryLimit) {
|
||||
// Do another fetch immediately rather than wait for an observation
|
||||
[self doFetchNewData];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)doFetchNewData {
|
||||
if (!_healthStore || !_isRecording) {
|
||||
return;
|
||||
}
|
||||
NSAssert(_samplePredicate != nil, @"Sample predicate should be non-nil if recording");
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
void (^handleResults)(NSArray <__kindof HKSample *> *, HKQueryAnchor *, NSUInteger, NSError *) = ^ (NSArray *results, HKQueryAnchor *newAnchor, NSUInteger newAnchorValue, NSError *error) {
|
||||
if (error) {
|
||||
// An error in the query's not the end of the world: we'll probably get another chance. Just log it.
|
||||
ORK_Log_Warning(@"Anchored query error: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf query_logResults:results withAnchor:newAnchor anchorValue:newAnchorValue];
|
||||
};
|
||||
|
||||
|
||||
HKAnchoredObjectQuery *anchoredQuery;
|
||||
if ([HKAnchoredObjectQuery instancesRespondToSelector:@selector(initWithType:predicate:anchor:limit:resultsHandler:)]) {
|
||||
|
||||
anchoredQuery = [[HKAnchoredObjectQuery alloc] initWithType:_quantityType
|
||||
predicate:_samplePredicate
|
||||
anchor:_anchor
|
||||
limit:_HealthAnchoredQueryLimit
|
||||
resultsHandler:
|
||||
^(HKAnchoredObjectQuery *query, NSArray *sampleObjects, NSArray *deletedObjects, HKQueryAnchor *newAnchor, NSError *error) {
|
||||
handleResults(sampleObjects, newAnchor, 0, error);
|
||||
}];
|
||||
} else if ([HKAnchoredObjectQuery instancesRespondToSelector:@selector(initWithType:predicate:anchor:limit:completionHandler:)]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
anchoredQuery = [[HKAnchoredObjectQuery alloc] initWithType:_quantityType
|
||||
predicate:_samplePredicate
|
||||
anchor:_anchorValue
|
||||
limit:_HealthAnchoredQueryLimit
|
||||
completionHandler:
|
||||
^(HKAnchoredObjectQuery *query, NSArray<__kindof HKSample *> *results, NSUInteger newAnchor, NSError *error) {
|
||||
handleResults(results, nil, newAnchor, error);
|
||||
}];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
else {
|
||||
NSAssert(NO, @"Could not instantiate an HKAnchoredObjectQuery.");
|
||||
}
|
||||
|
||||
[_healthStore executeQuery:anchoredQuery];
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (![HKHealthStore isHealthDataAvailable]) {
|
||||
[self finishRecordingWithError:[NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}]];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_healthStore) {
|
||||
// Get a new obsever query
|
||||
_healthStore = [HKHealthStore new];
|
||||
} else {
|
||||
// Reset
|
||||
if (_observerQuery) {
|
||||
[_healthStore stopQuery:_observerQuery];
|
||||
_observerQuery = nil;
|
||||
}
|
||||
}
|
||||
|
||||
_lastSample = nil;
|
||||
_samplePredicate = [HKQuery predicateForSamplesWithStartDate:[NSDate date] endDate:nil options:HKQueryOptionStrictStartDate];
|
||||
|
||||
NSAssert(!_observerQuery, @"observer query should not exist if not recording");
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
_observerQuery = [[HKObserverQuery alloc]
|
||||
initWithSampleType:_quantityType
|
||||
predicate:_samplePredicate
|
||||
updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (error) {
|
||||
[strongSelf finishRecordingWithError:error];
|
||||
} else {
|
||||
[strongSelf doFetchNewData];
|
||||
}
|
||||
});
|
||||
|
||||
// Immediately signal receipt. We've fired off to either finish or do a new fetch.
|
||||
completionHandler();
|
||||
|
||||
}];
|
||||
|
||||
_isRecording = YES;
|
||||
[_healthStore executeQuery:_observerQuery];
|
||||
}
|
||||
|
||||
- (NSString *)recorderType {
|
||||
return _quantityType.identifier;
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (!_isRecording) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self doStopRecording];
|
||||
[_logger finishCurrentLog];
|
||||
|
||||
NSError *error = nil;
|
||||
__block NSURL *fileUrl = nil;
|
||||
[_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
fileUrl = logFileUrl;
|
||||
} error:&error];
|
||||
|
||||
[self reportFileResultWithFile:fileUrl error:error];
|
||||
|
||||
[super stop];
|
||||
}
|
||||
|
||||
- (void)doStopRecording {
|
||||
if (_isRecording) {
|
||||
NSAssert(_observerQuery != nil, @"Observer query should be non-nil when recording");
|
||||
[_healthStore stopQuery:_observerQuery];
|
||||
_observerQuery = nil;
|
||||
|
||||
_samplePredicate = nil;
|
||||
_isRecording = NO;
|
||||
|
||||
[self updateMostRecentSample:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
[super finishRecordingWithError:error];
|
||||
}
|
||||
|
||||
- (BOOL)isRecording {
|
||||
return _isRecording;
|
||||
}
|
||||
|
||||
- (NSString *)mimeType {
|
||||
return @"application/json";
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
|
||||
_logger = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHealthQuantityTypeRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier healthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
NSParameterAssert(quantityType != nil);
|
||||
NSParameterAssert(unit != nil);
|
||||
// Quantity type and unit are immutable, so should be equivalent to -copy
|
||||
_quantityType = quantityType;
|
||||
_unit = unit;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKHealthQuantityTypeRecorder alloc] initWithIdentifier:self.identifier
|
||||
healthQuantityType:_quantityType
|
||||
unit:_unit
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, quantityType, HKQuantityType);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, unit, HKUnit);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
ORK_ENCODE_OBJ(aCoder, quantityType);
|
||||
ORK_ENCODE_OBJ(aCoder, unit);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
ORKEqualObjects(self.quantityType, castObject.quantityType)&&
|
||||
ORKEqualObjects(self.unit, castObject.unit));
|
||||
}
|
||||
|
||||
- (NSSet *)requestedHealthKitTypesForReading {
|
||||
return [NSSet setWithObject:_quantityType];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKTypes.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ORKHolePegTestPlaceContentViewDelegate;
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlaceContentView : ORKActiveStepCustomView
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
||||
- (instancetype)initWithMovingDirection:(ORKBodySagittal)movingDirection rotated:(BOOL)rotated NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
@property (nonatomic, weak) id<ORKHolePegTestPlaceContentViewDelegate> delegate;
|
||||
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol ORKHolePegTestPlaceContentViewDelegate <NSObject>
|
||||
|
||||
- (void)holePegTestPlaceDidProgress:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView;
|
||||
- (void)holePegTestPlaceDidSucceed:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView withDistance:(CGFloat)distance;
|
||||
- (void)holePegTestPlaceDidFail:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,399 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHolePegTestPlaceContentView.h"
|
||||
|
||||
#import "ORKDirectionView.h"
|
||||
#import "ORKHolePegTestPlaceHoleView.h"
|
||||
#import "ORKHolePegTestPlacePegView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
static const CGFloat ORKOrientationThreshold = 12.0f;
|
||||
static const CGFloat ORKHolePegViewDiameter = 88.0f;
|
||||
#define degreesToRadians(degrees) ((degrees) / 180.0 * M_PI)
|
||||
|
||||
|
||||
@interface ORKHolePegTestPlaceContentView () <UIGestureRecognizerDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
@property (nonatomic, strong) ORKHolePegTestPlacePegView *pegView;
|
||||
@property (nonatomic, strong) ORKHolePegTestPlaceHoleView *holeView;
|
||||
@property (nonatomic, strong) ORKDirectionView *directionView;
|
||||
@property (nonatomic, copy) NSArray *constraints;
|
||||
|
||||
@property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer;
|
||||
@property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer;
|
||||
@property (nonatomic, strong) UIRotationGestureRecognizer *rotationRecognizer;
|
||||
@property (nonatomic, assign, getter = isMovable) BOOL movable;
|
||||
@property (nonatomic, assign, getter = hasMoveEnded) BOOL moveEnded;
|
||||
@property (nonatomic, assign) CGFloat rotation;
|
||||
@property (nonatomic, assign) CGFloat rotationOffset;
|
||||
@property (nonatomic, assign) CGPoint translation;
|
||||
@property (nonatomic, assign) CGPoint translationOffset;
|
||||
@property (nonatomic, assign) CGPoint startPoint;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlaceContentView
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithMovingDirection:(ORKBodySagittal)movingDirection rotated:(BOOL)rotated {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
self.movingDirection = movingDirection;
|
||||
self.rotated = rotated;
|
||||
|
||||
self.progressView = [UIProgressView new];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
[self.progressView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.progressView setAlpha:0];
|
||||
[self addSubview:self.progressView];
|
||||
|
||||
self.holeView = [[ORKHolePegTestPlaceHoleView alloc] initWithFrame:CGRectMake(0, 0, ORKHolePegViewDiameter, ORKHolePegViewDiameter)];
|
||||
self.holeView.rotated = self.isRotated;
|
||||
[self.holeView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self.holeView];
|
||||
|
||||
self.pegView = [[ORKHolePegTestPlacePegView alloc] initWithFrame:CGRectMake(0, 0, ORKHolePegViewDiameter, ORKHolePegViewDiameter)];
|
||||
self.pegView.rotated = self.isRotated;
|
||||
[self.pegView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self.pegView];
|
||||
|
||||
self.directionView = [[ORKDirectionView alloc] initWithOrientation:(self.movingDirection == ORKBodySagittalLeft) ? ORKBodySagittalRight : ORKBodySagittalLeft];
|
||||
[self.directionView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self.directionView];
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
self.movable = NO;
|
||||
self.moveEnded = NO;
|
||||
self.startPoint = CGPointZero;
|
||||
|
||||
self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handlePinch:)];
|
||||
self.pinchRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.pinchRecognizer];
|
||||
|
||||
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handlePan:)];
|
||||
self.panRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.panRecognizer];
|
||||
|
||||
if (rotated) {
|
||||
self.rotationRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handleRotate:)];
|
||||
self.rotationRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.rotationRecognizer];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
}
|
||||
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
|
||||
[self.progressView setProgress:progress animated:animated];
|
||||
[UIView animateWithDuration:animated ? 0.2 : 0 animations:^{
|
||||
[self.progressView setAlpha:(progress == 0) ? 0 : 1];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateLayoutMargins {
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin * 2, .right = margin * 2};
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
[super setFrame:frame];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
[super setBounds:bounds];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([self.constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
self.constraints = nil;
|
||||
}
|
||||
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _pegView, _holeView, _directionView);
|
||||
NSDictionary *metrics = @{@"diameter": @(ORKHolePegViewDiameter)};
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:(self.movingDirection == ORKBodySagittalLeft) ? @"H:|-[_pegView]->=0-[_holeView]-|" : @"H:|-[_holeView]->=0-[_pegView]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_progressView]"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[_pegView(diameter)]->=0-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:metrics views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[_holeView]->=0-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.pegView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.directionView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.directionView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
self.constraints = constraintsArray;
|
||||
[self addConstraints:self.constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark - gesture recognizer methods
|
||||
|
||||
- (void)pickupPegWithGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
|
||||
CGPoint touch = [gestureRecognizer locationInView:self];
|
||||
CGPoint touch1 = [gestureRecognizer locationOfTouch:0 inView:self];
|
||||
CGPoint touch2 = [gestureRecognizer locationOfTouch:1 inView:self];
|
||||
double distance = hypot(touch1.x - touch2.x, touch1.y - touch2.y);
|
||||
|
||||
if (distance < 3 * CGRectGetWidth(self.pegView.frame) &&
|
||||
CGRectContainsPoint(self.pegView.frame, touch)) {
|
||||
self.movable = YES;
|
||||
} else {
|
||||
self.movable = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePinch:(UIPinchGestureRecognizer *)pinchGestureRecognizer {
|
||||
if ([pinchGestureRecognizer numberOfTouches] == 2) {
|
||||
[self pickupPegWithGestureRecognizer:pinchGestureRecognizer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePan:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
if ([panGestureRecognizer numberOfTouches] != 2 ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateEnded ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateCancelled ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateFailed) {
|
||||
[self resetTransformAtPoint:[panGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
if (self.isMovable) {
|
||||
self.translation = CGPointMake([panGestureRecognizer translationInView:self].x - self.translationOffset.x,
|
||||
[panGestureRecognizer translationInView:self].y - self.translationOffset.y);
|
||||
[self updateTransformAtPoint:[panGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
self.translationOffset = CGPointMake([panGestureRecognizer translationInView:self].x - self.translation.x,
|
||||
[panGestureRecognizer translationInView:self].y - self.translation.y);
|
||||
if (CGPointEqualToPoint(self.startPoint, CGPointZero)) {
|
||||
[self pickupPegWithGestureRecognizer:panGestureRecognizer];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleRotate:(UIRotationGestureRecognizer *)rotationGestureRecognizer {
|
||||
if ([rotationGestureRecognizer numberOfTouches] != 2 ||
|
||||
rotationGestureRecognizer.state == UIGestureRecognizerStateEnded ||
|
||||
rotationGestureRecognizer.state == UIGestureRecognizerStateCancelled ||
|
||||
rotationGestureRecognizer.state == UIGestureRecognizerStateFailed) {
|
||||
[self resetTransformAtPoint:[rotationGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
if (self.isMovable) {
|
||||
self.rotation = rotationGestureRecognizer.rotation - self.rotationOffset;
|
||||
[self updateTransformAtPoint:[rotationGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
self.rotationOffset = rotationGestureRecognizer.rotation - self.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateTransformAtPoint:(CGPoint)point {
|
||||
self.pegView.transform = CGAffineTransformMakeTranslation(self.translation.x, self.translation.y);
|
||||
self.pegView.transform = CGAffineTransformRotate(self.pegView.transform, self.rotation);
|
||||
[self pegViewDidMoveAtPoint:point];
|
||||
}
|
||||
|
||||
- (void)resetTransformAtPoint:(CGPoint)point {
|
||||
if (!self.hasMoveEnded) {
|
||||
self.movable = NO;
|
||||
self.moveEnded = YES;
|
||||
|
||||
self.pinchRecognizer.enabled = NO;
|
||||
self.panRecognizer.enabled = NO;
|
||||
self.rotationRecognizer.enabled = NO;
|
||||
|
||||
BOOL animated = ![self pegViewMoveDidEndAtPoint:point];
|
||||
self.pegView.hidden = !animated;
|
||||
|
||||
[UIView animateWithDuration:animated ? 0.15f : 0.0f
|
||||
delay:0.0f
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^(){
|
||||
self.pegView.transform = CGAffineTransformIdentity;
|
||||
self.pegView.alpha = 1.0f;
|
||||
}
|
||||
completion:^(BOOL finished){
|
||||
self.rotation = 0.0f;
|
||||
self.rotationOffset = 0.0f;
|
||||
self.translation = CGPointZero;
|
||||
self.translationOffset = CGPointZero;
|
||||
self.pinchRecognizer.enabled = YES;
|
||||
self.panRecognizer.enabled = YES;
|
||||
self.rotationRecognizer.enabled = YES;
|
||||
self.moveEnded = NO;
|
||||
self.pegView.hidden = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
||||
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - peg view delegate
|
||||
|
||||
- (void)pegViewDidMoveAtPoint:(CGPoint)point {
|
||||
self.directionView.hidden = YES;
|
||||
|
||||
if (CGPointEqualToPoint(self.startPoint, CGPointZero)) {
|
||||
self.startPoint = point;
|
||||
}
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestPlaceDidProgress:)]) {
|
||||
[self.delegate holePegTestPlaceDidProgress:self];
|
||||
}
|
||||
|
||||
if (self.holeView.isSuccess) {
|
||||
self.holeView.success = NO;
|
||||
}
|
||||
|
||||
if ([self holeViewContainsPegView]) {
|
||||
self.pegView.alpha = 1.0f;
|
||||
} else {
|
||||
self.pegView.alpha = 0.2f;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)pegViewMoveDidEndAtPoint:(CGPoint)point {
|
||||
self.directionView.hidden = NO;
|
||||
|
||||
BOOL succeeded = NO;
|
||||
if ([self holeViewContainsPegView]) {
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestPlaceDidSucceed:withDistance:)]) {
|
||||
CGFloat distance = hypotf(point.x - self.startPoint.x, point.y - self.startPoint.y);
|
||||
[self.delegate holePegTestPlaceDidSucceed:self withDistance:distance];
|
||||
}
|
||||
self.holeView.success = YES;
|
||||
succeeded = YES;
|
||||
} else {
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestPlaceDidFail:)]) {
|
||||
[self.delegate holePegTestPlaceDidFail:self];
|
||||
}
|
||||
self.holeView.success = NO;
|
||||
}
|
||||
self.startPoint = CGPointZero;
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
- (BOOL)holeViewContainsPegView {
|
||||
CGRect detectionFrame = CGRectMake(CGRectGetMidX(self.holeView.frame) - (self.threshold * CGRectGetWidth(self.holeView.frame) / 2),
|
||||
CGRectGetMidY(self.holeView.frame) - (self.threshold * CGRectGetHeight(self.holeView.frame) / 2),
|
||||
self.threshold * CGRectGetWidth(self.holeView.frame),
|
||||
self.threshold * CGRectGetHeight(self.holeView.frame));
|
||||
|
||||
CGPoint pegCenter = CGPointMake(CGRectGetMaxX(self.pegView.frame) - CGRectGetWidth(self.pegView.frame) / 2,
|
||||
CGRectGetMaxY(self.pegView.frame) - CGRectGetHeight(self.pegView.frame) / 2);
|
||||
|
||||
if (CGRectContainsPoint(detectionFrame, pegCenter)) {
|
||||
if (self.isRotated) {
|
||||
double rotation = atan2(self.pegView.transform.b, self.pegView.transform.a);
|
||||
double angle = fmod(fabs(rotation), M_PI_2);
|
||||
if (angle > M_PI_4 - degreesToRadians(ORKOrientationThreshold) &&
|
||||
angle < M_PI_4 + degreesToRadians(ORKOrientationThreshold)) {
|
||||
return YES;
|
||||
}
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKDefines.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlaceHoleView : UIView <CAAnimationDelegate>
|
||||
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
@property (nonatomic, assign, getter = isSuccess) BOOL success;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHolePegTestPlaceHoleView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
static const CGFloat ORKPlaceHoleViewRotation = 45.0f;
|
||||
|
||||
|
||||
@interface ORKHolePegTestPlaceHoleView ()
|
||||
|
||||
@property (nonatomic, strong) CAShapeLayer *checkLayer;
|
||||
@property (nonatomic, strong) CAShapeLayer *crossLayer;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlaceHoleView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
UIBezierPath *path = [[UIBezierPath alloc] init];
|
||||
[path moveToPoint:CGPointMake(27.7f, 46.9f)];
|
||||
[path addLineToPoint:CGPointMake(36.1f, 56.3f)];
|
||||
[path addLineToPoint:CGPointMake(62.8f, 30.3f)];
|
||||
path.lineCapStyle = kCGLineCapRound;
|
||||
path.lineWidth = 3.6f;
|
||||
|
||||
CAShapeLayer *checkLayer = [CAShapeLayer new];
|
||||
checkLayer.path = path.CGPath;
|
||||
checkLayer.lineWidth = 3.6f;
|
||||
checkLayer.lineCap = kCALineCapRound;
|
||||
checkLayer.lineJoin = kCALineJoinRound;
|
||||
checkLayer.frame = self.layer.bounds;
|
||||
checkLayer.strokeColor = self.tintColor.CGColor;
|
||||
checkLayer.backgroundColor = [UIColor clearColor].CGColor;
|
||||
checkLayer.fillColor = nil;
|
||||
self.checkLayer = checkLayer;
|
||||
|
||||
self.opaque = NO;
|
||||
self.success = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize {
|
||||
return CGSizeMake(self.frame.size.width, self.frame.size.height);
|
||||
}
|
||||
|
||||
#pragma mark - drawing method
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.checkLayer.strokeColor = self.tintColor.CGColor;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setSuccess:(BOOL)success
|
||||
{
|
||||
_success = success;
|
||||
[self.checkLayer removeFromSuperlayer];
|
||||
[self.crossLayer removeFromSuperlayer];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setRotated:(BOOL)rotated
|
||||
{
|
||||
_rotated = rotated;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(bounds, 1.0f, 1.0f)];
|
||||
path.lineWidth = 2.0f;
|
||||
[self.tintColor setStroke];
|
||||
[path stroke];
|
||||
|
||||
if (self.isSuccess) {
|
||||
[self.layer addSublayer:self.checkLayer];
|
||||
|
||||
CAMediaTimingFunction *timing = [[CAMediaTimingFunction alloc] initWithControlPoints:0.180739998817444
|
||||
:0
|
||||
:0.577960014343262
|
||||
:0.918200016021729];
|
||||
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
[animation setTimingFunction:timing];
|
||||
[animation setFillMode:kCAFillModeBoth];
|
||||
animation.fromValue = @(0);
|
||||
animation.toValue = @(1);
|
||||
animation.duration = 0.3f;
|
||||
animation.delegate = self;
|
||||
[self.checkLayer addAnimation:animation forKey:@"strokeEnd"];
|
||||
} else if (self.isRotated) {
|
||||
UIBezierPath *crossPath = [[UIBezierPath alloc] init];
|
||||
[crossPath moveToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 1/4)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 7/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 1/4, CGRectGetHeight(bounds) * 7/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 1/4, CGRectGetHeight(bounds) * 9/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 9/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 3/4)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 3/4)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 9/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 3/4, CGRectGetHeight(bounds) * 9/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 3/4, CGRectGetHeight(bounds) * 7/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 7/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 1/4)];
|
||||
[crossPath closePath];
|
||||
|
||||
CAShapeLayer *crossLayer = [[CAShapeLayer alloc] init];
|
||||
crossLayer.path = crossPath.CGPath;
|
||||
crossLayer.bounds = CGPathGetBoundingBox(crossLayer.path);
|
||||
crossLayer.anchorPoint = CGPointMake(0.5, 0.5);
|
||||
crossLayer.fillColor = self.tintColor.CGColor;
|
||||
|
||||
CATransform3D transform = CATransform3DMakeTranslation(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 1);
|
||||
transform = CATransform3DRotate(transform, ORKPlaceHoleViewRotation * (M_PI / 180), 0, 0, 1);
|
||||
crossLayer.transform = transform;
|
||||
|
||||
self.crossLayer = crossLayer;
|
||||
|
||||
[self.layer addSublayer:self.crossLayer];
|
||||
}
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
strongSelf.success = NO;
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKDefines.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlacePegView : UIView
|
||||
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHolePegTestPlacePegView.h"
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlacePegView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.opaque = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize {
|
||||
return CGSizeMake(self.frame.size.width, self.frame.size.height);
|
||||
}
|
||||
|
||||
#pragma mark - drawing method
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setRotated:(BOOL)rotated
|
||||
{
|
||||
_rotated = rotated;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:bounds];
|
||||
|
||||
if (self.isRotated) {
|
||||
[path moveToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 1/4)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 7/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 1/4, CGRectGetHeight(bounds) * 7/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 1/4, CGRectGetHeight(bounds) * 9/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 9/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 3/4)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 3/4)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 9/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 3/4, CGRectGetHeight(bounds) * 9/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 3/4, CGRectGetHeight(bounds) * 7/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 7/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 1/4)];
|
||||
[path closePath];
|
||||
}
|
||||
|
||||
[self.tintColor setFill];
|
||||
[path fill];
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlaceStep : ORKActiveStep
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign, getter = isDominantHandTested) BOOL dominantHandTested;
|
||||
@property (nonatomic, assign) NSInteger numberOfPegs;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHolePegTestPlaceStep.h"
|
||||
#import "ORKHolePegTestPlaceStepViewController.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlaceStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKHolePegTestPlaceStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
int const ORKHolePegTestMinimumNumberOfPegs = 1;
|
||||
|
||||
double const ORKHolePegTestMinimumThreshold = 0.0f;
|
||||
double const ORKHolePegTestMaximumThreshold = 1.0f;
|
||||
|
||||
NSTimeInterval const ORKHolePegTestMinimumDuration = 1.0f;
|
||||
|
||||
if (self.movingDirection != ORKBodySagittalLeft &&
|
||||
self.movingDirection != ORKBodySagittalRight) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"moving direction should be left or right."] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.numberOfPegs < ORKHolePegTestMinimumNumberOfPegs) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"number of pegs must be greater than or equal to %@.", @(ORKHolePegTestMinimumNumberOfPegs)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.threshold < ORKHolePegTestMinimumThreshold ||
|
||||
self.threshold > ORKHolePegTestMaximumThreshold) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"threshold must be greater than or equal to %@ and lower or equal to %@.", @(ORKHolePegTestMinimumThreshold), @(ORKHolePegTestMaximumThreshold)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.stepDuration < ORKHolePegTestMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_ENUM(aDecoder, movingDirection);
|
||||
ORK_DECODE_BOOL(aDecoder, dominantHandTested);
|
||||
ORK_DECODE_INTEGER(aDecoder, numberOfPegs);
|
||||
ORK_DECODE_DOUBLE(aDecoder, threshold);
|
||||
ORK_DECODE_BOOL(aDecoder, rotated);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_ENUM(aCoder, movingDirection);
|
||||
ORK_ENCODE_BOOL(aCoder, dominantHandTested);
|
||||
ORK_ENCODE_INTEGER(aCoder, numberOfPegs);
|
||||
ORK_ENCODE_DOUBLE(aCoder, threshold);
|
||||
ORK_ENCODE_BOOL(aCoder, rotated);
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
__typeof(self) step = [super copyWithZone:zone];
|
||||
step.movingDirection = self.movingDirection;
|
||||
step.dominantHandTested = self.dominantHandTested;
|
||||
step.numberOfPegs = self.numberOfPegs;
|
||||
step.threshold = self.threshold;
|
||||
step.rotated = self.rotated;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [super hash] ^ self.movingDirection ^ self.dominantHandTested ^ self.numberOfPegs ^ (NSInteger)(self.threshold * 100) ^ self.rotated;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.movingDirection == castObject.movingDirection) &&
|
||||
(self.dominantHandTested == castObject.dominantHandTested) &&
|
||||
(self.numberOfPegs == castObject.numberOfPegs) &&
|
||||
(self.threshold == castObject.threshold) &&
|
||||
(self.rotated == castObject.rotated));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlaceStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHolePegTestPlaceStepViewController.h"
|
||||
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKHolePegTestPlaceContentView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKTaskViewController.h"
|
||||
|
||||
#import "ORKHolePegTestPlaceStep.h"
|
||||
#import "ORKNavigableOrderedTask.h"
|
||||
#import "ORKResult.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@interface ORKHolePegTestPlaceStepViewController () <ORKHolePegTestPlaceContentViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *samples;
|
||||
@property (nonatomic, strong) ORKHolePegTestPlaceContentView *holePegTestPlaceContentView;
|
||||
@property (nonatomic, assign) NSTimeInterval sampleStart;
|
||||
@property (nonatomic, assign) NSUInteger successes;
|
||||
@property (nonatomic, assign) NSUInteger failures;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlaceStepViewController
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
self.suspendIfInactive = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKHolePegTestPlaceStep *)holePegTestPlaceStep {
|
||||
return (ORKHolePegTestPlaceStep *)self.step;
|
||||
}
|
||||
|
||||
- (void)initializeInternalButtonItems {
|
||||
[super initializeInternalButtonItems];
|
||||
|
||||
// Don't show next button
|
||||
self.internalContinueButtonItem = nil;
|
||||
self.internalDoneButtonItem = nil;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.holePegTestPlaceContentView = [[ORKHolePegTestPlaceContentView alloc] initWithMovingDirection:[self holePegTestPlaceStep].movingDirection
|
||||
rotated:[self holePegTestPlaceStep].rotated];
|
||||
self.holePegTestPlaceContentView.threshold = [self holePegTestPlaceStep].threshold;
|
||||
self.holePegTestPlaceContentView.delegate = self;
|
||||
self.activeStepView.activeCustomView = self.holePegTestPlaceContentView;
|
||||
self.activeStepView.stepViewFillsAvailableSpace = YES;
|
||||
}
|
||||
|
||||
#pragma mark - step life cycle methods
|
||||
|
||||
- (void)start {
|
||||
self.successes = 0;
|
||||
self.failures = 0;
|
||||
self.samples = [NSMutableArray array];
|
||||
[self.holePegTestPlaceContentView setProgress:0.001f animated:NO];
|
||||
|
||||
[super start];
|
||||
}
|
||||
|
||||
#pragma mark - result methods
|
||||
|
||||
- (ORKStepResult *)result {
|
||||
ORKStepResult *sResult = [super result];
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKHolePegTestResult *holePegTestResult = [[ORKHolePegTestResult alloc] initWithIdentifier:self.step.identifier];
|
||||
holePegTestResult.movingDirection = [self holePegTestPlaceStep].movingDirection;
|
||||
holePegTestResult.dominantHandTested = [self holePegTestPlaceStep].isDominantHandTested;
|
||||
holePegTestResult.numberOfPegs = [self holePegTestPlaceStep].numberOfPegs;
|
||||
holePegTestResult.threshold = [self holePegTestPlaceStep].threshold;
|
||||
holePegTestResult.rotated = [self holePegTestPlaceStep].isRotated;
|
||||
holePegTestResult.totalSuccesses = self.successes;
|
||||
holePegTestResult.totalFailures = self.failures;
|
||||
holePegTestResult.totalTime = [self holePegTestPlaceStep].stepDuration - self.timeRemaining;
|
||||
double totalDistance = 0.0;
|
||||
for (ORKHolePegTestSample *sample in self.samples) {
|
||||
totalDistance += sample.distance;
|
||||
}
|
||||
holePegTestResult.totalDistance = totalDistance;
|
||||
holePegTestResult.samples = self.samples;
|
||||
|
||||
[results addObject:holePegTestResult];
|
||||
|
||||
sResult.results = [results copy];
|
||||
|
||||
return sResult;
|
||||
}
|
||||
|
||||
- (void)saveSampleWithDistance:(CGFloat)distance {
|
||||
ORKHolePegTestSample *sample = [[ORKHolePegTestSample alloc] init];
|
||||
sample.time = CACurrentMediaTime() - self.sampleStart;
|
||||
sample.distance = distance;
|
||||
self.sampleStart = CACurrentMediaTime();
|
||||
|
||||
[self.samples addObject:sample];
|
||||
}
|
||||
|
||||
#pragma mark - hole peg test content view delegate
|
||||
|
||||
- (NSString *)stepTitle {
|
||||
NSString *title = ([self holePegTestPlaceStep].movingDirection == ORKBodySagittalLeft) ? ORKLocalizedString(@"HOLE_PEG_TEST_PLACE_INSTRUCTION_LEFT_HAND", nil) : ORKLocalizedString(@"HOLE_PEG_TEST_PLACE_INSTRUCTION_RIGHT_HAND", nil);
|
||||
return title;
|
||||
}
|
||||
|
||||
- (void)holePegTestPlaceDidProgress:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView {
|
||||
if (!self.isStarted) {
|
||||
self.sampleStart = CACurrentMediaTime();
|
||||
[self start];
|
||||
}
|
||||
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT_2", nil)];
|
||||
}
|
||||
|
||||
- (void)holePegTestPlaceDidSucceed:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView withDistance:(CGFloat)distance {
|
||||
self.successes++;
|
||||
|
||||
[self saveSampleWithDistance:distance];
|
||||
|
||||
[holePegTestPlaceContentView setProgress:((CGFloat)self.successes / [self holePegTestPlaceStep].numberOfPegs) animated:YES];
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT", nil)];
|
||||
|
||||
if (self.successes >= [self holePegTestPlaceStep].numberOfPegs) {
|
||||
[((ORKNavigableOrderedTask *)self.taskViewController.task) removeNavigationRuleForTriggerStepIdentifier:[self holePegTestPlaceStep].identifier];
|
||||
[self finish];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)holePegTestPlaceDidFail:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView {
|
||||
self.failures++;
|
||||
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT", nil)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKTypes.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ORKHolePegTestRemoveContentViewDelegate;
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestRemoveContentView : ORKActiveStepCustomView
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
||||
- (instancetype)initWithMovingDirection:(ORKBodySagittal)movingDirection NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
@property (nonatomic, weak) id<ORKHolePegTestRemoveContentViewDelegate> delegate;
|
||||
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol ORKHolePegTestRemoveContentViewDelegate <NSObject>
|
||||
|
||||
- (void)holePegTestRemoveDidProgress:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView;
|
||||
- (void)holePegTestRemoveDidSucceed:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView withDistance:(CGFloat)distance;
|
||||
- (void)holePegTestRemoveDidFail:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,358 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHolePegTestRemoveContentView.h"
|
||||
|
||||
#import "ORKDirectionView.h"
|
||||
#import "ORKHolePegTestRemovePegView.h"
|
||||
#import "ORKSeparatorView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
static const CGFloat PegViewDiameter = 88.0f;
|
||||
static const CGFloat PegViewSeparatorWidth = 2.0f;
|
||||
|
||||
|
||||
@interface ORKHolePegTestRemoveContentView () <UIGestureRecognizerDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
@property (nonatomic, strong) ORKHolePegTestRemovePegView *pegView;
|
||||
@property (nonatomic, strong) ORKSeparatorView *separatorView;
|
||||
@property (nonatomic, strong) ORKDirectionView *directionView;
|
||||
@property (nonatomic, strong) UIView *container;
|
||||
@property (nonatomic, copy) NSArray *constraints;
|
||||
|
||||
@property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer;
|
||||
@property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer;
|
||||
@property (nonatomic, assign, getter = isMovable) BOOL movable;
|
||||
@property (nonatomic, assign, getter = hasMoveEnded) BOOL moveEnded;
|
||||
@property (nonatomic, assign) CGPoint translation;
|
||||
@property (nonatomic, assign) CGPoint translationOffset;
|
||||
@property (nonatomic, assign) CGPoint startPoint;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestRemoveContentView
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithMovingDirection:(ORKBodySagittal)movingDirection {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
self.movingDirection = movingDirection;
|
||||
self.opaque = NO;
|
||||
|
||||
self.container = [UIView new];
|
||||
self.container.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
self.progressView = [UIProgressView new];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
[self.progressView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.progressView setAlpha:0];
|
||||
[self addSubview:self.progressView];
|
||||
|
||||
self.pegView = [[ORKHolePegTestRemovePegView alloc] initWithFrame:CGRectMake(0, 0, PegViewDiameter, PegViewDiameter)];
|
||||
[self.pegView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.container addSubview:self.pegView];
|
||||
|
||||
self.separatorView = [[ORKSeparatorView alloc] init];
|
||||
[self.separatorView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.container addSubview:self.separatorView];
|
||||
|
||||
self.directionView = [[ORKDirectionView alloc] initWithOrientation:(self.movingDirection == ORKBodySagittalLeft) ? ORKBodySagittalRight : ORKBodySagittalLeft];
|
||||
[self.directionView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self.directionView];
|
||||
|
||||
[self addSubview:self.container];
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
self.movable = NO;
|
||||
self.moveEnded = NO;
|
||||
self.startPoint = CGPointZero;
|
||||
|
||||
self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handlePinch:)];
|
||||
self.pinchRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.pinchRecognizer];
|
||||
|
||||
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handlePan:)];
|
||||
self.panRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.panRecognizer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
}
|
||||
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
|
||||
[self.progressView setProgress:progress animated:animated];
|
||||
[UIView animateWithDuration:animated ? 0.2 : 0 animations:^{
|
||||
[self.progressView setAlpha:(progress == 0) ? 0 : 1];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateLayoutMargins {
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin * 2, .right = margin * 2};
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
[super setFrame:frame];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
[super setBounds:bounds];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([self.constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
self.constraints = nil;
|
||||
}
|
||||
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _container, _pegView, _separatorView, _directionView);
|
||||
NSDictionary *metrics = @{@"diameter": @(PegViewDiameter), @"separator": @(PegViewSeparatorWidth), @"margin": @((1 + self.threshold) * PegViewDiameter)};
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:(self.movingDirection == ORKBodySagittalLeft) ? @"H:|-[_pegView(diameter)]->=0-[_separatorView(separator)]-(margin)-|" : @"H:|-(margin)-[_separatorView(separator)]->=0-[_pegView(diameter)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:metrics views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_container]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[_pegView(diameter)]-(>=0)-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:metrics views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_separatorView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_progressView][_container]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:metrics views:views]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.directionView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.directionView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
self.constraints = constraintsArray;
|
||||
[self addConstraints:self.constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark - gesture recognizer methods
|
||||
|
||||
- (void)pickupPegWithGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
|
||||
CGPoint touch = [gestureRecognizer locationInView:self];
|
||||
CGPoint touch1 = [gestureRecognizer locationOfTouch:0 inView:self];
|
||||
CGPoint touch2 = [gestureRecognizer locationOfTouch:1 inView:self];
|
||||
double distance = hypot(touch1.x - touch2.x, touch1.y - touch2.y);
|
||||
|
||||
if (distance < 3 * CGRectGetWidth(self.pegView.frame) &&
|
||||
CGRectContainsPoint(self.pegView.frame, touch)) {
|
||||
self.movable = YES;
|
||||
} else {
|
||||
self.movable = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePinch:(UIPinchGestureRecognizer *)pinchGestureRecognizer {
|
||||
if ([pinchGestureRecognizer numberOfTouches] == 2) {
|
||||
[self pickupPegWithGestureRecognizer:pinchGestureRecognizer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePan:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
if ([panGestureRecognizer numberOfTouches] != 2 ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateEnded ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateCancelled ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateFailed) {
|
||||
[self resetTransformAtPoint:[panGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
if (self.isMovable) {
|
||||
self.translation = CGPointMake([panGestureRecognizer translationInView:self].x - self.translationOffset.x,
|
||||
[panGestureRecognizer translationInView:self].y - self.translationOffset.y);
|
||||
[self updateTransformAtPoint:[panGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
self.translationOffset = CGPointMake([panGestureRecognizer translationInView:self].x - self.translation.x,
|
||||
[panGestureRecognizer translationInView:self].y - self.translation.y);
|
||||
if (CGPointEqualToPoint(self.startPoint, CGPointZero)) {
|
||||
[self pickupPegWithGestureRecognizer:panGestureRecognizer];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateTransformAtPoint:(CGPoint)point {
|
||||
self.pegView.transform = CGAffineTransformMakeTranslation(self.translation.x, self.translation.y);
|
||||
[self pegViewDidMoveAtPoint:point];
|
||||
}
|
||||
|
||||
- (void)resetTransformAtPoint:(CGPoint)point {
|
||||
if (!self.hasMoveEnded) {
|
||||
self.movable = NO;
|
||||
self.moveEnded = YES;
|
||||
|
||||
self.pinchRecognizer.enabled = NO;
|
||||
self.panRecognizer.enabled = NO;
|
||||
|
||||
BOOL animated = ![self pegViewMoveDidEndAtPoint:point];
|
||||
|
||||
[UIView animateWithDuration:animated ? 0.15f : 0.0f
|
||||
delay:animated ? 0.0f : 0.30f
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^(){
|
||||
self.pegView.transform = CGAffineTransformIdentity;
|
||||
self.pegView.alpha = 1.0f;
|
||||
}
|
||||
completion:^(BOOL finished){
|
||||
self.translation = CGPointZero;
|
||||
self.translationOffset = CGPointZero;
|
||||
self.pinchRecognizer.enabled = YES;
|
||||
self.panRecognizer.enabled = YES;
|
||||
self.moveEnded = NO;
|
||||
self.pegView.hidden = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
||||
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - peg view delegate
|
||||
|
||||
- (void)pegViewDidMoveAtPoint:(CGPoint)point {
|
||||
self.directionView.hidden = YES;
|
||||
|
||||
if (CGPointEqualToPoint(self.startPoint, CGPointZero)) {
|
||||
self.startPoint = point;
|
||||
}
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestRemoveDidProgress:)]) {
|
||||
[self.delegate holePegTestRemoveDidProgress:self];
|
||||
}
|
||||
|
||||
if (self.pegView.isSuccess) {
|
||||
self.pegView.success = NO;
|
||||
}
|
||||
|
||||
if ([self pegViewBehindLine]) {
|
||||
self.pegView.alpha = 1.0f;
|
||||
} else {
|
||||
self.pegView.alpha = 0.2f;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)pegViewMoveDidEndAtPoint:(CGPoint)point {
|
||||
self.directionView.hidden = NO;
|
||||
|
||||
BOOL succeeded = NO;
|
||||
if ([self pegViewBehindLine]) {
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestRemoveDidSucceed:withDistance:)]) {
|
||||
CGFloat distance = hypotf(point.x - self.startPoint.x, point.y - self.startPoint.y);
|
||||
[self.delegate holePegTestRemoveDidSucceed:self withDistance:distance];
|
||||
}
|
||||
self.pegView.success = YES;
|
||||
succeeded = YES;
|
||||
} else {
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestRemoveDidFail:)]) {
|
||||
[self.delegate holePegTestRemoveDidFail:self];
|
||||
}
|
||||
self.pegView.success = NO;
|
||||
}
|
||||
self.startPoint = CGPointZero;
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
- (BOOL)pegViewBehindLine {
|
||||
if (self.movingDirection == ORKBodySagittalLeft) {
|
||||
if (CGRectGetMinX(self.pegView.frame) > CGRectGetMaxX(self.separatorView.frame)) {
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
if (CGRectGetMaxX(self.pegView.frame) < CGRectGetMinX(self.separatorView.frame)) {
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKDefines.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestRemovePegView : UIView <CAAnimationDelegate>
|
||||
|
||||
@property (nonatomic, assign, getter = isSuccess) BOOL success;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHolePegTestRemovePegView.h"
|
||||
|
||||
|
||||
@interface ORKHolePegTestRemovePegView ()
|
||||
|
||||
@property (nonatomic, strong) CAShapeLayer *checkLayer;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestRemovePegView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
UIBezierPath *path = [[UIBezierPath alloc] init];
|
||||
[path moveToPoint:CGPointMake(27.7f, 46.9f)];
|
||||
[path addLineToPoint:CGPointMake(36.1f, 56.3f)];
|
||||
[path addLineToPoint:CGPointMake(62.8f, 30.3f)];
|
||||
path.lineCapStyle = kCGLineCapRound;
|
||||
path.lineWidth = 3.6f;
|
||||
|
||||
CAShapeLayer *checkLayer = [CAShapeLayer new];
|
||||
checkLayer.path = path.CGPath;
|
||||
checkLayer.lineWidth = 3.6f;
|
||||
checkLayer.lineCap = kCALineCapRound;
|
||||
checkLayer.lineJoin = kCALineJoinRound;
|
||||
checkLayer.frame = self.layer.bounds;
|
||||
checkLayer.strokeColor = [UIColor whiteColor].CGColor;
|
||||
checkLayer.backgroundColor = [UIColor clearColor].CGColor;
|
||||
checkLayer.fillColor = nil;
|
||||
self.checkLayer = checkLayer;
|
||||
|
||||
self.opaque = NO;
|
||||
self.success = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
#pragma mark - drawing method
|
||||
|
||||
- (void)setSuccess:(BOOL)success
|
||||
{
|
||||
_success = success;
|
||||
[self.checkLayer removeFromSuperlayer];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:bounds];
|
||||
path.lineWidth = 2.0f;
|
||||
[self.tintColor setFill];
|
||||
[path fill];
|
||||
|
||||
if (self.isSuccess) {
|
||||
[self.layer addSublayer:self.checkLayer];
|
||||
|
||||
CAMediaTimingFunction *timing = [[CAMediaTimingFunction alloc] initWithControlPoints:0.180739998817444
|
||||
:0
|
||||
:0.577960014343262
|
||||
:0.918200016021729];
|
||||
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
[animation setTimingFunction:timing];
|
||||
[animation setFillMode:kCAFillModeBoth];
|
||||
animation.fromValue = @(0);
|
||||
animation.toValue = @(1);
|
||||
animation.duration = 0.25f;
|
||||
animation.delegate = self;
|
||||
[self.checkLayer addAnimation:animation forKey:@"strokeEnd"];
|
||||
}
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
|
||||
self.success = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestRemoveStep : ORKActiveStep
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign, getter = isDominantHandTested) BOOL dominantHandTested;
|
||||
@property (nonatomic, assign) NSInteger numberOfPegs;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHolePegTestRemoveStep.h"
|
||||
|
||||
#import "ORKHolePegTestRemoveStepViewController.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKHolePegTestRemoveStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKHolePegTestRemoveStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
int const ORKHolePegTestMinimumNumberOfPegs = 1;
|
||||
|
||||
double const ORKHolePegTestMinimumThreshold = 0.0f;
|
||||
double const ORKHolePegTestMaximumThreshold = 1.0f;
|
||||
|
||||
NSTimeInterval const ORKHolePegTestMinimumDuration = 1.0f;
|
||||
|
||||
if (self.movingDirection != ORKBodySagittalLeft &&
|
||||
self.movingDirection != ORKBodySagittalRight) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"moving direction should be left or right."] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.numberOfPegs < ORKHolePegTestMinimumNumberOfPegs) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"number of pegs must be greater than or equal to %@.", @(ORKHolePegTestMinimumNumberOfPegs)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.threshold < ORKHolePegTestMinimumThreshold ||
|
||||
self.threshold > ORKHolePegTestMaximumThreshold) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"threshold must be greater than or equal to %@ and lower or equal to %@.", @(ORKHolePegTestMinimumThreshold), @(ORKHolePegTestMaximumThreshold)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.stepDuration < ORKHolePegTestMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_ENUM(aDecoder, movingDirection);
|
||||
ORK_DECODE_BOOL(aDecoder, dominantHandTested);
|
||||
ORK_DECODE_INTEGER(aDecoder, numberOfPegs);
|
||||
ORK_DECODE_DOUBLE(aDecoder, threshold);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_ENUM(aCoder, movingDirection);
|
||||
ORK_ENCODE_BOOL(aCoder, dominantHandTested);
|
||||
ORK_ENCODE_INTEGER(aCoder, numberOfPegs);
|
||||
ORK_ENCODE_DOUBLE(aCoder, threshold);
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
__typeof(self) step = [super copyWithZone:zone];
|
||||
step.movingDirection = self.movingDirection;
|
||||
step.dominantHandTested = self.dominantHandTested;
|
||||
step.numberOfPegs = self.numberOfPegs;
|
||||
step.threshold = self.threshold;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [super hash] ^ self.movingDirection ^ self.dominantHandTested ^ self.numberOfPegs ^ (NSInteger)(self.threshold * 100);
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.movingDirection == castObject.movingDirection) &&
|
||||
(self.dominantHandTested == castObject.dominantHandTested) &&
|
||||
(self.numberOfPegs == castObject.numberOfPegs) &&
|
||||
(self.threshold == castObject.threshold));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestRemoveStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHolePegTestRemoveStepViewController.h"
|
||||
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKHolePegTestRemoveContentView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKTaskViewController.h"
|
||||
|
||||
#import "ORKHolePegTestRemoveStep.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKResult.h"
|
||||
|
||||
|
||||
@interface ORKHolePegTestRemoveStepViewController () <ORKHolePegTestRemoveContentViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *samples;
|
||||
@property (nonatomic, strong) ORKHolePegTestRemoveContentView *holePegTestRemoveContentView;
|
||||
@property (nonatomic, assign) NSTimeInterval sampleStart;
|
||||
@property (nonatomic, assign) NSUInteger successes;
|
||||
@property (nonatomic, assign) NSUInteger failures;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestRemoveStepViewController
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
self.suspendIfInactive = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKHolePegTestRemoveStep *)holePegTestRemoveStep {
|
||||
return (ORKHolePegTestRemoveStep *)self.step;
|
||||
}
|
||||
|
||||
- (void)initializeInternalButtonItems {
|
||||
[super initializeInternalButtonItems];
|
||||
|
||||
// Don't show next button
|
||||
self.internalContinueButtonItem = nil;
|
||||
self.internalDoneButtonItem = nil;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.holePegTestRemoveContentView = [[ORKHolePegTestRemoveContentView alloc] initWithMovingDirection:[self holePegTestRemoveStep].movingDirection];
|
||||
self.holePegTestRemoveContentView.threshold = [self holePegTestRemoveStep].threshold;
|
||||
self.holePegTestRemoveContentView.delegate = self;
|
||||
self.activeStepView.activeCustomView = self.holePegTestRemoveContentView;
|
||||
self.activeStepView.stepViewFillsAvailableSpace = YES;
|
||||
|
||||
NSString *identifier = [[self holePegTestRemoveStep].identifier stringByReplacingOccurrencesOfString:@"remove" withString:@"place"];
|
||||
NSTimeInterval placeStepDuration = ((ORKHolePegTestResult *)[[self.taskViewController.result stepResultForStepIdentifier:identifier].results firstObject]).totalTime;
|
||||
[self holePegTestRemoveStep].stepDuration -= placeStepDuration;
|
||||
|
||||
[self start];
|
||||
}
|
||||
|
||||
#pragma mark - step life cycle methods
|
||||
|
||||
- (void)start {
|
||||
self.sampleStart = CACurrentMediaTime();
|
||||
self.successes = 0;
|
||||
self.failures = 0;
|
||||
self.samples = [NSMutableArray array];
|
||||
[self.holePegTestRemoveContentView setProgress:0.001f animated:NO];
|
||||
|
||||
[super start];
|
||||
}
|
||||
|
||||
#pragma mark - result methods
|
||||
|
||||
- (ORKStepResult *)result {
|
||||
ORKStepResult *sResult = [super result];
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKHolePegTestResult *holePegTestResult = [[ORKHolePegTestResult alloc] initWithIdentifier:self.step.identifier];
|
||||
holePegTestResult.movingDirection = [self holePegTestRemoveStep].movingDirection;
|
||||
holePegTestResult.dominantHandTested = [self holePegTestRemoveStep].isDominantHandTested;
|
||||
holePegTestResult.numberOfPegs = [self holePegTestRemoveStep].numberOfPegs;
|
||||
holePegTestResult.threshold = [self holePegTestRemoveStep].threshold;
|
||||
holePegTestResult.rotated = NO;
|
||||
holePegTestResult.totalSuccesses = self.successes;
|
||||
holePegTestResult.totalFailures = self.failures;
|
||||
holePegTestResult.totalTime = [self holePegTestRemoveStep].stepDuration - self.timeRemaining;
|
||||
double totalDistance = 0.0;
|
||||
for (ORKHolePegTestSample *sample in self.samples) {
|
||||
totalDistance += sample.distance;
|
||||
}
|
||||
holePegTestResult.totalDistance = totalDistance;
|
||||
holePegTestResult.samples = self.samples;
|
||||
|
||||
[results addObject:holePegTestResult];
|
||||
|
||||
sResult.results = [results copy];
|
||||
|
||||
return sResult;
|
||||
}
|
||||
|
||||
- (void)saveSampleWithDistance:(CGFloat)distance {
|
||||
ORKHolePegTestSample *sample = [[ORKHolePegTestSample alloc] init];
|
||||
sample.time = CACurrentMediaTime() - self.sampleStart;
|
||||
sample.distance = distance;
|
||||
self.sampleStart = CACurrentMediaTime();
|
||||
|
||||
[self.samples addObject:sample];
|
||||
}
|
||||
|
||||
#pragma mark - hole peg test content view delegate
|
||||
|
||||
- (NSString *)stepTitle {
|
||||
NSString *title = ([self holePegTestRemoveStep].movingDirection == ORKBodySagittalLeft) ? ORKLocalizedString(@"HOLE_PEG_TEST_REMOVE_INSTRUCTION_RIGHT_HAND", nil) : ORKLocalizedString(@"HOLE_PEG_TEST_REMOVE_INSTRUCTION_LEFT_HAND", nil);
|
||||
return title;
|
||||
}
|
||||
|
||||
- (void)holePegTestRemoveDidProgress:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView {
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT_2", nil)];
|
||||
}
|
||||
|
||||
- (void)holePegTestRemoveDidSucceed:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView withDistance:(CGFloat)distance {
|
||||
self.successes++;
|
||||
|
||||
[self saveSampleWithDistance:distance];
|
||||
|
||||
[holePegTestRemoveContentView setProgress:((CGFloat)self.successes / [self holePegTestRemoveStep].numberOfPegs) animated:YES];
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT", nil)];
|
||||
|
||||
if (self.successes >= [self holePegTestRemoveStep].numberOfPegs) {
|
||||
[self finish];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)holePegTestRemoveDidFail:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView {
|
||||
self.failures++;
|
||||
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT", nil)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
@import CoreLocation;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKLocationRecorder` class represents a recorder for collecting location data from CoreLocation.
|
||||
|
||||
Because location data is sensitive information, you need to take special care in handling it, including removoing or otherwise preparing it for a
|
||||
anonymous data set.
|
||||
|
||||
The accuracy of location data may be limited indoors.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKLocationRecorder : ORKRecorder
|
||||
|
||||
/**
|
||||
Returns an initialized location recorder.
|
||||
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the location data should be stored.
|
||||
|
||||
@return An initialized location recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
|
||||
/**
|
||||
The location manager, if any, being used by this recorder.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable, readonly) CLLocationManager *locationManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKLocationRecorder.h"
|
||||
|
||||
#import "ORKDataLogger.h"
|
||||
|
||||
#import "ORKRecorder_Internal.h"
|
||||
|
||||
#import "CLLocation+ORKJSONDictionary.h"
|
||||
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
|
||||
@interface ORKLocationRecorder () <CLLocationManagerDelegate> {
|
||||
ORKDataLogger *_logger;
|
||||
NSError *_recordingError;
|
||||
BOOL _started;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong, nullable) CLLocationManager *locationManager;
|
||||
|
||||
@property (nonatomic) NSTimeInterval uptime;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKLocationRecorder
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
self.continuesInBackground = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_logger finishCurrentLog];
|
||||
}
|
||||
|
||||
- (NSString *)recorderType {
|
||||
return @"location";
|
||||
}
|
||||
|
||||
- (CLLocationManager *)createLocationManager {
|
||||
return [[CLLocationManager alloc] init];
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (!_logger) {
|
||||
NSError *error = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&error];
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.locationManager = [self createLocationManager];
|
||||
if ([CLLocationManager authorizationStatus] <= kCLAuthorizationStatusDenied) {
|
||||
[self.locationManager requestWhenInUseAuthorization];
|
||||
}
|
||||
self.locationManager.pausesLocationUpdatesAutomatically = NO;
|
||||
self.locationManager.delegate = self;
|
||||
|
||||
if (!self.locationManager) {
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder": self}];
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
self.uptime = [NSProcessInfo processInfo].systemUptime;
|
||||
[self.locationManager startUpdatingLocation];
|
||||
}
|
||||
|
||||
- (void)doStopRecording {
|
||||
[self.locationManager stopUpdatingLocation];
|
||||
self.locationManager.delegate = nil;
|
||||
self.locationManager = nil;
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
[self doStopRecording];
|
||||
[_logger finishCurrentLog];
|
||||
|
||||
NSError *error = _recordingError;
|
||||
_recordingError = nil;
|
||||
__block NSURL *fileUrl = nil;
|
||||
[_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
fileUrl = logFileUrl;
|
||||
} error:&error];
|
||||
|
||||
[self reportFileResultWithFile:fileUrl error:error];
|
||||
|
||||
[super stop];
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager
|
||||
didUpdateLocations:(NSArray *)locations {
|
||||
BOOL success = YES;
|
||||
NSParameterAssert(locations.count >= 0);
|
||||
NSError *error = nil;
|
||||
if (locations) {
|
||||
NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:locations.count];
|
||||
[locations enumerateObjectsUsingBlock:^(CLLocation *obj, NSUInteger idx, BOOL *stop) {
|
||||
NSDictionary *d = [obj ork_JSONDictionary];
|
||||
[dictionaries addObject:d];
|
||||
}];
|
||||
|
||||
success = [_logger appendObjects:dictionaries error:&error];
|
||||
}
|
||||
if (!success) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_recordingError = error;
|
||||
[self stop];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
[super finishRecordingWithError:nil];
|
||||
}
|
||||
|
||||
- (BOOL)isRecording {
|
||||
return [CLLocationManager locationServicesEnabled] && (self.locationManager != nil) && ([CLLocationManager authorizationStatus] > kCLAuthorizationStatusDenied);
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
|
||||
_logger = nil;
|
||||
}
|
||||
|
||||
- (NSString *)mimeType {
|
||||
return @"application/json";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKLocationRecorderConfiguration
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
return [super initWithIdentifier:identifier];
|
||||
}
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKLocationRecorder alloc] initWithIdentifier:self.identifier step:step outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
return isParentSame;
|
||||
}
|
||||
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
return ORKPermissionCoreLocation;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKTypes.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKPSATKeyboardView;
|
||||
|
||||
@interface ORKPSATContentView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, strong) ORKPSATKeyboardView *keyboardView;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
||||
- (instancetype)initWithPresentationMode:(ORKPSATPresentationMode)presentationMode NS_DESIGNATED_INITIALIZER;
|
||||
- (void)setEnabled:(BOOL)enabled;
|
||||
- (void)setAddition:(NSUInteger)additionIndex forTotal:(NSUInteger)totalAddition withDigit:(NSNumber *)digit;
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKPSATContentView.h"
|
||||
|
||||
#import "ORKBorderedButton.h"
|
||||
#import "ORKPSATKeyboardView.h"
|
||||
#import "ORKTapCountLabel.h"
|
||||
#import "ORKVoiceEngine.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@interface ORKPSATContentView ()
|
||||
|
||||
@property (nonatomic, assign, getter = isAuditory) BOOL auditory;
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
@property (nonatomic, strong) ORKTapCountLabel *digitLabel;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKPSATContentView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [self initWithPresentationMode:ORKPSATPresentationModeAuditory];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPresentationMode:(ORKPSATPresentationMode)presentationMode {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
|
||||
if (self) {
|
||||
|
||||
_digitLabel = [ORKTapCountLabel new];
|
||||
_digitLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_digitLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_digitLabel];
|
||||
_auditory = (presentationMode & ORKPSATPresentationModeAuditory) ? YES : NO;
|
||||
if (!(presentationMode & ORKPSATPresentationModeVisual)) {
|
||||
_digitLabel.hidden = YES;
|
||||
}
|
||||
|
||||
_progressView = [UIProgressView new];
|
||||
_progressView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_progressView.progressTintColor = [self tintColor];
|
||||
[_progressView setAlpha:0];
|
||||
[self addSubview:_progressView];
|
||||
|
||||
_keyboardView = [ORKPSATKeyboardView new];
|
||||
_keyboardView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_keyboardView];
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
self.keyboardView.enabled = enabled;
|
||||
}
|
||||
|
||||
- (void)setAddition:(NSUInteger)additionIndex forTotal:(NSUInteger)totalAddition withDigit:(NSNumber *)digit {
|
||||
if (digit.integerValue == -1) {
|
||||
self.digitLabel.textColor = [[UIColor blackColor] colorWithAlphaComponent:0.3f];
|
||||
self.digitLabel.text = ORKLocalizedString(@"PSAT_NO_DIGIT", nil);
|
||||
} else {
|
||||
[self.keyboardView.selectedAnswerButton setSelected:NO];
|
||||
self.digitLabel.textColor = nil;
|
||||
self.digitLabel.text = [NSNumberFormatter localizedStringFromNumber:digit
|
||||
numberStyle:NSNumberFormatterNoStyle];
|
||||
if (self.isAuditory) {
|
||||
[[ORKVoiceEngine sharedVoiceEngine] speakInt:digit.integerValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
}
|
||||
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
|
||||
[self.progressView setProgress:progress animated:animated];
|
||||
[UIView animateWithDuration:animated ? 0.2 : 0 animations:^{
|
||||
[self.progressView setAlpha:(progress == 0) ? 0 : 1];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
|
||||
const CGFloat ORKPSATKeyboardWidth = ORKGetMetricForWindow(ORKScreenMetricPSATKeyboardViewWidth, self.window);
|
||||
const CGFloat ORKPSATKeyboardHeight = ORKGetMetricForWindow(ORKScreenMetricPSATKeyboardViewHeight, self.window);
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _digitLabel, _keyboardView);
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_keyboardView(==keyboardWidth)]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:@{ @"keyboardWidth": @(ORKPSATKeyboardWidth) }
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_keyboardView(==keyboardHeight)]"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:@{ @"keyboardHeight": @(ORKPSATKeyboardHeight) }
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_progressView]-[_digitLabel]-(>=10)-[_keyboardView]-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKDefines.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKBorderedButton;
|
||||
@protocol ORKPSATKeyboardViewDelegate;
|
||||
|
||||
@interface ORKPSATKeyboardView : UIView
|
||||
|
||||
@property (nonatomic, weak) id<ORKPSATKeyboardViewDelegate> delegate;
|
||||
@property (nonatomic, weak) ORKBorderedButton *selectedAnswerButton;
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled;
|
||||
|
||||
@end
|
||||
|
||||
@protocol ORKPSATKeyboardViewDelegate <NSObject>
|
||||
|
||||
- (void)keyboardView:(ORKPSATKeyboardView *)keyboardView didSelectAnswer:(NSInteger)answer;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKPSATKeyboardView.h"
|
||||
|
||||
#import "ORKBorderedButton.h"
|
||||
|
||||
|
||||
NSUInteger const ORKPSATMinimumAnswer = 3;
|
||||
NSUInteger const ORKPSATMaximumAnswer = 17;
|
||||
|
||||
@interface ORKPSATKeyboardView ()
|
||||
|
||||
@property (nonatomic, strong, readonly) NSArray *answerButtons;
|
||||
@property (nonatomic, strong) NSArray *constraints;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKPSATKeyboardView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSMutableArray *buttonsArray = [[NSMutableArray alloc] initWithCapacity:(ORKPSATMaximumAnswer - ORKPSATMinimumAnswer) + 1];
|
||||
ORKBorderedButton *answerButton = nil;
|
||||
for (int i = ORKPSATMinimumAnswer; i <= ORKPSATMaximumAnswer; i++) {
|
||||
answerButton = [self answerButtonWithTitle:[NSNumberFormatter localizedStringFromNumber:@(i)
|
||||
numberStyle:NSNumberFormatterNoStyle]];
|
||||
[buttonsArray addObject:answerButton];
|
||||
[self addSubview:answerButton];
|
||||
}
|
||||
_answerButtons = [NSArray arrayWithArray:buttonsArray];
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKBorderedButton *)answerButtonWithTitle:(NSString *)title {
|
||||
ORKBorderedButton *answerButton = [ORKBorderedButton new];
|
||||
answerButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[answerButton setTitle:title forState:UIControlStateNormal];
|
||||
[answerButton addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside];
|
||||
answerButton.accessibilityTraits |= UIAccessibilityTraitKeyboardKey;
|
||||
return answerButton;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
for (ORKBorderedButton *answerButton in self.answerButtons) {
|
||||
[answerButton setEnabled:enabled];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
ORKBorderedButton *answer3Button = self.answerButtons[0];
|
||||
ORKBorderedButton *answer4Button = self.answerButtons[1];
|
||||
ORKBorderedButton *answer5Button = self.answerButtons[2];
|
||||
ORKBorderedButton *answer6Button = self.answerButtons[3];
|
||||
ORKBorderedButton *answer7Button = self.answerButtons[4];
|
||||
ORKBorderedButton *answer8Button = self.answerButtons[5];
|
||||
ORKBorderedButton *answer9Button = self.answerButtons[6];
|
||||
ORKBorderedButton *answer10Button = self.answerButtons[7];
|
||||
ORKBorderedButton *answer11Button = self.answerButtons[8];
|
||||
ORKBorderedButton *answer12Button = self.answerButtons[9];
|
||||
ORKBorderedButton *answer13Button = self.answerButtons[10];
|
||||
ORKBorderedButton *answer14Button = self.answerButtons[11];
|
||||
ORKBorderedButton *answer15Button = self.answerButtons[12];
|
||||
ORKBorderedButton *answer16Button = self.answerButtons[13];
|
||||
ORKBorderedButton *answer17Button = self.answerButtons[14];
|
||||
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(answer3Button, answer4Button, answer5Button, answer6Button, answer7Button, answer8Button, answer9Button, answer10Button, answer11Button, answer12Button, answer13Button, answer14Button, answer15Button, answer16Button, answer17Button);
|
||||
|
||||
// First line of answer buttons
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[answer3Button]-[answer4Button(==answer3Button)]-[answer5Button(==answer3Button)]-[answer6Button(==answer3Button)]-[answer7Button(==answer3Button)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY|NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
|
||||
metrics:nil views:views]];
|
||||
|
||||
// Second line of answer buttons
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[answer8Button]-[answer9Button(==answer8Button)]-[answer10Button(==answer8Button)]-[answer11Button(==answer8Button)]-[answer12Button(==answer8Button)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY|NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
|
||||
metrics:nil views:views]];
|
||||
|
||||
// Third line of answer buttons
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[answer13Button]-[answer14Button(==answer13Button)]-[answer15Button(==answer13Button)]-[answer16Button(==answer13Button)]-[answer17Button(==answer13Button)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY|NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
|
||||
metrics:nil views:views]];
|
||||
|
||||
// Align vertically
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[answer3Button]-[answer8Button(==answer3Button)]-[answer13Button(==answer3Button)]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark buttonAction
|
||||
|
||||
- (IBAction)buttonPressed:(id)button forEvent:(UIEvent *)event {
|
||||
ORKBorderedButton *tappedAnswerButton = (ORKBorderedButton *)button;
|
||||
|
||||
[self.selectedAnswerButton setSelected:NO];
|
||||
self.selectedAnswerButton = tappedAnswerButton;
|
||||
[self.selectedAnswerButton setSelected:YES];
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(keyboardView:didSelectAnswer:)]) {
|
||||
NSInteger answerValue = [self.answerButtons indexOfObject:tappedAnswerButton] + ORKPSATMinimumAnswer;
|
||||
[self.delegate keyboardView:self didSelectAnswer:answerValue];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKPSATStep : ORKActiveStep
|
||||
|
||||
@property (nonatomic, assign) ORKPSATPresentationMode presentationMode;
|
||||
@property (nonatomic, assign) NSTimeInterval interStimulusInterval;
|
||||
@property (nonatomic, assign) NSTimeInterval stimulusDuration;
|
||||
@property (nonatomic, assign) NSInteger seriesLength;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKPSATStep.h"
|
||||
|
||||
#import "ORKPSATStepViewController.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKPSATStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKPSATStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
NSTimeInterval const ORKPSATInterStimulusMinimumInterval = 1.0;
|
||||
NSTimeInterval const ORKPSATInterStimulusMaximumInterval = 5.0;
|
||||
|
||||
NSTimeInterval const ORKPSATStimulusMinimumDuration = 0.2;
|
||||
|
||||
NSInteger const ORKPSATSerieMinimumLength = 10;
|
||||
NSInteger const ORKPSATSerieMaximumLength = 120;
|
||||
|
||||
NSTimeInterval totalDuration = (self.seriesLength + 1) * self.interStimulusInterval;
|
||||
if (self.stepDuration != totalDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"step duration must be equal to %@ seconds.", @(totalDuration)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (!(self.presentationMode & ORKPSATPresentationModeAuditory) &&
|
||||
!(self.presentationMode & ORKPSATPresentationModeVisual)) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"step presentation mode must be auditory and/or visual." userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.interStimulusInterval < ORKPSATInterStimulusMinimumInterval ||
|
||||
self.interStimulusInterval > ORKPSATInterStimulusMaximumInterval) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"inter stimulus interval must be greater than or equal to %@ seconds and less than or equal to %@ seconds.", @(ORKPSATInterStimulusMinimumInterval), @(ORKPSATInterStimulusMaximumInterval)] userInfo:nil];
|
||||
}
|
||||
|
||||
if ((self.presentationMode & ORKPSATPresentationModeVisual) &&
|
||||
(self.stimulusDuration < ORKPSATStimulusMinimumDuration || self.stimulusDuration > self.interStimulusInterval)) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"stimulus duration must be greater than or equal to %@ seconds and less than or equal to %@ seconds.", @(ORKPSATStimulusMinimumDuration), @(self.interStimulusInterval)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.seriesLength < ORKPSATSerieMinimumLength ||
|
||||
self.seriesLength > ORKPSATSerieMaximumLength) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"serie length must be greater than or equal to %@ additions and less than or equal to %@ additions.", @(ORKPSATSerieMinimumLength), @(ORKPSATSerieMaximumLength)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_ENUM(aDecoder, presentationMode);
|
||||
ORK_DECODE_DOUBLE(aDecoder, interStimulusInterval);
|
||||
ORK_DECODE_DOUBLE(aDecoder, stimulusDuration);
|
||||
ORK_DECODE_INTEGER(aDecoder, seriesLength);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_ENUM(aCoder, presentationMode);
|
||||
ORK_ENCODE_DOUBLE(aCoder, interStimulusInterval);
|
||||
ORK_ENCODE_DOUBLE(aCoder, stimulusDuration);
|
||||
ORK_ENCODE_INTEGER(aCoder, seriesLength);
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKPSATStep *step = [super copyWithZone:zone];
|
||||
step.presentationMode = self.presentationMode;
|
||||
step.interStimulusInterval = self.interStimulusInterval;
|
||||
step.stimulusDuration = self.stimulusDuration;
|
||||
step.seriesLength = self.seriesLength;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [super hash] ^ self.presentationMode ^ (NSInteger)(self.interStimulusInterval*100) ^ (NSInteger)(self.stimulusDuration*100) ^ self.seriesLength;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.presentationMode == castObject.presentationMode) &&
|
||||
(self.interStimulusInterval == castObject.interStimulusInterval) &&
|
||||
(self.stimulusDuration == castObject.stimulusDuration) &&
|
||||
(self.seriesLength == castObject.seriesLength));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKPSATStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user