Compare commits
846 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4259a1cc92 | |||
| 70d4532d0e | |||
| 23900bed0e | |||
| 8c8a1adc47 | |||
| 429c3c9886 | |||
| 4228cf46c6 | |||
| ce31ebd366 | |||
| 135ec4a3f2 | |||
| a30119aa0d | |||
| 31526c6664 | |||
| e5ac62f298 | |||
| bb1be965a2 | |||
| 3fb266139e | |||
| 0b2216c6cf | |||
| c90d8c7fbd | |||
| 3cb705c123 | |||
| 1817398f61 | |||
| b542c46189 | |||
| b6cc636975 | |||
| 1844a0774c | |||
| 6a1343622d | |||
| a3b45b6268 | |||
| f076b69a98 | |||
| 39e17e7867 | |||
| 663426250f | |||
| 197eb8422a | |||
| fe10cd8a71 | |||
| 63dfad2b0d | |||
| 886605e013 | |||
| 4c0a4a1e51 | |||
| aa2b1f9e61 | |||
| fed9fb9ce6 | |||
| 6a786cfb37 | |||
| 39d76c8ed6 | |||
| 7059bd39a6 | |||
| dddc633e3a | |||
| 9e358e7fc5 | |||
| 0aacde2057 | |||
| 5bdd973315 | |||
| cb4514328e | |||
| 84710f773f | |||
| 42005af39c | |||
| 9c847f8fe6 | |||
| aac584b2f1 | |||
| 4be283e888 | |||
| 4d37142873 | |||
| 08b48589da | |||
| 6ba85a1504 | |||
| 5fc4316e22 | |||
| 287dcd979c | |||
| 29f2d16676 | |||
| b022ae3589 | |||
| 76bbbbe604 | |||
| a322f0b17f | |||
| f11d17752f | |||
| 14a815d63e | |||
| 1b4b0166b8 | |||
| 669269fd45 | |||
| 3f7c1d15d5 | |||
| ca136a9ce1 | |||
| fe2ea1e3d8 | |||
| eedcff0ab0 | |||
| 00bd267c1b | |||
| d9c005aa18 | |||
| be02e1a22a | |||
| b474f6f12d | |||
| bc263d92af | |||
| 0575f21c3e | |||
| 88543a14d6 | |||
| cd8a801738 | |||
| 30af7f46f7 | |||
| 1e889ad7dc | |||
| 839c8eca53 | |||
| 48ed4b3771 | |||
| e6f4c1deb8 | |||
| ae2bff43fa | |||
| 14260e0df1 | |||
| 4153702c2d | |||
| a9494ce5d8 | |||
| 0b06d4c18a | |||
| 2b6d1fcab9 | |||
| eb52acefa9 | |||
| 82de8783bd | |||
| 0dea3dd673 | |||
| 6f4c072f93 | |||
| fee0d2d7b6 | |||
| 610a1fd93f | |||
| 36bda05556 | |||
| 069ea16731 | |||
| a1ed3ea711 | |||
| d910062bb9 | |||
| ba5d80c6c1 | |||
| 21ada70ae4 | |||
| 9be3f057be | |||
| 51c23ccf17 | |||
| 113b7422a0 | |||
| c852c62262 | |||
| c27b784870 | |||
| 2510bc7f97 | |||
| de7860b60a | |||
| 37c65bdc55 | |||
| 4fe13d0b27 | |||
| 853d4384fd | |||
| f9669508dc | |||
| e1ea72b861 | |||
| 1cf445af09 | |||
| fc1c328b68 | |||
| 7b7a851cca | |||
| 37ac134b5e | |||
| 067f8f8e3d | |||
| 7d30dd09f3 | |||
| 19921baf55 | |||
| ffd8552aa8 | |||
| 140e1ebae0 | |||
| 9338ea8b59 | |||
| 63d56e44f8 | |||
| 3daf9484c4 | |||
| d87372923b | |||
| 3dccc6d438 | |||
| 50cf8171f5 | |||
| d2c51201ef | |||
| 8a320f0b72 | |||
| 2988cb0fa6 | |||
| 59788e53ec | |||
| af823d1f63 | |||
| 75ac5d3366 | |||
| 932024c4af | |||
| 26cf7e4d64 | |||
| fa2b18da42 | |||
| 304d93a79a | |||
| a57a8d8370 | |||
| d44676d816 | |||
| 18d50721cc | |||
| 967518a576 | |||
| ef8c1e1b3a | |||
| 6563ebdca4 | |||
| f85faecb49 | |||
| 0341e511ab | |||
| 8f9ab33897 | |||
| 372fbaaaa4 | |||
| a3c28f666c | |||
| 4c34d1e8a3 | |||
| 50c0d4b3c0 | |||
| 4b269b8c21 | |||
| 143b474aff | |||
| ca11a8def0 | |||
| 09cb204a24 | |||
| 20204946bc | |||
| 74dc33a654 | |||
| f738f18570 | |||
| 7af35c7c35 | |||
| ca2f7848f8 | |||
| 85097a5e20 | |||
| 60af4ce1e5 | |||
| 8c10abfd74 | |||
| 0f97c0704a | |||
| a9ea108054 | |||
| ac0a9a25d6 | |||
| 4ca0197532 | |||
| e021f815ca | |||
| 1492ab82c1 | |||
| 61408fc5f6 | |||
| 0459818613 | |||
| 99bdc3cbbc | |||
| c0b751d05a | |||
| d3ed706680 | |||
| d5175083ef | |||
| 721572a533 | |||
| 2494895cf9 | |||
| 160b7c5f71 | |||
| 1b5ea88e0f | |||
| dd42f4740e | |||
| 1f26d5c5a1 | |||
| 0a07f24bea | |||
| 224146f7fd | |||
| 5e7768483e | |||
| 66884c045b | |||
| e7ca832c3d | |||
| af3285f5f0 | |||
| 50cd9055ac | |||
| d0226f29f7 | |||
| eceeab053d | |||
| de0019054a | |||
| cbf9d7284f | |||
| 00183879ce | |||
| 58453f5df9 | |||
| 9c65780911 | |||
| f7f8e12d9d | |||
| ee494c5ed1 | |||
| 12a2d556c9 | |||
| ce6d4fe3ac | |||
| 05f2fe9db4 | |||
| 7e8175b8a2 | |||
| c3460d8aaa | |||
| 886bca74bf | |||
| 53bb638c69 | |||
| 3d40007df9 | |||
| 5070517ba3 | |||
| 5bca8eb66d | |||
| 4dff0189df | |||
| 9e1e855b70 | |||
| 4e7f24f1c2 | |||
| 3229df2a48 | |||
| 7bc407ee90 | |||
| 96e94155d7 | |||
| b8b5648c71 | |||
| 0471bf61d4 | |||
| a0ca62ec23 | |||
| aac14a3a1a | |||
| 696758bdc2 | |||
| 43b6585bfa | |||
| f47f9d824c | |||
| 9b4152ec6e | |||
| b58ae6139b | |||
| 7ba97f4089 | |||
| a2bfb9a15d | |||
| 17b7d7d4f9 | |||
| d9e2f54c41 | |||
| 9347ef6573 | |||
| ed4a50dacc | |||
| d8d3882a1c | |||
| ec8308b8bd | |||
| 9296fe90bc | |||
| df9170f9cf | |||
| 0deb8ba55e | |||
| b24310adbc | |||
| c138f6aa02 | |||
| 49e541d1b7 | |||
| f22676a8e7 | |||
| d34749e1ef | |||
| 0e4251d511 | |||
| 06ff988941 | |||
| 3b3c907d39 | |||
| b1515e0fc2 | |||
| f9924ddbc0 | |||
| bbde9865ee | |||
| cfa9e90d76 | |||
| 851bd4e2c5 | |||
| 9fbabf8f3b | |||
| 536a22f733 | |||
| e51c9b708a | |||
| b45425ba72 | |||
| 181651b365 | |||
| b8d2c6bf86 | |||
| feb5f11e3e | |||
| 8d4b73ae8f | |||
| 81e67dc6fa | |||
| e873af81d6 | |||
| 59681fbab5 | |||
| a3a53b7c68 | |||
| a28e2596bd | |||
| f5e8b40f6d | |||
| 55bc242896 | |||
| d3dee01678 | |||
| a6445546fd | |||
| 44a786d51d | |||
| 5954a6b254 | |||
| 06eecb054c | |||
| c38da58f6b | |||
| 17c35e6765 | |||
| 1074eefbbe | |||
| 6c54eea404 | |||
| daa267b8f0 | |||
| 7f66623c59 | |||
| 7854c619a3 | |||
| deef811bc9 | |||
| b9fff0e4c0 | |||
| f35e84189c | |||
| 5ff0f9324a | |||
| 35d33709b2 | |||
| 0d9c98dd56 | |||
| 2ac5e30d85 | |||
| d1e246bba4 | |||
| 0dc31e4593 | |||
| 521dba3d4e | |||
| 07e05298a0 | |||
| 9214c48d41 | |||
| ebdc7a218a | |||
| 3d9cc15c0b | |||
| f45722366c | |||
| 10152cfb79 | |||
| 038794df0d | |||
| d74bfe956f | |||
| af9588fd93 | |||
| 80ee2b8792 | |||
| 8dffe81ea7 | |||
| 95099e34de | |||
| 9dc22ff97e | |||
| df7a5d2ae4 | |||
| 475d84de73 | |||
| cc6380af67 | |||
| ebf3a5474d | |||
| f8a94a0f66 | |||
| 1afb67bf9e | |||
| 258afcb7df | |||
| 8a3882d85c | |||
| 2bd667bc6f | |||
| 7a4715dd72 | |||
| 8f7047220f | |||
| fda01e8dd0 | |||
| 8042df3127 | |||
| 153726633f | |||
| 4040d962a0 | |||
| 861211fa3b | |||
| a8f6be61f3 | |||
| 7c01903108 | |||
| 848800bcd6 | |||
| caa324ec79 | |||
| 27bf948f6d | |||
| ed60c84a2d | |||
| e70d56142d | |||
| a4c9286f29 | |||
| 97c5ada28f | |||
| fd0d4af750 | |||
| dc81d8af3a | |||
| deac6b5de0 | |||
| 2c504f4b01 | |||
| db4b19b229 | |||
| 129d2ac3df | |||
| 0790e52a5a | |||
| 9784c1425a | |||
| df13dca4d9 | |||
| acfce230b4 | |||
| bfac86d706 | |||
| c2e2951d6f | |||
| 36a578549b | |||
| 165e107ddc | |||
| e32efc4ec2 | |||
| 3479c35fb3 | |||
| 74fbc9af77 | |||
| ec91b7b139 | |||
| d4403704ce | |||
| 4919e0fc0c | |||
| 4c49283504 | |||
| 26a094e05f | |||
| d6504c68ef | |||
| f90af1ce01 | |||
| ae6fcf4ce4 | |||
| 8730a26be1 | |||
| 20d6defb6f | |||
| cf917dc208 | |||
| 47ad36d906 | |||
| e69228445a | |||
| 90619ac2af | |||
| 43c4b892b2 | |||
| 1c2028ce5d | |||
| 29942a01d5 | |||
| c159d6db61 | |||
| 7635528b2f | |||
| d5b6b0f87a | |||
| 328b8421a3 | |||
| b4a2cbebd0 | |||
| 9a749c5489 | |||
| 09272a78df | |||
| 92ae7cf071 | |||
| b5568e278b | |||
| 33864f1012 | |||
| 2534e6b20e | |||
| 56b50e9296 | |||
| 8eb756b96c | |||
| 52d5fcabe7 | |||
| bf07984430 | |||
| 0554cbd6f1 | |||
| b34d1ecd29 | |||
| f3c7c53e21 | |||
| 59045b27a5 | |||
| 14ea5cc282 | |||
| ec04a5c633 | |||
| f1e4941f3f | |||
| bb01f0bb4d | |||
| 7ebc03c93f | |||
| 413bc6682a | |||
| 9ccf4fb743 | |||
| 723075a61f | |||
| c659539a45 | |||
| 48661fe670 | |||
| b9fadc1b87 | |||
| 11dc10e66e | |||
| 6911fe614f | |||
| 452665de14 | |||
| 1f352c39d2 | |||
| e42c92004a | |||
| 17ac3f5177 | |||
| 400da43f42 | |||
| c0aed6d641 | |||
| f7e9c672e2 | |||
| 7f106898e2 | |||
| d3da59a4f6 | |||
| 8a78e95ebb | |||
| c113deb1a3 | |||
| 3097232bd5 | |||
| 04a55343bc | |||
| 485e6035e0 | |||
| 785ea40fc4 | |||
| 69319a3c5e | |||
| c10ab44036 | |||
| 3fdbad88b2 | |||
| 2287b99b86 | |||
| 3d6a7fa481 | |||
| 0759764889 | |||
| e3ab9d75ef | |||
| f401dcf94d | |||
| b941379779 | |||
| f2d13d1305 | |||
| 96e5f26f7d | |||
| cecef562a9 | |||
| 7d2d04239f | |||
| 2b649f9e12 | |||
| a9478c3cb0 | |||
| 9b64b9c0f0 | |||
| ac0f3c542c | |||
| 2e356cac03 | |||
| 91d4fa7890 | |||
| 18504e9ffe | |||
| d80ca89094 | |||
| 706900ff07 | |||
| 20cfa9f588 | |||
| 76aef1a069 | |||
| 4afe6dcf77 | |||
| 4d82dcb8f3 | |||
| d2c381fefb | |||
| fbfeb233fa | |||
| 2a4b1b370a | |||
| 52ef002a67 | |||
| 65e2946cba | |||
| 52fbba25b8 | |||
| 73c59a40b9 | |||
| 87c6feceaa | |||
| 4bfc412626 | |||
| 8621b38a89 | |||
| 8517354463 | |||
| 250d016306 | |||
| a2a73b1661 | |||
| 46e61011a2 | |||
| 13ab769dd8 | |||
| 30f3900307 | |||
| f85a4c0c06 | |||
| 32fc46f896 | |||
| f15940f83e | |||
| 310080fc8e | |||
| b41e0be80f | |||
| c7d1d99b32 | |||
| 684dd82c15 | |||
| c0ff5bc6e0 | |||
| d2381657fa | |||
| 3463f1a16f | |||
| 9878b34c2a | |||
| 81bc7221d1 | |||
| a5bb7b5506 | |||
| 921b280a3b | |||
| d313ab3a45 | |||
| 73657854d4 | |||
| 7dc91f531d | |||
| 294ec34d47 | |||
| bcfb0b4dbb | |||
| 82c325d6c7 | |||
| c7bd1f5c41 | |||
| bdb6849ab5 | |||
| 6d1d22d9fa | |||
| 393a2bf404 | |||
| f828fbd8c4 | |||
| 27193727e9 | |||
| a7173cc746 | |||
| b0977b55e0 | |||
| 6d792cfd83 | |||
| fb6b1ab2c5 | |||
| ab729432b4 | |||
| 5b5e53bdc3 | |||
| 2b6c69e01d | |||
| fb1c5e9b9a | |||
| f4b4d2aee1 | |||
| 96478c583d | |||
| a796e6cc1e | |||
| 8599553a79 | |||
| 8197c147e1 | |||
| 8bdb32fc7a | |||
| 16f9b1dbe1 | |||
| 9459e330d5 | |||
| 78be958aea | |||
| 3c9291ed24 | |||
| 5454cf83e1 | |||
| b4e8a91474 | |||
| 829f93523a | |||
| 98b0acaeb7 | |||
| 91a9acb9ef | |||
| 71bdaab7be | |||
| 3e18a08690 | |||
| 63b8bea246 | |||
| 7a1a1c6cdd | |||
| 2b7f05fd96 | |||
| 234f40306a | |||
| 66218e4e28 | |||
| 130898cafd | |||
| 7db4bf50c8 | |||
| feb26fa506 | |||
| c317b30e82 | |||
| af6b027b7c | |||
| 3c715cc211 | |||
| c7bddcf4d5 | |||
| ad9eefb19a | |||
| 537e67af78 | |||
| 1c16398bc7 | |||
| ca292c1987 | |||
| 7a9009759e | |||
| 2040bbd095 | |||
| 111ac5627a | |||
| 57f911a4bb | |||
| 17776d04a4 | |||
| 66b7806bce | |||
| c79b228fba | |||
| 0398fb3767 | |||
| f168e02e9e | |||
| 5d1ed481e3 | |||
| 7cd75c4d40 | |||
| aa58b643a0 | |||
| 3b0ce6478b | |||
| c545aa8cbb | |||
| 74ff026b92 | |||
| 257ee610b7 | |||
| 79b9351433 | |||
| 359f8f3387 | |||
| 1f7c856fdb | |||
| 58a5a2c3b2 | |||
| 54f064247e | |||
| 3f35a49f50 | |||
| c16c929054 | |||
| be6a922554 | |||
| 3711ab342c | |||
| 908f7b0f5f | |||
| 1be6949c2d | |||
| fc4e8a9915 | |||
| 413e607258 | |||
| 7847c813a5 | |||
| 956549c320 | |||
| 709e4b818c | |||
| 8a6f0a35bd | |||
| b218db0f63 | |||
| 8fe483a5e0 | |||
| 3a555592ff | |||
| 4fb39abac9 | |||
| fbaf85ae9b | |||
| 11a0427883 | |||
| d23e188d64 | |||
| d70ee216c8 | |||
| 0cd42556f5 | |||
| 3daec83a7f | |||
| ed44fd84d9 | |||
| be9d2e3486 | |||
| dc78de8fdd | |||
| 21f1dbb5af | |||
| 7bc31391fa | |||
| e932d118b9 | |||
| 874386987a | |||
| 2b27a9d038 | |||
| 293e0ed23c | |||
| c322e0455d | |||
| 9960b4376f | |||
| 1d3adbba45 | |||
| 07d1aba216 | |||
| b69d40a86b | |||
| 633f9eaaef | |||
| 2edf7bc481 | |||
| 92620b782b | |||
| 06ca255bb1 | |||
| 5baa3a423a | |||
| b56e622ea0 | |||
| 9fad01dd0c | |||
| f2c751a71f | |||
| 06d9244c80 | |||
| 6004058c50 | |||
| cc1e1ae435 | |||
| be6d351bb3 | |||
| eac89e0119 | |||
| e035236314 | |||
| ea0c6a1cf8 | |||
| 48cc58f04c | |||
| 6c97c550ed | |||
| 50b3f1d170 | |||
| 601b16a985 | |||
| ecb0fde9c5 | |||
| 81be7627eb | |||
| f94929daae | |||
| 8d7ab7861c | |||
| 1354181f84 | |||
| 395d2727c6 | |||
| 7f67d9c4da | |||
| 2c5b5cda7d | |||
| 4fd95efe57 | |||
| fb223bb1bb | |||
| 831f136cd1 | |||
| 6ad9ad52af | |||
| b28d5e6456 | |||
| d7f9987ede | |||
| 86aaeae857 | |||
| 642bc3db80 | |||
| af0ca497d5 | |||
| 75f62ee5f7 | |||
| bd63d1576b | |||
| a4c521203c | |||
| b8aba5459e | |||
| f0507b973f | |||
| 58a9366bf5 | |||
| 5b298d17a7 | |||
| bca3ceae78 | |||
| d8c0ecbd1d | |||
| 133b154023 | |||
| 95baa8b943 | |||
| 0e433640c4 | |||
| 1ae771ee80 | |||
| c5b14925bd | |||
| fa40e30ee3 | |||
| 8736dd5165 | |||
| 4917b34291 | |||
| 29d3e0da89 | |||
| 9b68f5188f | |||
| ed3503370d | |||
| 43480170aa | |||
| fdaf18198f | |||
| aee5c48875 | |||
| 521715f7dd | |||
| 68572aefe2 | |||
| 731b65d65c | |||
| b584258946 | |||
| d46ab216f4 | |||
| b6bee7e9de | |||
| 7272291eb7 | |||
| 57be36482c | |||
| c7d180b3ad | |||
| ed6913e2ad | |||
| 9f79e144ce | |||
| 185789dfb3 | |||
| 5445b67b41 | |||
| 66286a80c9 | |||
| d9addcf469 | |||
| 7ba00fb853 | |||
| e4e2626823 | |||
| 48178b01c2 | |||
| 8b847e4de0 | |||
| 2a76ff2a2e | |||
| caa24b9ae6 | |||
| 0a3d938f8a | |||
| 1ea4960c29 | |||
| 7013e954bc | |||
| ec8c36260c | |||
| cfeb39ef5c | |||
| b56bda5aaa | |||
| a9857da213 | |||
| 3b3ec14acd | |||
| c499309897 | |||
| b813690c67 | |||
| 714f29e185 | |||
| d752d4d926 | |||
| 050cea0c51 | |||
| 5a20aeab03 | |||
| 748d13e56e | |||
| e9f3bc919b | |||
| b17dda9697 | |||
| 729ef4fa58 | |||
| e9cc5ea800 | |||
| 090b976083 | |||
| bd32b0ad24 | |||
| d86edbc3f2 | |||
| a7c7aa7466 | |||
| 72da218778 | |||
| 89571ef876 | |||
| dc6bb27f57 | |||
| 1c200251e8 | |||
| bdad58b3a7 | |||
| c79b0eebe9 | |||
| de05c7a632 | |||
| 0d07828edc | |||
| 527609982a | |||
| 01cb0e77be | |||
| 1fdd29c308 | |||
| b6e185284b | |||
| ade8879d54 | |||
| 015be2da3a | |||
| 2650360476 | |||
| d42e7eca38 | |||
| 074a00e2ae | |||
| f7b8901fd2 | |||
| c01d530cde | |||
| ceec6adf1a | |||
| dffbaeee6b | |||
| c45d2cb856 | |||
| 7e5b5b45ec | |||
| 739d22b5d3 | |||
| 070f449c57 | |||
| f76c960846 | |||
| a5640cb4b1 | |||
| 222c017e0d | |||
| 90c9e1de94 | |||
| e8bd259952 | |||
| 07736ac5f3 | |||
| 21bb2884b5 | |||
| b323bd8980 | |||
| f5e2f54979 | |||
| 39ef6a21f0 | |||
| 215e4d6008 | |||
| 674a131060 | |||
| 778e8571d2 | |||
| e71356805c | |||
| 25612dc872 | |||
| 8087810ac2 | |||
| 3e99ad04c7 | |||
| 0a1eb9c8a2 | |||
| c11e70585a | |||
| 045abdd919 | |||
| f9ffd5e5f1 | |||
| 407a3da80e | |||
| c786ed06e1 | |||
| 2765fa5575 | |||
| 835df17c5a | |||
| 68e0c685db | |||
| 352fa46668 | |||
| 87d8db3218 | |||
| 458f290956 | |||
| f8c492aadd | |||
| 22bcc07047 | |||
| ca7479609c | |||
| d1adbf1f50 | |||
| 321b19adbe | |||
| d86fba5c56 | |||
| 9614d50af9 | |||
| 42f9f6f03d | |||
| 43e4ae11b2 | |||
| 848c4dee8b | |||
| a750b2718d | |||
| 071242f217 | |||
| 52aed1a5b6 | |||
| 2e736a7306 | |||
| 217f40e6ec | |||
| 3353ff3dd0 | |||
| 9e45652356 | |||
| d83e859d84 | |||
| 4195dfe755 | |||
| 55aa48b544 | |||
| 5d3c846ed7 | |||
| 17c82175c2 | |||
| ddeea33a02 | |||
| f4d8224164 | |||
| aa5e5b8394 | |||
| f914263d0e | |||
| d3634439af | |||
| b509908b2a | |||
| 6490a7a8c3 | |||
| 162764e508 | |||
| 5d99a54c58 | |||
| 7252b83ef2 | |||
| 98b6269f11 | |||
| b8ee219a5f | |||
| 01e8c561bb | |||
| 71a0646065 | |||
| c8f5a1ccb6 | |||
| 2d1c697402 | |||
| c96a94c164 | |||
| 41d346853e | |||
| ad919639c7 | |||
| 5ebcc6d6b5 | |||
| 27c084707e | |||
| eeae8f3c1d | |||
| 36f86eb607 | |||
| e69460acfe | |||
| f7be4f6ae2 | |||
| d7975ec9f2 | |||
| 0405f813a2 | |||
| 8492334353 | |||
| 20e5e6db03 | |||
| ca442e748d | |||
| 4e6e3c9ad3 | |||
| eeee427c97 | |||
| 2bc714f05f | |||
| a07aa08fd4 | |||
| 00ca93e14d | |||
| a5e641185b | |||
| ba7433dade | |||
| 99f4263417 | |||
| a5c16f9d0b | |||
| 2d82a2a904 | |||
| 286366333f | |||
| a90abe74ae | |||
| 9e72e3aaeb | |||
| 0af4b33056 | |||
| 62fc9cc6ea | |||
| 2b54f90b97 | |||
| cfd1724cb0 | |||
| f6d9feda07 | |||
| be44b0c39a | |||
| 5e3f4acd78 | |||
| fd4557a8e6 | |||
| 28f4205bcd | |||
| 7b70f5c362 | |||
| 7f25099acd | |||
| fd84d8d893 | |||
| 49bee20f8e | |||
| 49cbb27222 | |||
| 90b4c7b1b6 | |||
| aa57b8223c | |||
| db93a6ddc8 | |||
| 41457846e2 | |||
| 08f1b8e3d9 | |||
| dc5460c120 | |||
| 6a6cb929e1 | |||
| fb56d6b64b | |||
| 2ed0111866 | |||
| ccc615cea6 | |||
| 97be632ed8 | |||
| 588d9b6528 | |||
| 4d791e96e3 | |||
| deaa85ad09 | |||
| 415a937a7e | |||
| e15906aeb8 | |||
| 853adebbcd | |||
| 0c39747462 | |||
| 7d9e48a8a6 | |||
| e75f76a1e6 | |||
| e5ac86f029 | |||
| 1ef8b48d2a | |||
| b19f2e06a8 | |||
| 8912749a75 | |||
| 033719036f | |||
| 4bde825ba0 | |||
| ad5aa9912b | |||
| 43c6e8b72b | |||
| 650c9154b5 | |||
| deeec06e03 | |||
| 2737e60305 | |||
| 662e2f746b | |||
| 43488a1145 | |||
| 6d0bd6a66d | |||
| 36d57c9419 | |||
| 43c75d6d76 | |||
| d146c50cf0 | |||
| bd6d36fa31 | |||
| 27a777a3e7 | |||
| fdedacaa51 | |||
| 38707d4f67 | |||
| b5b3c61be0 | |||
| 64f702b826 | |||
| fdef4d29bd | |||
| e6a27ba94a | |||
| ad72ce7347 | |||
| 6b0559b13b | |||
| 93bb589eb7 | |||
| f923bd7fe0 | |||
| af1ed4425c |
@@ -75,13 +75,19 @@ 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
|
||||
`Localizable.strings` table so that they can be picked up and
|
||||
`ResearchKit.strings` table so that they can be picked up and
|
||||
localized in the next release cycle.
|
||||
|
||||
|
||||
|
||||
@@ -4,19 +4,19 @@ ResearchKit Framework
|
||||
The ResearchKit™ framework is an open source software framework that makes it easy to
|
||||
create apps for medical research or for other research projects.
|
||||
|
||||
* Getting Started: [Getting Started](#gettingstarted)
|
||||
* Documentation: ([Programming Guide](http://researchkit.github.io/docs/docs/Overview/GuideOverview.html)) ([API](http://researchkit.github.io/docs/index.html))
|
||||
* Best practices: [Best Practices](../../wiki/best-practices)
|
||||
* Contributing to ResearchKit: [Contributing](CONTRIBUTING.md)
|
||||
* Website and blog: ([researchkit.org](http://researchkit.github.io/index.html)) ([Blog](http://researchkit.github.io/blog.html))
|
||||
* ResearchKit BSD License: [License](#license)
|
||||
* [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)
|
||||
|
||||
Getting More Information
|
||||
========================
|
||||
|
||||
* Join [researchkit-users](https://lists.apple.com/mailman/listinfo/researchkit-users) for discussing uses of the ResearchKit framework and related projects.
|
||||
* Join [researchkit-dev](https://lists.apple.com/mailman/listinfo/researchkit-dev) for discussing ongoing work to improve and expand the framework.
|
||||
* Or [contact us](https://developer.apple.com/contact/researchkit/)
|
||||
* Join the [ResearchKit Forum](https://forums.developer.apple.com/community/researchkit) for discussing uses of the ResearchKit framework and related projects.
|
||||
|
||||
Use Cases
|
||||
===========
|
||||
@@ -29,14 +29,14 @@ Surveys
|
||||
-------
|
||||
|
||||
The ResearchKit framework provides a pre-built user interface for surveys, which can be
|
||||
presented modally on an iPhone, iPod Touch, or iPad. [Surveys](http://researchkit.github.io/docs/docs/Survey/CreatingSurveys.html)
|
||||
presented modally on an iPhone, iPod Touch, or iPad. See *[Creating Surveys](http://researchkit.org/docs/docs/Survey/CreatingSurveys.html)* for more information.
|
||||
|
||||
|
||||
Consent
|
||||
----------------
|
||||
|
||||
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. [Consent](http://researchkit.github.io/docs/docs/InformedConsent/InformedConsent.html)
|
||||
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.
|
||||
|
||||
|
||||
Active Tasks
|
||||
@@ -45,7 +45,7 @@ Active Tasks
|
||||
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. [Active Tasks](http://researchkit.github.io/docs/docs/ActiveTasks/ActiveTasks.html)
|
||||
under semi-controlled conditions, while iPhone sensors actively collect data. See *[Active Tasks](http://researchkit.org/docs/docs/ActiveTasks/ActiveTasks.html)* for more information.
|
||||
|
||||
|
||||
Getting Started<a name="gettingstarted"></a>
|
||||
@@ -56,7 +56,8 @@ Requirements
|
||||
------------
|
||||
|
||||
The primary ResearchKit framework codebase supports iOS and requires Xcode 7.0
|
||||
or newer. The ResearchKit framework has a Base SDK version of 8.0, meaning that apps
|
||||
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.
|
||||
|
||||
|
||||
@@ -124,7 +125,7 @@ the step `myStep`.
|
||||
|
||||
*Objective-C*
|
||||
|
||||
```objc
|
||||
```objc
|
||||
ORKInstructionStep *myStep =
|
||||
[[ORKInstructionStep alloc] initWithIdentifier:@"intro"];
|
||||
myStep.title = @"Welcome to ResearchKit";
|
||||
@@ -203,8 +204,8 @@ which you must implement in order to handle the completion of the task:
|
||||
*Swift*
|
||||
|
||||
```swift
|
||||
func taskViewController(taskViewController: ORKTaskViewController,
|
||||
didFinishWithReason reason: ORKTaskViewControllerFinishReason,
|
||||
func taskViewController(taskViewController: ORKTaskViewController,
|
||||
didFinishWithReason reason: ORKTaskViewControllerFinishReason,
|
||||
error: NSError?) {
|
||||
let taskResult = taskViewController.result
|
||||
// You could do something with the result here.
|
||||
@@ -214,7 +215,7 @@ func taskViewController(taskViewController: ORKTaskViewController,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
If you now run your app, you should see your first ResearchKit framework
|
||||
instruction step:
|
||||
|
||||
@@ -245,22 +246,22 @@ following license unless another license is explicitly identified:
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
# ResearchKit Release Notes
|
||||
|
||||
|
||||
## 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 Tasks**
|
||||
|
||||
- **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 variable number of pegs in a hole, and then 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]* 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 to create a new account.
|
||||
2. Verification to verify email.
|
||||
3. Login 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 pin entry.
|
||||
|
||||
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-pin and 6-pin entries.
|
||||
|
||||
The passcode module can be used in the following scenarios:
|
||||
|
||||
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 presented modally 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).*
|
||||
|
||||
Implements the `ORKFormItem` `optional` property.
|
||||
|
||||
The *Continue/Done* button of form steps enables only if:
|
||||
|
||||
- At least one form item has an answer.
|
||||
- All answered form items are valid.
|
||||
- All the non-optional form items have 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 a specific address.
|
||||
|
||||
The question uses *MapKit* to provides 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 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.
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ResearchKit'
|
||||
s.version = '1.2.1'
|
||||
s.version = '1.3.1'
|
||||
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/'
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
static NSString *const kActivityUnknown = @"unknown";
|
||||
static NSString *const kActivityStationary = @"stationary";
|
||||
static NSString *const kActivityWalking = @"walking";
|
||||
static NSString *const kActivityRunning = @"running";
|
||||
static NSString *const kActivityAutomotive = @"automotive";
|
||||
static NSString *const kStartDateKey = @"startDate";
|
||||
static NSString *const kEndDateKey = @"endDate";
|
||||
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",
|
||||
@@ -51,33 +51,32 @@ static NSString *stringFromActivityConfidence(CMMotionActivityConfidence confide
|
||||
static NSArray *activityArray(CMMotionActivity *activity) {
|
||||
NSMutableArray *array = [NSMutableArray array];
|
||||
if (activity.unknown) {
|
||||
[array addObject:kActivityUnknown];
|
||||
[array addObject:ActivityUnknown];
|
||||
}
|
||||
if (activity.stationary) {
|
||||
[array addObject:kActivityStationary];
|
||||
[array addObject:ActivityStationary];
|
||||
}
|
||||
if (activity.walking) {
|
||||
[array addObject:kActivityWalking];
|
||||
[array addObject:ActivityWalking];
|
||||
}
|
||||
if (activity.running) {
|
||||
[array addObject:kActivityRunning];
|
||||
[array addObject:ActivityRunning];
|
||||
}
|
||||
if (activity.automotive) {
|
||||
[array addObject:kActivityAutomotive];
|
||||
[array addObject:ActivityAutomotive];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
static NSString *const kActivityKey = @"activity";
|
||||
|
||||
static NSString *const kConfidenceKey = @"confidence";
|
||||
static NSString *const ActivityKey = @"activity";
|
||||
static NSString *const ConfidenceKey = @"confidence";
|
||||
|
||||
@implementation CMMotionActivity (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
return @{kConfidenceKey : stringFromActivityConfidence(self.confidence),
|
||||
kActivityKey : activityArray(self),
|
||||
kStartDateKey : ORKStringFromDateISO8601(self.startDate)};
|
||||
return @{ConfidenceKey : stringFromActivityConfidence(self.confidence),
|
||||
ActivityKey : activityArray(self),
|
||||
StartDateKey : ORKStringFromDateISO8601(self.startDate)};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -33,16 +33,16 @@
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
static NSString *const kHKSampleIdentifierKey = @"type"; // For compatibility with Health XML export
|
||||
static NSString *const kHKUUIDKey = @"uuid";
|
||||
static NSString *const kHKSampleStartDateKey = @"startDate";
|
||||
static NSString *const kHKSampleEndDateKey = @"endDate";
|
||||
static NSString *const kHKSampleValue = @"value";
|
||||
static NSString *const kHKMetadataKey = @"metadata";
|
||||
static NSString *const kHKSourceKey = @"source";
|
||||
static NSString *const kHKUnitKey = @"unit";
|
||||
static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
// static NSString *const kHKSourceIdentifierKey = @"sourceBundleIdentifier";
|
||||
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)
|
||||
@@ -52,31 +52,31 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
|
||||
// Type identification
|
||||
HKSampleType *sampleType = [self sampleType];
|
||||
mutableDictionary[kHKSampleIdentifierKey] = [sampleType identifier];
|
||||
mutableDictionary[HKSampleIdentifierKey] = [sampleType identifier];
|
||||
|
||||
// consider adding @"class" : NSStringFromClass(sampleType) ?
|
||||
|
||||
// Start and end dates
|
||||
NSDate *startDate = [self startDate];
|
||||
if (startDate) {
|
||||
mutableDictionary[kHKSampleStartDateKey] = ORKStringFromDateISO8601(startDate);
|
||||
mutableDictionary[HKSampleStartDateKey] = ORKStringFromDateISO8601(startDate);
|
||||
}
|
||||
|
||||
NSDate *endDate = [self endDate];
|
||||
if (endDate) {
|
||||
mutableDictionary[kHKSampleEndDateKey] = ORKStringFromDateISO8601(endDate);
|
||||
mutableDictionary[HKSampleEndDateKey] = ORKStringFromDateISO8601(endDate);
|
||||
}
|
||||
if (unit) {
|
||||
mutableDictionary[kHKUnitKey] = [unit unitString];
|
||||
mutableDictionary[HKUnitKey] = [unit unitString];
|
||||
}
|
||||
if ((options & ORKSampleIncludeUUID)) {
|
||||
NSUUID *uuid = [self UUID];
|
||||
if (uuid) {
|
||||
mutableDictionary[kHKUUIDKey] = uuid.UUIDString;
|
||||
mutableDictionary[HKUUIDKey] = uuid.UUIDString;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (options & ORKSampleIncludeMetadata) && [self.metadata count] > 0) {
|
||||
if ( (options & ORKSampleIncludeMetadata) && self.metadata.count > 0) {
|
||||
NSMutableDictionary *metadata = [self.metadata mutableCopy];
|
||||
for (NSString *k in metadata) {
|
||||
id obj = metadata[k];
|
||||
@@ -85,13 +85,13 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
}
|
||||
}
|
||||
|
||||
mutableDictionary[kHKMetadataKey] = metadata;
|
||||
mutableDictionary[HKMetadataKey] = metadata;
|
||||
}
|
||||
|
||||
if (options & ORKSampleIncludeSource) {
|
||||
HKSource *source = [self source];
|
||||
if (source.name) {
|
||||
mutableDictionary[kHKSourceKey] = source.name;
|
||||
mutableDictionary[HKSourceKey] = source.name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,8 +115,8 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
NSMutableDictionary *dictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:unit];
|
||||
|
||||
NSInteger value = [self value];
|
||||
dictionary[kHKSampleValue] = @(value);
|
||||
NSInteger value = self.value;
|
||||
dictionary[HKSampleValue] = @(value);
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
@@ -136,7 +136,7 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
|
||||
HKQuantity *quantity = [self quantity];
|
||||
double value = [quantity doubleValueForUnit:unit];
|
||||
dictionary[kHKSampleValue] = @(value);
|
||||
dictionary[HKSampleValue] = @(value);
|
||||
|
||||
|
||||
return dictionary;
|
||||
@@ -151,7 +151,7 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
NSMutableDictionary *mutableDictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:nil];
|
||||
|
||||
// The correlated objects
|
||||
NSMutableArray *correlatedObjects = [NSMutableArray arrayWithCapacity:[sampleTypes count]];
|
||||
NSMutableArray *correlatedObjects = [NSMutableArray arrayWithCapacity:sampleTypes.count];
|
||||
for (HKSample *sample in self.objects) {
|
||||
NSUInteger idx = [sampleTypes indexOfObject:sample.sampleType];
|
||||
if (idx == NSNotFound) {
|
||||
@@ -160,7 +160,7 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
|
||||
[correlatedObjects addObject:[sample ork_JSONDictionaryWithOptions:options unit:units[idx]]];
|
||||
}
|
||||
mutableDictionary[kHKCorrelatedObjectsKey] = correlatedObjects;
|
||||
mutableDictionary[HKCorrelatedObjectsKey] = correlatedObjects;
|
||||
|
||||
return mutableDictionary;
|
||||
}
|
||||
|
||||
@@ -86,16 +86,16 @@
|
||||
|
||||
self.motionManager = [self createMotionManager];
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (! self.motionManager || ! self.motionManager.accelerometerAvailable) {
|
||||
if (!self.motionManager || !self.motionManager.accelerometerAvailable) {
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}];
|
||||
@@ -103,7 +103,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
self.motionManager.accelerometerUpdateInterval = 1.0/_frequency;
|
||||
self.motionManager.accelerometerUpdateInterval = 1.0 / _frequency;
|
||||
|
||||
self.uptime = [NSProcessInfo processInfo].systemUptime;
|
||||
|
||||
@@ -172,11 +172,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKAccelerometerRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAccelerometerRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
|
||||
@@ -84,15 +84,14 @@
|
||||
view.isAccessibilityElement = NO;
|
||||
}
|
||||
|
||||
[self setupConstraints];
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
_enabled = enabled;
|
||||
self.hidden = ! enabled;
|
||||
self.hidden = !enabled;
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
@@ -111,91 +110,89 @@
|
||||
_imageView.image = image;
|
||||
}
|
||||
|
||||
- (void)setupConstraints {
|
||||
- (void)setUpConstraints {
|
||||
|
||||
const CGFloat TitleBaselineToValueBaseline = 40;
|
||||
const CGFloat ValueBaselineToBottom = 36;
|
||||
|
||||
NSMutableArray *additionalConstraints = [NSMutableArray array];
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_titleLabel, _valueLabel, _imageView);
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleLabel]"
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleLabel]"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleLabel]|"
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleLabel]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_imageView]-10-[_valueLabel]|"
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_imageView]-10-[_valueLabel]|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_titleLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1.0
|
||||
constant:TitleBaselineToValueBaseline]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_valueLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1.0
|
||||
constant:ValueBaselineToBottom]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:_valueHolder
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:_valueHolder
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeLeft
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeLeft
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeRight
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeRight
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
for (NSLayoutConstraint *constraint in additionalConstraints) {
|
||||
constraint.priority = UILayoutPriorityRequired-2;
|
||||
for (NSLayoutConstraint *constraint in constraints) {
|
||||
constraint.priority = UILayoutPriorityRequired - 2;
|
||||
}
|
||||
|
||||
[NSLayoutConstraint activateConstraints:additionalConstraints];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
|
||||
|
||||
NSLayoutConstraint *zeroWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
zeroWidthConstraint.priority = UILayoutPriorityRequired-1;
|
||||
_zeroWidthConstraint = zeroWidthConstraint;
|
||||
_zeroWidthConstraint.active = !_enabled;
|
||||
_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 {
|
||||
@@ -245,18 +242,18 @@
|
||||
[self addSubview:_leftView];
|
||||
[self addSubview:_rightView];
|
||||
[self addSubview:_metricKeyline];
|
||||
[self setupConstraints];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupConstraints {
|
||||
- (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];
|
||||
CGFloat scale = [UIScreen mainScreen].scale;
|
||||
NSArray *vertConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_leftView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
@@ -265,10 +262,10 @@
|
||||
|
||||
NSArray *horizConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_leftView]-s-[_rightView]-|"
|
||||
options:NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom|NSLayoutFormatDirectionLeftToRight
|
||||
metrics:@{ @"s": @(1/scale) }
|
||||
metrics:@{ @"s": @(1.0 / scale) }
|
||||
views:views];
|
||||
for (NSLayoutConstraint *constraint in horizConstraints) {
|
||||
constraint.priority = UILayoutPriorityDefaultHigh+1;
|
||||
constraint.priority = UILayoutPriorityDefaultHigh + 1;
|
||||
}
|
||||
[constraints addObjectsFromArray:horizConstraints];
|
||||
|
||||
@@ -290,7 +287,7 @@
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_leftView][_metricKeyline(==s)]"
|
||||
options:NSLayoutFormatAlignAllTop|NSLayoutFormatDirectionLeftToRight
|
||||
metrics:@{ @"s": @(1/scale) }
|
||||
metrics:@{ @"s": @(1.0 / scale) }
|
||||
views:views]];
|
||||
NSLayoutConstraint *keylineBottom = [NSLayoutConstraint constraintWithItem:_metricKeyline
|
||||
attribute:NSLayoutAttributeBottom
|
||||
@@ -308,7 +305,7 @@
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired-2;
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired - 2;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
- (instancetype)initWithDuration:(NSTimeInterval)duration interval:(NSTimeInterval)interval runtime:(NSTimeInterval)runtime handler:(ORKActiveStepTimerHandler)handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
if (! handler) {
|
||||
if (!handler) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Handler is required" userInfo:nil];
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
_preExistingRuntime += timeIntervalFromMachTime(now - _startTime);
|
||||
_startTime = 0;
|
||||
|
||||
if (! atFinish) {
|
||||
if (!atFinish) {
|
||||
// If we are atFinish, the task will be released after the handler completes
|
||||
[self queue_releaseBackgroundTask];
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
@implementation ORKActiveStepTimerView {
|
||||
BOOL _started;
|
||||
BOOL _registeredForNotifications;
|
||||
|
||||
NSLayoutConstraint *_countDownLabelBottomToStartTimerButtonTopConstraint;
|
||||
NSLayoutConstraint *_countDownLabelZeroHeightConstraint;
|
||||
NSLayoutConstraint *_startTimerButtonZeroHeightConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
@@ -70,7 +74,8 @@
|
||||
|
||||
_countDownLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -88,11 +93,11 @@
|
||||
}
|
||||
|
||||
registered = _registeredForNotifications;
|
||||
NSNotificationCenter *nfc = [NSNotificationCenter defaultCenter];
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
if (registered) {
|
||||
[nfc addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
} else {
|
||||
[nfc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[notificationCenter removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,56 +149,80 @@
|
||||
- (void)finishStep:(ORKActiveStepViewController *)viewController {
|
||||
}
|
||||
|
||||
- (void)setNeedsUpdateConstraints {
|
||||
[NSLayoutConstraint deactivateConstraints:[self constraints]];
|
||||
[super setNeedsUpdateConstraints];
|
||||
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 {
|
||||
NSDictionary *dictionary = NSDictionaryOfVariableBindings(_countDownLabel, _startTimerButton);
|
||||
NSDictionary *metrics = @{@"CS" : @(2)};
|
||||
ORKEnableAutoLayoutForViews([dictionary allValues]);
|
||||
|
||||
for (UIView *view in [dictionary allValues]) {
|
||||
[self addConstraint:[NSLayoutConstraint
|
||||
constraintWithItem:view
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
[self addConstraint:[NSLayoutConstraint
|
||||
constraintWithItem:view
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
}
|
||||
|
||||
if (! _countDownLabel.hidden) {
|
||||
NSMutableString *verticalLayout = [NSMutableString new];
|
||||
[verticalLayout appendString:@"V:|[_countDownLabel]"];
|
||||
if (! _startTimerButton.hidden) {
|
||||
[verticalLayout appendString:@"-CS-[_startTimerButton]"];
|
||||
}
|
||||
[verticalLayout appendString:@"|"];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalLayout
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:metrics
|
||||
views:dictionary]];
|
||||
} else {
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
}
|
||||
_countDownLabelZeroHeightConstraint.active = _countDownLabel.hidden;
|
||||
_startTimerButtonZeroHeightConstraint.active = (_countDownLabel.hidden || _startTimerButton.hidden);
|
||||
_countDownLabelBottomToStartTimerButtonTopConstraint.constant =
|
||||
(_countDownLabel.hidden || _startTimerButton.hidden) ? 0.0 : CountDownLabelToButtonMargin;
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
[super viewDidLoad];
|
||||
|
||||
_activeStepView = [[ORKActiveStepView alloc] initWithFrame:self.view.bounds];
|
||||
[_activeStepView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
_activeStepView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[_activeStepView setCustomView:_customView];
|
||||
[self updateContinueButtonItem];
|
||||
_activeStepView.headerView.learnMoreButtonItem = self.learnMoreButtonItem;
|
||||
@@ -111,9 +111,17 @@
|
||||
_activeStepView.continueSkipContainer.continueEnabled = _finished;
|
||||
[self.view addSubview:_activeStepView];
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray arrayWithArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[s]|" options:0 metrics:nil views:@{@"s":_activeStepView}]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[tg][s]|" options:0 metrics:nil views:@{@"s":_activeStepView,@"tg":self.topLayoutGuide}]];
|
||||
[self.view addConstraints:constraints];
|
||||
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];
|
||||
}
|
||||
@@ -155,7 +163,10 @@
|
||||
|
||||
// Wait for animation complete
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([[self activeStep] shouldStartTimerAutomatically]) {
|
||||
if(self.started){
|
||||
// Should call resume instead of start when the task has been started.
|
||||
[self resume];
|
||||
} else if ([[self activeStep] shouldStartTimerAutomatically]) {
|
||||
[self start];
|
||||
}
|
||||
});
|
||||
@@ -313,7 +324,7 @@
|
||||
|
||||
- (void)suspend {
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
if (self.finished || ! self.started) {
|
||||
if (self.finished || !self.started) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,7 +336,7 @@
|
||||
|
||||
- (void)resume {
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
if (self.finished || ! self.started) {
|
||||
if (self.finished || !self.started) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -351,7 +362,7 @@
|
||||
if (self.activeStep.shouldVibrateOnFinish) {
|
||||
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
if (! self.activeStep.startsFinished) {
|
||||
if (!self.activeStep.startsFinished) {
|
||||
if (self.activeStep.shouldContinueOnFinish) {
|
||||
[self goForward];
|
||||
}
|
||||
|
||||
@@ -56,14 +56,15 @@ static const CGFloat GraphViewRedZoneHeight = 25;
|
||||
@end
|
||||
|
||||
|
||||
static const CGFloat kValueLineWidth = 4.5;
|
||||
static const CGFloat kValueLineMargin = 1.5;
|
||||
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)];
|
||||
@@ -72,6 +73,20 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
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];
|
||||
@@ -99,41 +114,41 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
|
||||
CGContextFillRect(context, bounds);
|
||||
|
||||
CGFloat scale = [self.window.screen scale];
|
||||
CGFloat scale = self.window.screen.scale;
|
||||
|
||||
CGFloat midY = CGRectGetMidY(bounds);
|
||||
CGFloat maxX = CGRectGetMaxX(bounds);
|
||||
CGFloat halfHeight = bounds.size.height/2;
|
||||
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}];
|
||||
[centerLine moveToPoint:(CGPoint){.x = 0, .y = midY}];
|
||||
[centerLine addLineToPoint:(CGPoint){.x = maxX, .y = midY}];
|
||||
|
||||
CGContextSetLineWidth(context, 1/scale);
|
||||
CGContextSetLineWidth(context, 1.0 / scale);
|
||||
[_keyColor setStroke];
|
||||
CGFloat lengths[2] = {3,3};
|
||||
CGFloat lengths[2] = {3, 3};
|
||||
CGContextSetLineDash(context, 0, lengths, 2);
|
||||
|
||||
[centerLine stroke];
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
CGFloat lineStep = kValueLineMargin + kValueLineWidth;
|
||||
CGFloat lineStep = ValueLineMargin + ValueLineWidth;
|
||||
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
CGFloat x = maxX - lineStep/2;
|
||||
CGContextSetLineWidth(context, kValueLineWidth);
|
||||
CGFloat x = maxX - lineStep / 2;
|
||||
CGContextSetLineWidth(context, ValueLineWidth);
|
||||
CGContextSetLineCap(context, kCGLineCapRound);
|
||||
|
||||
UIBezierPath *path1 = [UIBezierPath new];
|
||||
path1.lineCapStyle = kCGLineCapRound;
|
||||
path1.lineWidth = kValueLineWidth;
|
||||
path1.lineWidth = ValueLineWidth;
|
||||
UIBezierPath *path2 = [path1 copy];
|
||||
|
||||
for (NSNumber *value in [_values reverseObjectEnumerator]) {
|
||||
CGFloat floatValue = [value doubleValue];
|
||||
CGFloat floatValue = value.doubleValue;
|
||||
|
||||
UIBezierPath *path = nil;
|
||||
if (floatValue > _alertThreshold) {
|
||||
@@ -143,8 +158,8 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
path = path2;
|
||||
[_keyColor setStroke];
|
||||
}
|
||||
[path moveToPoint:(CGPoint){.x=x,.y=midY-floatValue*halfHeight}];
|
||||
[path addLineToPoint:(CGPoint){.x=x,.y=midY+floatValue*halfHeight}];
|
||||
[path moveToPoint:(CGPoint){.x = x, .y = midY - floatValue*halfHeight}];
|
||||
[path addLineToPoint:(CGPoint){.x = x, .y = midY + floatValue*halfHeight}];
|
||||
|
||||
x -= lineStep;
|
||||
|
||||
@@ -177,7 +192,7 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
+ (UIFont *)defaultFont {
|
||||
UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];
|
||||
UIFontDescriptor *alternativeDescriptor = ORKFontDescriptorForLightStylisticAlternative(descriptor);
|
||||
return [UIFont fontWithDescriptor:alternativeDescriptor size:[alternativeDescriptor pointSize]+4];
|
||||
return [UIFont fontWithDescriptor:alternativeDescriptor size:[alternativeDescriptor pointSize] + 4];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -193,7 +208,6 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
|
||||
|
||||
@implementation ORKAudioContentView {
|
||||
NSArray *_constraints;
|
||||
NSMutableArray *_samples;
|
||||
UIColor *_keyColor;
|
||||
}
|
||||
@@ -221,11 +235,11 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
_timerLabel.text = @"06:00";
|
||||
_alertLabel.text = ORKLocalizedString(@"AUDIO_TOO_LOUD_LABEL", nil);
|
||||
|
||||
self.alertThreshold = GraphViewBlueZoneHeight/(GraphViewRedZoneHeight*2+GraphViewBlueZoneHeight);
|
||||
self.alertThreshold = GraphViewBlueZoneHeight / ((GraphViewRedZoneHeight * 2) + GraphViewBlueZoneHeight);
|
||||
|
||||
[self updateGraphSamples];
|
||||
[self applyKeyColor];
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -266,27 +280,21 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
_graphView.alertColor = alertColor;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([_constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:_constraints];
|
||||
_constraints = nil;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_timerLabel, _alertLabel, _graphView);
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_graphView]-[_alertLabel]|"
|
||||
options:0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[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
|
||||
constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
const CGFloat sideMargin = self.layoutMargins.left + (2 * ORKStandardLeftMarginForTableViewCell(self));
|
||||
const CGFloat innerMargin = 2;
|
||||
@@ -297,18 +305,15 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
metrics:@{@"sideMargin": @(sideMargin), @"innerMargin": @(innerMargin)}
|
||||
views:views]];
|
||||
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_graphView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:(GraphViewBlueZoneHeight+GraphViewRedZoneHeight*2)]];
|
||||
multiplier:1.0
|
||||
constant:(GraphViewBlueZoneHeight + GraphViewRedZoneHeight * 2)]];
|
||||
|
||||
_constraints = constraints;
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
- (void)setAlertThreshold:(CGFloat)alertThreshold {
|
||||
@@ -344,8 +349,8 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
}
|
||||
|
||||
- (void)updateAlertLabelHidden {
|
||||
NSNumber *sample = [_samples lastObject];
|
||||
BOOL show = (! _finished && ([sample doubleValue] > _alertThreshold)) || _failed;
|
||||
NSNumber *sample = _samples.lastObject;
|
||||
BOOL show = (!_finished && (sample.doubleValue > _alertThreshold)) || _failed;
|
||||
_alertLabel.hidden = !show;
|
||||
}
|
||||
|
||||
@@ -356,13 +361,13 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
|
||||
- (void)addSample:(NSNumber *)sample {
|
||||
NSAssert(sample != nil, @"Sample should be non-nil");
|
||||
if (! _samples) {
|
||||
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];
|
||||
if (_samples.count > 500) {
|
||||
_samples = [[_samples subarrayWithRange:(NSRange){250, _samples.count - 250}] mutableCopy];
|
||||
}
|
||||
[self updateGraphSamples];
|
||||
}
|
||||
|
||||
@@ -103,8 +103,7 @@ OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
bufferActive[frame] = bufferValue;
|
||||
if (audioGenerator->_playsStereo) {
|
||||
bufferNonActive[frame] = bufferValue;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
bufferNonActive[frame] = 0;
|
||||
}
|
||||
|
||||
@@ -113,7 +112,7 @@ OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
theta -= 2.0 * M_PI;
|
||||
}
|
||||
|
||||
fadeInFactor += 1/(ORKSineWaveToneGeneratorSampleRateDefault * audioGenerator->_fadeInDuration);
|
||||
fadeInFactor += 1.0 / (ORKSineWaveToneGeneratorSampleRateDefault * audioGenerator->_fadeInDuration);
|
||||
if (fadeInFactor >= 1) {
|
||||
fadeInFactor = 1;
|
||||
}
|
||||
|
||||
@@ -68,10 +68,10 @@
|
||||
if (self) {
|
||||
|
||||
self.continuesInBackground = YES;
|
||||
if (! recorderSettings) {
|
||||
if (!recorderSettings) {
|
||||
recorderSettings = [[self class] defaultRecorderSettings];
|
||||
}
|
||||
if (! [recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
if (![recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"recorderSettings should be a dictionary" userInfo:recorderSettings];
|
||||
}
|
||||
self.recorderSettings = recorderSettings;
|
||||
@@ -84,18 +84,18 @@
|
||||
@throw [NSException exceptionWithName:NSDestinationInvalidException reason:@"audioRecorder requires an output directory" userInfo:nil];
|
||||
}
|
||||
// Only create the file when we should actually start recording.
|
||||
if (! _audioRecorder) {
|
||||
if (!_audioRecorder) {
|
||||
|
||||
NSError *error = nil;
|
||||
NSURL *soundFileURL = [self recordingFileURL];
|
||||
if (! [self recreateFileWithError:&error]) {
|
||||
if (![self recreateFileWithError:&error]) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
||||
if (! [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]) {
|
||||
if (![audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
@@ -105,19 +105,19 @@
|
||||
initWithURL:soundFileURL
|
||||
settings:self.recorderSettings
|
||||
error:&error];
|
||||
if (! _audioRecorder) {
|
||||
if (!_audioRecorder) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
#if ! TARGET_IPHONE_SIMULATOR
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
if (!_audioRecorder.recording) {
|
||||
[_audioRecorder prepareToRecord];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ! TARGET_IPHONE_SIMULATOR
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
if (!_audioRecorder.recording) {
|
||||
[_audioRecorder prepareToRecord];
|
||||
[_audioRecorder record];
|
||||
@@ -128,7 +128,7 @@
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (! _audioRecorder) {
|
||||
if (!_audioRecorder) {
|
||||
// Error has already been returned.
|
||||
return;
|
||||
}
|
||||
@@ -136,7 +136,7 @@
|
||||
[self doStopRecording];
|
||||
|
||||
NSURL *fileUrl = [self recordingFileURL];
|
||||
if (! [[NSFileManager defaultManager] fileExistsAtPath:[[self recordingFileURL] path]]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[[self recordingFileURL] path]]) {
|
||||
fileUrl = nil;
|
||||
}
|
||||
|
||||
@@ -151,12 +151,12 @@
|
||||
|
||||
- (NSString *)mimeType {
|
||||
NSDictionary *recorderSettings = [self recorderSettings];
|
||||
unsigned int recorderFormat = [recorderSettings[AVFormatIDKey] unsignedIntValue];
|
||||
unsigned int recorderFormat = ((NSNumber *)recorderSettings[AVFormatIDKey]).unsignedIntValue;
|
||||
|
||||
NSString *contentType = @"audio";
|
||||
switch (recorderFormat) {
|
||||
case kAudioFormatLinearPCM: {
|
||||
int numBits = [recorderSettings[AVLinearPCMBitDepthKey] intValue] ? : 16;
|
||||
int numBits = ((NSNumber *)recorderSettings[AVLinearPCMBitDepthKey]).intValue ? : 16;
|
||||
contentType = [NSString stringWithFormat:@"audio/L%d", numBits];
|
||||
break;
|
||||
}
|
||||
@@ -202,7 +202,7 @@
|
||||
|
||||
- (NSString *)extension {
|
||||
NSDictionary *recorderSettings = [self recorderSettings];
|
||||
unsigned int recorderFormat = [recorderSettings[AVFormatIDKey] unsignedIntValue];
|
||||
unsigned int recorderFormat = ((NSNumber *)recorderSettings[AVFormatIDKey]).unsignedIntValue;
|
||||
|
||||
NSString *extension = @"au";
|
||||
switch (recorderFormat) {
|
||||
@@ -233,7 +233,7 @@
|
||||
|
||||
- (BOOL)recreateFileWithError:(NSError * __autoreleasing *)error {
|
||||
NSURL *url = [self recordingFileURL];
|
||||
if (! url) {
|
||||
if (!url) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteInvalidFileNameError userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"ERROR_RECORDER_NO_OUTPUT_DIRECTORY", nil)}];
|
||||
}
|
||||
@@ -242,12 +242,12 @@
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
|
||||
if (! [fileManager createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:error]) {
|
||||
if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([fileManager fileExistsAtPath:[url path]]) {
|
||||
if (! [fileManager removeItemAtPath:[url path] error:error]) {
|
||||
if (![fileManager removeItemAtPath:[url path] error:error]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
@@ -266,11 +266,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKAudioRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
@@ -283,7 +278,7 @@
|
||||
recorderSettings:(NSDictionary *)recorderSettings {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
if (recorderSettings && ! [recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
if (recorderSettings && ![recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"recorderSettings should be a dictionary" userInfo:recorderSettings];
|
||||
}
|
||||
_recorderSettings = recorderSettings;
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
NSTimeInterval const ORKAudioTaskMinimumDuration = 5.0;
|
||||
|
||||
if ( self.stepDuration < ORKAudioTaskMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKAudioTaskMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKAudioTaskMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,16 +107,16 @@
|
||||
[_avAudioRecorder updateMeters];
|
||||
float value = [_avAudioRecorder averagePowerForChannel:0];
|
||||
// Assume value is in range roughly -60dB to 0dB
|
||||
float clampedValue = MAX(value/60.0, -1) + 1;
|
||||
float clampedValue = MAX(value / 60.0, -1) + 1;
|
||||
[_audioContentView addSample:@(clampedValue)];
|
||||
_audioContentView.timeLeft = [_timer duration] - [_timer runtime];
|
||||
}
|
||||
|
||||
- (void)startNewTimerIfNeeded {
|
||||
if (! _timer) {
|
||||
if (!_timer) {
|
||||
NSTimeInterval duration = self.audioStep.stepDuration;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_timer = [[ORKActiveStepTimer alloc] initWithDuration:duration interval:duration/100 runtime:0 handler:^(ORKActiveStepTimer *timer, BOOL finished) {
|
||||
_timer = [[ORKActiveStepTimer alloc] initWithDuration:duration interval:duration / 100 runtime:0 handler:^(ORKActiveStepTimer *timer, BOOL finished) {
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf doSample];
|
||||
if (finished) {
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
NSTimeInterval const ORKCountdownStepMinimumDuration = 3.0;
|
||||
|
||||
if ( self.stepDuration < ORKCountdownStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKCountdownStepMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKCountdownStepMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
CAShapeLayer *_circleLayer;
|
||||
}
|
||||
|
||||
static const CGFloat ProgressIndicatorDiameter = 104.0;
|
||||
static const CGFloat ProgressIndicatorOuterMargin = 1.0;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -88,55 +91,11 @@
|
||||
[self addSubview:_progressView];
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
static const CGFloat ProgressIndicatorDiameter = 104;
|
||||
static const CGFloat ProgressIndicatorOuterMargin = 1;
|
||||
NSDictionary *metrics = @{@"d":@(ProgressIndicatorDiameter+2*ProgressIndicatorOuterMargin)};
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_textLabel, _timeLabel, _progressView);
|
||||
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_textLabel]-(>=0)-[_progressView(==d)]|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing|NSLayoutFormatAlignAllCenterX
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_textLabel]-(>=0)-|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_progressView(==d)]-(>=0)-|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0 constant:0]];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_timeLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0 constant:0]];
|
||||
|
||||
// Constant required in order to give appearance of vertical centering (compensating for leading on font)
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_timeLabel
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1.0 constant:-3]];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_textLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1 constant:16-ProgressIndicatorOuterMargin]];
|
||||
[self setUpConstraints];
|
||||
|
||||
_circleLayer = [CAShapeLayer layer];
|
||||
static const CGFloat ProgressIndicatorRadius = ProgressIndicatorDiameter/2;
|
||||
_circleLayer.path = [[UIBezierPath bezierPathWithArcCenter:CGPointMake(ProgressIndicatorRadius+ProgressIndicatorOuterMargin, ProgressIndicatorRadius+ProgressIndicatorOuterMargin)
|
||||
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
|
||||
@@ -154,13 +113,65 @@
|
||||
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.duration = duration * 2;
|
||||
animation.removedOnCompletion = YES;
|
||||
animation.values = @[@(1.0), @(0.0), @(0.0)];
|
||||
animation.keyTimes = @[@(0.0), @(0.5), @(1.0)];
|
||||
@@ -224,7 +235,7 @@
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, [@(_countDown) stringValue]);
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @(_countDown).stringValue);
|
||||
[_countdownView startAnimateWithDuration:[(ORKActiveStep *)self.step stepDuration]];
|
||||
}
|
||||
|
||||
@@ -233,7 +244,7 @@
|
||||
}
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished {
|
||||
_countDown = MAX((_countDown-1), 0);
|
||||
_countDown = MAX((_countDown - 1), 0);
|
||||
[self updateCountdownLabel];
|
||||
|
||||
if (UIAccessibilityIsVoiceOverRunning()) {
|
||||
@@ -247,7 +258,7 @@
|
||||
}];
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, ORKLocalizedString(@"AX_ANNOUNCE_BEGIN_TASK", nil));
|
||||
} else {
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, [@(_countDown) stringValue]);
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @(_countDown).stringValue);
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -225,7 +225,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if appending succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)appendObjects:(NSArray *)objects error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Checks whether a file has been marked as uploaded.
|
||||
@@ -251,7 +251,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if adding or removing the attribute succeeded; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Removes files if they are marked uploaded.
|
||||
@@ -265,7 +265,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if removing the files succeeded; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Removes all files managed by this logger (files that have the `logName` prefix).
|
||||
@@ -274,7 +274,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if removing the files succeeded.; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeAllFilesWithError:(NSError *__nullable __autoreleasing *)error;
|
||||
- (BOOL)removeAllFilesWithError:(NSError *_Nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -320,7 +320,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Appends the specified object to the log file.
|
||||
@@ -331,7 +331,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Appends the specified objects to the log file.
|
||||
@@ -342,7 +342,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -518,7 +518,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Removes a set of uploaded files.
|
||||
@@ -532,7 +532,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Removes old and uploaded logs to bring total bytes down to a threshold.
|
||||
@@ -545,7 +545,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
#import "ORKDefines_Private.h"
|
||||
|
||||
|
||||
static const char * kORKDataLoggerUploadedAttr = "com.apple.ResearchKit.uploaded";
|
||||
static const char *ORKDataLoggerUploadedAttr = "com.apple.ResearchKit.uploaded";
|
||||
|
||||
// Default per-logfile settings when a data logger is used in an ORKDataLoggerManager
|
||||
static const NSTimeInterval kORKDataLoggerManagerDefaultLogFileLifetime = 60*60*24*3; // 3 days
|
||||
static const unsigned long long kORKDataLoggerManagerDefaultLogFileSize = 1024*1024; // 1 MB
|
||||
static const NSTimeInterval ORKDataLoggerManagerDefaultLogFileLifetime = 60 * 60 * 24 * 3; // 3 days
|
||||
static const unsigned long long ORKDataLoggerManagerDefaultLogFileSize = 1024 * 1024; // 1 MB
|
||||
|
||||
static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLoggerManagerConfiguration";
|
||||
static NSString *const ORKDataLoggerManagerConfigurationFilename = @".ORKDataLoggerManagerConfiguration";
|
||||
|
||||
|
||||
@interface ORKDataLogger ()
|
||||
@@ -80,7 +80,7 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
- (NSString *)ork_logName {
|
||||
NSString *lastComponent = [self lastPathComponent];
|
||||
NSRange idx = [lastComponent rangeOfString:@"-"];
|
||||
if (! idx.length) {
|
||||
if (!idx.length) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a completed log file" userInfo:@{@"url":self}];
|
||||
}
|
||||
|
||||
@@ -91,28 +91,28 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
- (NSString *)ork_logDateComponent {
|
||||
NSString *lastComponent = [self lastPathComponent];
|
||||
NSRange idx = [lastComponent rangeOfString:@"-"];
|
||||
if (! idx.length) {
|
||||
if (!idx.length) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a completed log file" userInfo:@{@"url":self}];
|
||||
}
|
||||
|
||||
NSString *logDateComponent = [lastComponent substringFromIndex:idx.location+1];
|
||||
NSString *logDateComponent = [lastComponent substringFromIndex:idx.location + 1];
|
||||
return logDateComponent;
|
||||
}
|
||||
|
||||
- (BOOL)ork_isUploaded {
|
||||
NSData *data = [self ork_dataForAttr:kORKDataLoggerUploadedAttr];
|
||||
NSData *data = [self ork_dataForAttr:ORKDataLoggerUploadedAttr];
|
||||
if (!data) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
return ([string integerValue] != 0);
|
||||
return (string.integerValue != 0);
|
||||
}
|
||||
|
||||
- (BOOL)ork_setUploaded:(BOOL)uploaded error:(NSError * __autoreleasing *)error {
|
||||
NSString *value = (uploaded ? @"1" : @"0");
|
||||
NSData *encodedString = [value dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [self ork_setData:encodedString forAttr:kORKDataLoggerUploadedAttr error:error];
|
||||
return [self ork_setData:encodedString forAttr:ORKDataLoggerUploadedAttr error:error];
|
||||
}
|
||||
|
||||
- (NSData *)ork_dataForAttr:(const char *)attr {
|
||||
@@ -125,7 +125,7 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
}
|
||||
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
length = getxattr(path, attr, [data mutableBytes], length, 0, 0);
|
||||
length = getxattr(path, attr, data.mutableBytes, length, 0, 0);
|
||||
if (length <= 0) {
|
||||
return nil;
|
||||
}
|
||||
@@ -135,7 +135,7 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
|
||||
- (BOOL)ork_setData:(NSData *)data forAttr:(const char *)attr error:(NSError * __autoreleasing *)error {
|
||||
const char *path = [self fileSystemRepresentation];
|
||||
int rc = setxattr(path, attr, [data bytes], [data length], 0, 0);
|
||||
int rc = setxattr(path, attr, data.bytes, data.length, 0, 0);
|
||||
if (rc != 0) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:rc userInfo:@{NSLocalizedDescriptionKey : ORKLocalizedString(@"ERROR_DATALOGGER_SET_ATTRIBUTE", nil)}];
|
||||
@@ -145,13 +145,13 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
}
|
||||
|
||||
- (NSString *)ork_logNameInDirectory:(NSURL *)directory {
|
||||
if (! [self isFileURL]) {
|
||||
if (![self isFileURL]) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a fileURL" userInfo:@{@"url":self}];
|
||||
}
|
||||
|
||||
NSString *lastComponent = [self lastPathComponent];
|
||||
NSRange idx = [lastComponent rangeOfString:@"-"];
|
||||
if (! idx.length) {
|
||||
if (!idx.length) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a completed log file" userInfo:@{@"url":self}];
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ static void *ORKObjectObserverContext = &ORKObjectObserverContext;
|
||||
}
|
||||
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
if (! [self canAcceptLogObject:object]) {
|
||||
if (![self canAcceptLogObject:object]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"ORKLogFormatter accepts NSData only" userInfo:nil];
|
||||
}
|
||||
return [self writeData:(NSData *)object fileHandle:fileHandle error:error];
|
||||
@@ -285,7 +285,7 @@ static void *ORKObjectObserverContext = &ORKObjectObserverContext;
|
||||
}
|
||||
}
|
||||
|
||||
if (! success) {
|
||||
if (!success) {
|
||||
[self rollbackToCheckpoint:checkpoint fileHandle:fileHandle];
|
||||
if (error) {
|
||||
*error = errorOut;
|
||||
@@ -312,8 +312,8 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
if (self) {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_ORKJSON_emptyLogLength = [[kJSONLogEmptyLogString dataUsingEncoding:NSUTF8StringEncoding] length];
|
||||
_ORKJSON_terminatorLength = [[kJSONLogFooterString dataUsingEncoding:NSUTF8StringEncoding] length];
|
||||
_ORKJSON_emptyLogLength = [kJSONLogEmptyLogString dataUsingEncoding:NSUTF8StringEncoding].length;
|
||||
_ORKJSON_terminatorLength = [kJSONLogFooterString dataUsingEncoding:NSUTF8StringEncoding].length;
|
||||
});
|
||||
}
|
||||
return self;
|
||||
@@ -361,15 +361,15 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
* object being appended, and the footer bytes.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
if (! fileHandle) {
|
||||
if (!fileHandle) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Filehandle is nil" userInfo:nil];
|
||||
}
|
||||
NSInteger numObjects = [objects count];
|
||||
NSInteger numObjects = objects.count;
|
||||
if (numObjects == 0) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"No objects" userInfo:nil];
|
||||
}
|
||||
for (NSObject *object in objects) {
|
||||
if (! [self canAcceptLogObject:object]) {
|
||||
if (![self canAcceptLogObject:object]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"ORKLogFormatter accepts JSON serializable objects only" userInfo:nil];
|
||||
}
|
||||
}
|
||||
@@ -406,7 +406,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
}
|
||||
}];
|
||||
if (! success) {
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -417,7 +417,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
|
||||
success = [self writeData:outputData fileHandle:fileHandle error:error];
|
||||
|
||||
if (! success) {
|
||||
if (!success) {
|
||||
[self rollbackToCheckpoint:checkpoint fileHandle:fileHandle];
|
||||
}
|
||||
|
||||
@@ -456,13 +456,13 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_url = [url copy];
|
||||
if (! [[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"directory does not exist" userInfo:nil];
|
||||
}
|
||||
if ([logName hasSuffix:@"-"]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"logName should not terminate with '-'" userInfo:nil];
|
||||
}
|
||||
if (! [logName length]) {
|
||||
if (!logName.length) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"logName must be non-empty" userInfo:nil];
|
||||
}
|
||||
|
||||
@@ -486,7 +486,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
|
||||
- (instancetype)initWithDirectory:(NSURL *)url configuration:(NSDictionary *)configuration delegate:(id<ORKDataLoggerDelegate>)delegate {
|
||||
Class formatterClass = NSClassFromString(configuration[@"formatterClass"]);
|
||||
if (! formatterClass) {
|
||||
if (!formatterClass) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:[NSString stringWithFormat:@"%@ is not a class", configuration[@"formatterClass"]] userInfo:nil];
|
||||
}
|
||||
|
||||
@@ -494,8 +494,8 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
if (self) {
|
||||
// Don't notify about initial setup
|
||||
[_observer pause];
|
||||
self.maximumCurrentLogFileSize = [configuration[@"maximumCurrentLogFileSize"] unsignedLongValue];
|
||||
self.maximumCurrentLogFileLifetime = [configuration[@"maximumCurrentLogFileLifetime"] doubleValue];
|
||||
self.maximumCurrentLogFileSize = ((NSNumber *)configuration[@"maximumCurrentLogFileSize"]).unsignedLongValue;
|
||||
self.maximumCurrentLogFileLifetime = ((NSNumber *)configuration[@"maximumCurrentLogFileLifetime"]).doubleValue;
|
||||
[_observer resume];
|
||||
}
|
||||
return self;
|
||||
@@ -515,7 +515,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
- (void)setupDirectorySource {
|
||||
int dirFD = open([_url fileSystemRepresentation], O_EVTONLY);
|
||||
if (dirFD < 0) {
|
||||
ORK_Log_Oops(@"Could not track directory %s (%d)", [_url fileSystemRepresentation], [[NSFileManager defaultManager] fileExistsAtPath:[_url path]]);
|
||||
ORK_Log_Warning(@"Could not track directory %s (%d)", [_url fileSystemRepresentation], [[NSFileManager defaultManager] fileExistsAtPath:[_url path]]);
|
||||
} else {
|
||||
// Dispatch to a concurrent queue, so we don't store up blocks while our
|
||||
// queue is working.
|
||||
@@ -614,7 +614,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
|
||||
- (BOOL)appendObjects:(NSArray *)objects error:(NSError * __autoreleasing *)error {
|
||||
if (![objects count]) {
|
||||
if (!objects.count) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Empty array" userInfo:nil];
|
||||
}
|
||||
__block BOOL success = NO;
|
||||
@@ -664,7 +664,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
|
||||
- (void)queue_setNeedsUpdateBytes {
|
||||
if (! _directoryDirty) {
|
||||
if (!_directoryDirty) {
|
||||
_directoryDirty = YES;
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), _queue, ^{
|
||||
@@ -704,7 +704,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
NSError *errorOut = nil;
|
||||
NSMutableArray *urls = [NSMutableArray array];
|
||||
for (NSURL *url in enumerator) {
|
||||
if (! [self urlMatchesLogName:url]) {
|
||||
if (![self urlMatchesLogName:url]) {
|
||||
continue;
|
||||
}
|
||||
if ( [[url lastPathComponent] isEqualToString:_logName]) {
|
||||
@@ -716,13 +716,13 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
// If there's been an error getting the resource values, give up
|
||||
break;
|
||||
}
|
||||
if (! [resources[NSURLIsRegularFileKey] boolValue]) {
|
||||
if (!((NSNumber *)resources[NSURLIsRegularFileKey]).boolValue) {
|
||||
continue;
|
||||
}
|
||||
[urls addObject:url];
|
||||
}
|
||||
|
||||
if (! errorOut) {
|
||||
if (!errorOut) {
|
||||
// Sort the URLs before beginning enumeration for the caller
|
||||
[urls sortUsingComparator:^NSComparisonResult(NSURL *obj1, NSURL *obj2) {
|
||||
// We can assume all relate to files in the same directory
|
||||
@@ -766,12 +766,12 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
NSNumber *fileExists = nil;
|
||||
[url getResourceValue:&fileExists forKey:NSURLIsRegularFileKey error:nil];
|
||||
|
||||
BOOL createNewFile = ! [fileExists boolValue];
|
||||
BOOL createNewFile = !fileExists.boolValue;
|
||||
|
||||
NSFileHandle *fileHandle = nil;
|
||||
if (!createNewFile) {
|
||||
fileHandle = [NSFileHandle fileHandleForWritingToURL:url error:error];
|
||||
if (! fileHandle) {
|
||||
if (!fileHandle) {
|
||||
// Assume it's because we can't open the file, perhaps for security reasons.
|
||||
// Close and rename the log.
|
||||
[self queue_closeAndRenameLog];
|
||||
@@ -858,19 +858,19 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
|
||||
// Check if a non-empty file exists, and create the file handle if so
|
||||
NSDictionary *params = [url resourceValuesForKeys:@[NSURLIsRegularFileKey,NSURLFileSizeKey] error:nil];
|
||||
NSDictionary *parameters = [url resourceValuesForKeys:@[NSURLIsRegularFileKey,NSURLFileSizeKey] error:nil];
|
||||
|
||||
if ( [params[NSURLIsRegularFileKey] boolValue]) {
|
||||
if ([params[NSURLFileSizeKey] intValue] > 0) {
|
||||
if (((NSNumber *)parameters[NSURLIsRegularFileKey]).boolValue) {
|
||||
if (((NSNumber *)parameters[NSURLFileSizeKey]).intValue > 0) {
|
||||
NSURL *destinationUrl = [ORKDataLogger nextUrlForDirectoryUrl:_url logName:_logName];
|
||||
ORK_Log_Debug(@"Rollover: %@ to %@", [url lastPathComponent], [destinationUrl lastPathComponent]);
|
||||
[fileManager moveItemAtURL:url toURL:destinationUrl error:nil];
|
||||
if (self.fileProtectionMode == ORKFileProtectionCompleteUnlessOpen) {
|
||||
// Upgrade to complete file protection after roll-over
|
||||
NSError *error = nil;
|
||||
if (! [fileManager setAttributes:@{NSFileProtectionKey : NSFileProtectionComplete}
|
||||
if (![fileManager setAttributes:@{NSFileProtectionKey : NSFileProtectionComplete}
|
||||
ofItemAtPath:[destinationUrl path] error:&error]) {
|
||||
ORK_Log_Debug(@"Error setting NSFileProtectionComplete on %@: %@", destinationUrl, error);
|
||||
ORK_Log_Warning(@"Error setting NSFileProtectionComplete on %@: %@", destinationUrl, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,7 +889,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
NSURL *url = [self currentLogFileURL];
|
||||
NSDictionary *parameters = [url resourceValuesForKeys:@[NSURLIsRegularFileKey, NSURLFileSizeKey, NSURLCreationDateKey] error:nil];
|
||||
|
||||
NSInteger fileSize = [parameters[NSURLFileSizeKey] integerValue];
|
||||
NSInteger fileSize = ((NSNumber *)parameters[NSURLFileSizeKey]).integerValue;
|
||||
NSDate *creationDate = parameters[NSURLCreationDateKey];
|
||||
|
||||
BOOL exceededSizeThreshold = ( (self.maximumCurrentLogFileSize > 0) && (fileSize >= self.maximumCurrentLogFileSize));
|
||||
@@ -968,7 +968,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
} error:error];
|
||||
|
||||
// Reporting multiple errors
|
||||
if ([errors count]) {
|
||||
if (errors.count) {
|
||||
if (!success && error && *error) {
|
||||
[errors addObject:*error];
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorMultipleErrors userInfo:@{NSLocalizedDescriptionKey : ORKLocalizedString(@"ERROR_DATALOGGER_MULTIPLE", nil), @"errors" : errors}];
|
||||
@@ -1052,7 +1052,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_directory = directory;
|
||||
if (! [[NSFileManager defaultManager] fileExistsAtPath:[_directory path]]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[_directory path]]) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"directory does not exist" userInfo:nil];
|
||||
}
|
||||
_delegate = delegate;
|
||||
@@ -1065,39 +1065,43 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary *configuration = [NSDictionary dictionaryWithContentsOfURL:[_directory URLByAppendingPathComponent:kORKDataLoggerManagerConfigurationFilename]];
|
||||
NSDictionary *configuration = [NSDictionary dictionaryWithContentsOfURL:[_directory URLByAppendingPathComponent:ORKDataLoggerManagerConfigurationFilename]];
|
||||
[self loadConfiguration:configuration];
|
||||
|
||||
_observer = [[ORKObjectObserver alloc] initWithObject:self keys:@[@"pendingUploadBytesThreshold",@"totalBytesThreshold"] selector:@selector(configurationDidChange)];
|
||||
_observer = [[ORKObjectObserver alloc] initWithObject:self keys:@[@"pendingUploadBytesThreshold", @"totalBytesThreshold"] selector:@selector(configurationDidChange)];
|
||||
|
||||
[self setNeedsUpdateBytes];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
static NSString *const PendingUploadBytesThresholdKey = @"pendingUploadBytesThreshold";
|
||||
static NSString *const TotalBytesThresholdKey = @"totalBytesThreshold";
|
||||
static NSString *const LoggerConfigurationsKey = @"loggers";
|
||||
|
||||
- (void)loadConfiguration:(NSDictionary *)configuration {
|
||||
self.pendingUploadBytesThreshold = [configuration[@"pendingUploadBytesThreshold"] unsignedLongLongValue];
|
||||
self.totalBytesThreshold = [configuration[@"totalBytesThreshold"] unsignedLongLongValue];
|
||||
self.pendingUploadBytesThreshold = ((NSNumber *)configuration[PendingUploadBytesThresholdKey]).unsignedLongLongValue;
|
||||
self.totalBytesThreshold = ((NSNumber *)configuration[TotalBytesThresholdKey]).unsignedLongLongValue;
|
||||
|
||||
NSMutableDictionary *records = [NSMutableDictionary dictionary];
|
||||
for (NSDictionary *loggerConfig in configuration[@"loggers"]) {
|
||||
ORKDataLogger *logger = [[ORKDataLogger alloc] initWithDirectory:_directory configuration:loggerConfig delegate:self];
|
||||
for (NSDictionary *loggerConfiguration in configuration[LoggerConfigurationsKey]) {
|
||||
ORKDataLogger *logger = [[ORKDataLogger alloc] initWithDirectory:_directory configuration:loggerConfiguration delegate:self];
|
||||
records[logger.logName] = logger;
|
||||
}
|
||||
_records = records;
|
||||
}
|
||||
|
||||
- (NSDictionary *)queue_configuration {
|
||||
NSMutableArray *loggerConfigurations = [[_records allValues] valueForKey:@"configuration"];
|
||||
NSMutableArray *loggerConfigurations = [_records.allValues valueForKey:@"configuration"];
|
||||
|
||||
return @{@"pendingUploadBytesThreshold" : @(self.pendingUploadBytesThreshold),
|
||||
@"totalBytesThreshold" : @(self.totalBytesThreshold),
|
||||
@"loggers" : loggerConfigurations };
|
||||
return @{PendingUploadBytesThresholdKey : @(self.pendingUploadBytesThreshold),
|
||||
TotalBytesThresholdKey : @(self.totalBytesThreshold),
|
||||
LoggerConfigurationsKey : loggerConfigurations };
|
||||
}
|
||||
|
||||
- (void)queue_synchronizeConfiguration {
|
||||
NSDictionary *configuration = [self queue_configuration];
|
||||
[configuration writeToURL:[_directory URLByAppendingPathComponent:kORKDataLoggerManagerConfigurationFilename] atomically:YES];
|
||||
[configuration writeToURL:[_directory URLByAppendingPathComponent:ORKDataLoggerManagerConfigurationFilename] atomically:YES];
|
||||
}
|
||||
|
||||
- (void)configurationDidChange {
|
||||
@@ -1114,8 +1118,8 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
ORKDataLogger *dataLogger = [[ORKDataLogger alloc] initWithDirectory:_directory logName:logName formatter:formatter delegate:self];
|
||||
dataLogger.delegate = nil;
|
||||
// Pick suitable defaults for a typical use pattern
|
||||
dataLogger.maximumCurrentLogFileLifetime = kORKDataLoggerManagerDefaultLogFileLifetime;
|
||||
dataLogger.maximumCurrentLogFileSize = kORKDataLoggerManagerDefaultLogFileSize;
|
||||
dataLogger.maximumCurrentLogFileLifetime = ORKDataLoggerManagerDefaultLogFileLifetime;
|
||||
dataLogger.maximumCurrentLogFileSize = ORKDataLoggerManagerDefaultLogFileSize;
|
||||
dataLogger.delegate = self;
|
||||
|
||||
_records[logName] = dataLogger;
|
||||
@@ -1166,7 +1170,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
- (NSArray<NSString *> *)logNames {
|
||||
__block NSArray<NSString *> *logNames = nil;
|
||||
dispatch_sync(_queue, ^{
|
||||
logNames = [_records allKeys];
|
||||
logNames = _records.allKeys;
|
||||
});
|
||||
return logNames;
|
||||
}
|
||||
@@ -1175,7 +1179,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
BOOL success = YES;
|
||||
NSMutableArray *allFiles = [NSMutableArray array];
|
||||
// Collect all the log file URLs so we can sort them by date rather than enumerating by logger.
|
||||
for (ORKDataLogger *logger in [_records allValues]) {
|
||||
for (ORKDataLogger *logger in _records.allValues) {
|
||||
success = [logger enumerateLogsNeedingUpload:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
[allFiles addObject:logFileUrl];
|
||||
} error:error];
|
||||
@@ -1228,7 +1232,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
for (NSURL *url in fileURLs) {
|
||||
NSString *logName = [url ork_logNameInDirectory:_directory];
|
||||
|
||||
if (! _records[logName]) {
|
||||
if (!_records[logName]) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not from a known logger" userInfo:@{@"url":url}];
|
||||
}
|
||||
|
||||
@@ -1239,7 +1243,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
success = NO;
|
||||
}
|
||||
}
|
||||
if (error && [notRemoved count]) {
|
||||
if (error && notRemoved.count) {
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorMultipleErrors userInfo:@{@"notRemoved":notRemoved}];
|
||||
}
|
||||
return success;
|
||||
@@ -1260,7 +1264,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
for (NSURL *url in fileURLs) {
|
||||
NSString *logName = [url ork_logNameInDirectory:_directory];
|
||||
ORKDataLogger *logger = _records[logName];
|
||||
if (! logger) {
|
||||
if (!logger) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not from a known logger" userInfo:@{@"url":url}];
|
||||
}
|
||||
|
||||
@@ -1271,7 +1275,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
success = NO;
|
||||
}
|
||||
}
|
||||
if (error && [notRemoved count]) {
|
||||
if (error && notRemoved.count) {
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorMultipleErrors userInfo:@{@"notRemoved":notRemoved}];
|
||||
}
|
||||
return success;
|
||||
@@ -1300,7 +1304,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
|
||||
if (totalBytes > bytes) {
|
||||
for (ORKDataLogger *logger in [_records allValues]) {
|
||||
for (ORKDataLogger *logger in _records.allValues) {
|
||||
[logger enumerateLogsAlreadyUploaded:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
unsigned long long fileSize = [[fileManager attributesOfItemAtPath:[logFileUrl path] error:nil] fileSize];
|
||||
if (fileSize > 0) {
|
||||
@@ -1353,7 +1357,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
- (void)queue_updateBytes {
|
||||
unsigned long long pending = 0;
|
||||
unsigned long long uploaded = 0;
|
||||
for (ORKDataLogger *logger in [_records allValues]) {
|
||||
for (ORKDataLogger *logger in _records.allValues) {
|
||||
pending += logger.pendingBytes;
|
||||
uploaded += logger.uploadedBytes;
|
||||
}
|
||||
@@ -1367,14 +1371,14 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
if (exceededPendingThreshold && !_pendingUploadDelegateSent) {
|
||||
[self.delegate dataLoggerManager:self pendingUploadBytesReachedThreshold:pending];
|
||||
_pendingUploadDelegateSent = YES;
|
||||
} else if (! exceededPendingThreshold) {
|
||||
} else if (!exceededPendingThreshold) {
|
||||
_pendingUploadDelegateSent = NO;
|
||||
}
|
||||
|
||||
if (exceededTotalThreshold && !_totalBytesDelegateSent) {
|
||||
[self.delegate dataLoggerManager:self totalBytesReachedThreshold:(pending + uploaded)];
|
||||
_totalBytesDelegateSent = YES;
|
||||
} else if (! exceededTotalThreshold) {
|
||||
} else if (!exceededTotalThreshold) {
|
||||
_totalBytesDelegateSent = NO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,17 +84,17 @@
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.motionManager = [self createMotionManager];
|
||||
self.motionManager.deviceMotionUpdateInterval = 1.0/_frequency;
|
||||
self.motionManager.deviceMotionUpdateInterval = 1.0 / _frequency;
|
||||
|
||||
self.uptime = [NSProcessInfo processInfo].systemUptime;
|
||||
|
||||
@@ -165,11 +165,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKDeviceMotionRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKDeviceMotionRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
|
||||
@@ -48,8 +48,6 @@
|
||||
ORKTintedImageView *_imageView;
|
||||
NSLengthFormatter *_lengthFormatter;
|
||||
NSLayoutConstraint *_imageRatioConstraint;
|
||||
ORKScreenType _screenType;
|
||||
NSArray *_constraints;
|
||||
NSLayoutConstraint *_topConstraint;
|
||||
}
|
||||
|
||||
@@ -69,13 +67,12 @@
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_screenType = ORKScreenTypeiPhone4;
|
||||
_timerLabel = [ORKQuantityLabel new];
|
||||
_quantityPairView = [ORKQuantityPairView new];
|
||||
_imageSpacer1 = [UIView new];
|
||||
[_imageSpacer1 setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
_imageSpacer1.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_imageSpacer2 = [UIView new];
|
||||
[_imageSpacer2 setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
_imageSpacer2.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_imageSpacer1];
|
||||
[self addSubview:_imageSpacer2];
|
||||
[self heartRateView].image = [UIImage imageNamed:@"heart-fitness" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
|
||||
@@ -96,7 +93,7 @@
|
||||
self.hasDistance = _hasDistance;
|
||||
|
||||
#if LAYOUT_TEST
|
||||
self.timeLeft = 60*5;
|
||||
self.timeLeft = 60 * 5;
|
||||
self.hasHeartRate = YES;
|
||||
self.hasDistance = YES;
|
||||
self.distanceInMeters = 100;
|
||||
@@ -113,7 +110,7 @@
|
||||
[self addSubview:_quantityPairView];
|
||||
[self addSubview:_imageView];
|
||||
[self addSubview:_timerLabel];
|
||||
[self setupConstraints];
|
||||
[self setUpConstraints];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(localeDidChange:) name:NSCurrentLocaleDidChangeNotification object:nil];
|
||||
|
||||
@@ -139,105 +136,114 @@
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
_screenType = ORKGetVerticalScreenTypeForWindow(newWindow);
|
||||
[self updateConstraintConstants];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstants {
|
||||
ORKScreenType screenType = _screenType;
|
||||
const CGFloat CaptionBaselineToTimerTop = ORKGetMetricForScreenType(ORKScreenMetricCaptionBaselineToFitnessTimerTop, screenType);
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForScreenType(ORKScreenMetricLearnMoreBaselineToStepViewTop, screenType);
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat CaptionBaselineToTimerTop = ORKGetMetricForWindow(ORKScreenMetricCaptionBaselineToFitnessTimerTop, window);
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
_topConstraint.constant = (CaptionBaselineToTimerTop - CaptionBaselineToStepViewTop);
|
||||
}
|
||||
|
||||
- (void)setupConstraints {
|
||||
- (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]];
|
||||
|
||||
[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
|
||||
constant:0];
|
||||
[self updateConstraintConstants];
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
[constraints addObject:_topConstraint];
|
||||
[constraints addObject:[NSLayoutConstraint
|
||||
constraintWithItem:_timerLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
[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
|
||||
constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_imageView][_imageSpacer2(>=0)][_quantityPairView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_imageView][_imageSpacer2(>=0)][_quantityPairView]|"
|
||||
options:0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer2
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_imageSpacer2
|
||||
attribute:NSLayoutAttributeHeight
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:1000];
|
||||
constraint1.priority = UILayoutPriorityDefaultLow-1;
|
||||
[constraints addObject:constraint1];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_quantityPairView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
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];
|
||||
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
[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
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired-1;
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
[self addConstraints:constraints];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
}
|
||||
|
||||
- (void)setImage:(UIImage *)image {
|
||||
@@ -253,8 +259,8 @@
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_imageView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:size
|
||||
.height/size.width constant:0];
|
||||
multiplier:size.height / size.width
|
||||
constant:0.0];
|
||||
_imageRatioConstraint.active = YES;
|
||||
}
|
||||
}
|
||||
@@ -277,7 +283,7 @@
|
||||
}
|
||||
|
||||
- (void)updateKeylineVisible {
|
||||
[_quantityPairView setKeylineHidden:! (_hasDistance && _hasHeartRate)];
|
||||
[_quantityPairView setKeylineHidden:!(_hasDistance && _hasHeartRate)];
|
||||
}
|
||||
|
||||
- (void)setDistanceInMeters:(double)distanceInMeters {
|
||||
@@ -304,7 +310,7 @@
|
||||
double conversionFactor = 1.0;
|
||||
if ([hkUnit isNull] && (unit == NSLengthFormatterUnitYard)) {
|
||||
hkUnit = [HKUnit footUnit];
|
||||
conversionFactor = 1/3.0;
|
||||
conversionFactor = 1.0 / 3.0;
|
||||
}
|
||||
HKQuantity *quantity = [HKQuantity quantityWithUnit:[HKUnit meterUnit] doubleValue:displayDistance];
|
||||
distanceString = [_lengthFormatter.numberFormatter stringFromNumber:@([quantity doubleValueForUnit:hkUnit]*conversionFactor)];
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
|
||||
NSTimeInterval const ORKFitnessStepMinimumDuration = 5.0;
|
||||
|
||||
if ( self.stepDuration < ORKFitnessStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"rest duration can not be shorter than %@ seconds.", @(ORKFitnessStepMinimumDuration)] userInfo:nil];
|
||||
if (self.stepDuration < ORKFitnessStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"rest duration cannot be shorter than %@ seconds.", @(ORKFitnessStepMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
|
||||
- (void)query_logResults:(NSArray *)results withAnchor:(NSUInteger)newAnchor {
|
||||
|
||||
NSUInteger resultCount = [results count];
|
||||
NSUInteger resultCount = results.count;
|
||||
if (resultCount == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -103,10 +103,10 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self updateMostRecentSample:[results lastObject]];
|
||||
[self updateMostRecentSample:results.lastObject];
|
||||
|
||||
NSError *error = nil;
|
||||
if (! [_logger appendObjects:dictionaries error:&error]) {
|
||||
if (![_logger appendObjects:dictionaries error:&error]) {
|
||||
// Logger writes are unrecoverable
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
@@ -122,7 +122,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
|
||||
- (void)doFetchNewData {
|
||||
if (! _healthStore || ! _isRecording) {
|
||||
if (!_healthStore || !_isRecording) {
|
||||
return;
|
||||
}
|
||||
NSAssert(_samplePredicate != nil, @"Sample predicate should be non-nil if recording");
|
||||
@@ -137,7 +137,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
{
|
||||
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_Debug(@"Anchored query error: %@", error);
|
||||
ORK_Log_Warning(@"Anchored query error: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,16 +151,16 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (! [HKHealthStore isHealthDataAvailable]) {
|
||||
if (![HKHealthStore isHealthDataAvailable]) {
|
||||
[self finishRecordingWithError:[NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}]];
|
||||
@@ -178,16 +178,6 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
}
|
||||
|
||||
[_healthStore enableBackgroundDeliveryForType:_quantityType
|
||||
frequency:HKUpdateFrequencyImmediate
|
||||
withCompletion:^(BOOL success, NSError *error) {
|
||||
|
||||
// Doesn't really matter if this succeeds, but nice if it does.
|
||||
if (! success) {
|
||||
ORK_Log_Debug(@"Failed to enable background delivery: %@", error);
|
||||
}
|
||||
}];
|
||||
|
||||
_lastSample = nil;
|
||||
_samplePredicate = [HKQuery predicateForSamplesWithStartDate:[NSDate date] endDate:nil options:HKQueryOptionStrictStartDate];
|
||||
|
||||
@@ -222,7 +212,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (! _isRecording) {
|
||||
if (!_isRecording) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -275,11 +265,6 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKHealthQuantityTypeRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHealthQuantityTypeRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
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 "ORKCustomStepView_Internal.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
|
||||
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
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 "ORKHolePegTestPlacePegView.h"
|
||||
#import "ORKHolePegTestPlaceHoleView.h"
|
||||
#import "ORKDirectionView.h"
|
||||
#import "ORKHelpers.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
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
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 <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlaceHoleView : UIView
|
||||
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
@property (nonatomic, assign, getter = isSuccess) BOOL success;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
|
||||
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 {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
strongSelf.success = NO;
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlacePegView : UIView
|
||||
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
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
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 <ResearchKit/ResearchKit_Private.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) int numberOfPegs;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
|
||||
@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 can not be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlaceStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
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 "ORKHolePegTestPlaceStep.h"
|
||||
#import "ORKHolePegTestPlaceContentView.h"
|
||||
#import "ORKActiveStepViewController_internal.h"
|
||||
#import "ORKStepViewController_internal.h"
|
||||
#import "ORKActiveStepView.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
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
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 "ORKCustomStepView_Internal.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
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
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 "ORKHolePegTestRemovePegView.h"
|
||||
#import "ORKSeparatorView.h"
|
||||
#import "ORKDirectionView.h"
|
||||
#import "ORKHelpers.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
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestRemovePegView : UIView
|
||||
|
||||
@property (nonatomic, assign, getter = isSuccess) BOOL success;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
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 <ResearchKit/ResearchKit_Private.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) int numberOfPegs;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
|
||||
@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 can not be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestRemoveStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
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 "ORKHolePegTestRemoveStep.h"
|
||||
#import "ORKHolePegTestRemoveContentView.h"
|
||||
#import "ORKActiveStepViewController_internal.h"
|
||||
#import "ORKStepViewController_internal.h"
|
||||
#import "ORKActiveStepView.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
|
||||
@@ -75,10 +75,10 @@
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
@@ -91,7 +91,7 @@
|
||||
self.locationManager.pausesLocationUpdatesAutomatically = NO;
|
||||
self.locationManager.delegate = self;
|
||||
|
||||
if (! self.locationManager) {
|
||||
if (!self.locationManager) {
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}];
|
||||
@@ -128,10 +128,10 @@
|
||||
- (void)locationManager:(CLLocationManager *)manager
|
||||
didUpdateLocations:(NSArray *)locations {
|
||||
BOOL success = YES;
|
||||
NSParameterAssert([locations count] >= 0);
|
||||
NSParameterAssert(locations.count >= 0);
|
||||
NSError *error = nil;
|
||||
if (locations) {
|
||||
NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:[locations count]];
|
||||
NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:locations.count];
|
||||
[locations enumerateObjectsUsingBlock:^(CLLocation *obj, NSUInteger idx, BOOL *stop) {
|
||||
NSDictionary *d = [obj ork_JSONDictionary];
|
||||
[dictionaries addObject:d];
|
||||
@@ -169,11 +169,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKLocationRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKLocationRecorderConfiguration
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
|
||||
@@ -43,8 +43,6 @@
|
||||
@property (nonatomic, assign, getter = isAuditory) BOOL auditory;
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
@property (nonatomic, strong) ORKTapCountLabel *digitLabel;
|
||||
@property (nonatomic, assign) ORKScreenType screenType;
|
||||
@property (nonatomic, strong) NSArray *constraints;
|
||||
|
||||
@end
|
||||
|
||||
@@ -65,8 +63,6 @@
|
||||
|
||||
if (self) {
|
||||
|
||||
_screenType = ORKGetVerticalScreenTypeForWindow(self.window);
|
||||
|
||||
_digitLabel = [ORKTapCountLabel new];
|
||||
_digitLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_digitLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
@@ -113,6 +109,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (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:^{
|
||||
@@ -121,42 +122,40 @@
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([self.constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
self.constraints = nil;
|
||||
}
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
|
||||
const CGFloat ORKPSATKeyboardWidth = ORKGetMetricForScreenType(ORKScreenMetricPSATKeyboardViewWidth, self.screenType);
|
||||
const CGFloat ORKPSATKeyboardHeight = ORKGetMetricForScreenType(ORKScreenMetricPSATKeyboardViewHeight, self.screenType);
|
||||
const CGFloat ORKPSATKeyboardWidth = ORKGetMetricForWindow(ORKScreenMetricPSATKeyboardViewWidth, self.window);
|
||||
const CGFloat ORKPSATKeyboardHeight = ORKGetMetricForWindow(ORKScreenMetricPSATKeyboardViewHeight, self.window);
|
||||
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _digitLabel, _keyboardView);
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"H:|-[_keyboardView(==%f)]-|", ORKPSATKeyboardWidth]
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_keyboardView(==keyboardWidth)]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:@{ @"keyboardWidth": @(ORKPSATKeyboardWidth) }
|
||||
views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:[_keyboardView(==%f)]", ORKPSATKeyboardHeight]
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_keyboardView(==keyboardHeight)]"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:@{ @"keyboardHeight": @(ORKPSATKeyboardHeight) }
|
||||
views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_progressView]-[_digitLabel]-(>=10)-[_keyboardView]-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
self.constraints = constraintsArray;
|
||||
[self addConstraints:self.constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.constraints];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
@@ -83,12 +83,9 @@ NSUInteger const ORKPSATMaximumAnswer = 17;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([self.constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
self.constraints = nil;
|
||||
}
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
ORKBorderedButton *answer3Button = self.answerButtons[0];
|
||||
ORKBorderedButton *answer4Button = self.answerButtons[1];
|
||||
@@ -110,33 +107,30 @@ NSUInteger const ORKPSATMaximumAnswer = 17;
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(answer3Button, answer4Button, answer5Button, answer6Button, answer7Button, answer8Button, answer9Button, answer10Button, answer11Button, answer12Button, answer13Button, answer14Button, answer15Button, answer16Button, answer17Button);
|
||||
|
||||
// First line of answer buttons
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[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
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[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
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[answer13Button]-[answer14Button(==answer13Button)]-[answer15Button(==answer13Button)]-[answer16Button(==answer13Button)]-[answer17Button(==answer13Button)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY|NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
|
||||
metrics:nil views:views]];
|
||||
|
||||
// Align vertically
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[answer3Button]-[answer8Button(==answer3Button)]-[answer13Button(==answer3Button)]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
self.constraints = constraintsArray;
|
||||
[self addConstraints:self.constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.constraints];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *samples;
|
||||
@property (nonatomic, strong) ORKPSATContentView *psatContentView;
|
||||
@property (nonatomic, strong) NSArray *digits;
|
||||
@property (nonatomic, strong) NSArray<NSNumber *> *digits;
|
||||
@property (nonatomic, assign) NSUInteger currentDigitIndex;
|
||||
@property (nonatomic, assign) NSInteger currentAnswer;
|
||||
@property (nonatomic, strong) ORKActiveStepTimer *clearDigitsTimer;
|
||||
@@ -107,7 +107,7 @@
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKPSATResult *PSATResult = [[ORKPSATResult alloc] initWithIdentifier:(NSString *__nonnull)self.step.identifier];
|
||||
ORKPSATResult *PSATResult = [[ORKPSATResult alloc] initWithIdentifier:self.step.identifier];
|
||||
PSATResult.presentationMode = [self psatStep].presentationMode;
|
||||
PSATResult.interStimulusInterval = [self psatStep].interStimulusInterval;
|
||||
if ([self psatStep].presentationMode & ORKPSATPresentationModeVisual) {
|
||||
@@ -116,7 +116,7 @@
|
||||
PSATResult.stimulusDuration = 0.0;
|
||||
}
|
||||
PSATResult.length = [self psatStep].seriesLength;
|
||||
PSATResult.initialDigit = [(NSNumber *)[self.digits objectAtIndex:0] integerValue];
|
||||
PSATResult.initialDigit = self.digits[0].integerValue;
|
||||
NSInteger totalCorrect = 0;
|
||||
BOOL previousAnswerCorrect = NO;
|
||||
NSInteger totalDyad = 0;
|
||||
@@ -146,7 +146,7 @@
|
||||
- (void)start {
|
||||
self.digits = [self arrayWithPSATDigits];
|
||||
self.currentDigitIndex = 0;
|
||||
[self.psatContentView setAddition:self.currentDigitIndex forTotal:[self psatStep].seriesLength withDigit:[self.digits objectAtIndex:self.currentDigitIndex]];
|
||||
[self.psatContentView setAddition:self.currentDigitIndex forTotal:[self psatStep].seriesLength withDigit:self.digits[self.currentDigitIndex]];
|
||||
[self.psatContentView setProgress:0.001 animated:NO];
|
||||
self.currentAnswer = -1;
|
||||
self.samples = [NSMutableArray array];
|
||||
@@ -198,7 +198,7 @@
|
||||
self.answerEnd = 0;
|
||||
|
||||
if (self.currentDigitIndex <= [self psatStep].seriesLength) {
|
||||
[self.psatContentView setAddition:self.currentDigitIndex forTotal:[self psatStep].seriesLength withDigit:[self.digits objectAtIndex:self.currentDigitIndex]];
|
||||
[self.psatContentView setAddition:self.currentDigitIndex forTotal:[self psatStep].seriesLength withDigit:self.digits[self.currentDigitIndex]];
|
||||
}
|
||||
|
||||
self.currentAnswer = -1;
|
||||
@@ -215,8 +215,8 @@
|
||||
|
||||
- (void)saveSample {
|
||||
ORKPSATSample *sample = [[ORKPSATSample alloc] init];
|
||||
NSInteger previousDigit = [(NSNumber *)[self.digits objectAtIndex:self.currentDigitIndex - 1] integerValue];
|
||||
NSInteger currentDigit = [(NSNumber *)[self.digits objectAtIndex:self.currentDigitIndex] integerValue];
|
||||
NSInteger previousDigit = self.digits[self.currentDigitIndex - 1].integerValue;
|
||||
NSInteger currentDigit = self.digits[self.currentDigitIndex].integerValue;;
|
||||
sample.correct = previousDigit + currentDigit == self.currentAnswer ? YES : NO;
|
||||
sample.digit = currentDigit;
|
||||
sample.answer = self.currentAnswer;
|
||||
|
||||
@@ -66,9 +66,9 @@
|
||||
|
||||
- (void)updateStatisticsWithData:(CMPedometerData *)pedometerData {
|
||||
_lastUpdateDate = pedometerData.endDate;
|
||||
_totalNumberOfSteps = [pedometerData.numberOfSteps integerValue];
|
||||
_totalNumberOfSteps = pedometerData.numberOfSteps.integerValue;
|
||||
if (pedometerData.distance) {
|
||||
_totalDistance = [pedometerData.distance doubleValue];
|
||||
_totalDistance = pedometerData.distance.doubleValue;
|
||||
} else {
|
||||
_totalDistance = -1;
|
||||
}
|
||||
@@ -90,10 +90,10 @@
|
||||
_totalNumberOfSteps = 0;
|
||||
_totalDistance = -1;
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
@@ -101,7 +101,7 @@
|
||||
|
||||
self.pedometer = [self createPedometer];
|
||||
|
||||
if (! [[self.pedometer class] isStepCountingAvailable]) {
|
||||
if (![[self.pedometer class] isStepCountingAvailable]) {
|
||||
[self finishRecordingWithError:[NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}]];
|
||||
@@ -179,11 +179,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKPedometerRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKPedometerRecorderConfiguration
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
|
||||
@@ -34,14 +34,9 @@
|
||||
#import "ORKNavigationContainerView.h"
|
||||
|
||||
|
||||
@interface ORKReactionTimeContentView ()
|
||||
|
||||
@property (nonatomic, strong) ORKReactionTimeStimulusView *stimulusView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKReactionTimeContentView
|
||||
@implementation ORKReactionTimeContentView {
|
||||
ORKReactionTimeStimulusView *_stimulusView;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
}
|
||||
if (self.maximumStimulusInterval < self.minimumStimulusInterval) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:@"maximumStimulusInterval can not be less than minimumStimulusInterval"
|
||||
reason:@"maximumStimulusInterval cannot be less than minimumStimulusInterval"
|
||||
userInfo:nil];
|
||||
}
|
||||
if (self.thresholdAcceleration <= 0) {
|
||||
|
||||
@@ -151,7 +151,7 @@ static const NSTimeInterval OutcomeAnimationDuration = 0.3;
|
||||
|
||||
- (void)attemptDidFinish {
|
||||
void (^completion)(void) = ^{
|
||||
if ([_results count] == [self reactionTimeStep].numberOfAttempts) {
|
||||
if (_results.count == [self reactionTimeStep].numberOfAttempts) {
|
||||
[self finish];
|
||||
} else {
|
||||
[self resetAfterDelay:2];
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
}
|
||||
|
||||
- (NSURL *)recordingDirectoryURL {
|
||||
if (! _outputDirectory) {
|
||||
if (!_outputDirectory) {
|
||||
return nil;
|
||||
}
|
||||
return [NSURL fileURLWithPath:[_outputDirectory.path stringByAppendingPathComponent:[NSString stringWithFormat:@"recorder-%@", _recorderUUID.UUIDString]]];
|
||||
@@ -185,7 +185,7 @@
|
||||
|
||||
- (ORKDataLogger *)makeJSONDataLoggerWithError:(NSError * __autoreleasing *)error {
|
||||
NSURL *workingDir = [self recordingDirectoryURL];
|
||||
if (! workingDir) {
|
||||
if (!workingDir) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteInvalidFileNameError userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"ERROR_RECORDER_NO_OUTPUT_DIRECTORY", nil)}];
|
||||
}
|
||||
@@ -221,7 +221,7 @@
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSError *error = nil;
|
||||
if (! [fileManager setAttributes:@{NSFileProtectionKey : ORKFileProtectionFromMode(fileProtection)} ofItemAtPath:[url path] error:&error]) {
|
||||
ORK_Log_Debug(@"Error setting %@ on %@: %@", ORKFileProtectionFromMode(fileProtection), url, error);
|
||||
ORK_Log_Warning(@"Error setting %@ on %@: %@", ORKFileProtectionFromMode(fileProtection), url, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@
|
||||
ORKFileResult *result = [[ORKFileResult alloc] initWithIdentifier:self.identifier];
|
||||
result.contentType = [self mimeType];
|
||||
result.fileURL = fileUrl;
|
||||
result.userInfo = [self userInfo];
|
||||
result.userInfo = self.userInfo;
|
||||
result.startDate = self.startDate;
|
||||
|
||||
[localDelegate recorder:self didCompleteWithResult:result];
|
||||
@@ -242,7 +242,7 @@
|
||||
[self reset];
|
||||
}
|
||||
} else {
|
||||
if (! error) {
|
||||
if (!error) {
|
||||
error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFileReadNoSuchFileError
|
||||
userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"ERROR_RECORDER_NO_DATA", nil)}];
|
||||
|
||||
@@ -36,11 +36,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKDataLogger;
|
||||
|
||||
@interface ORKRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKRecorder ()
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKStep *step;
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
/**
|
||||
The `ORKSpatialSpanGame` class represents a model object that represents one game-like experience in a spatial span memory task.
|
||||
|
||||
A game consists of a subset of a permutation of the integers [0 .. gameSize-1],
|
||||
A game consists of a subset of a permutation of the integers [0 .. gameSize - 1],
|
||||
which represent the sequence of targets that should be tapped.
|
||||
*/
|
||||
@interface ORKSpatialSpanGame : NSObject
|
||||
|
||||
@@ -89,13 +89,13 @@
|
||||
return ORKSpatialSpanResultIgnore;
|
||||
}
|
||||
|
||||
NSInteger sequencePosition = [_plays count];
|
||||
NSInteger sequencePosition = _plays.count;
|
||||
BOOL correct = ([_game tileIndexForStep:sequencePosition] == tileIndex);
|
||||
_states[tileIndex] = correct ? ORKSpatialSpanTargetStateCorrect : ORKSpatialSpanTargetStateIncorrect;
|
||||
if (correct) {
|
||||
[_plays addObject:@(tileIndex)];
|
||||
}
|
||||
if ([_plays count] >= [_game sequenceLength]) {
|
||||
if (_plays.count >= [_game sequenceLength]) {
|
||||
_complete = YES;
|
||||
}
|
||||
|
||||
@@ -103,14 +103,14 @@
|
||||
}
|
||||
|
||||
- (NSInteger)currentStepIndex {
|
||||
return [_plays count];
|
||||
return _plays.count;
|
||||
}
|
||||
|
||||
- (NSInteger)lastSuccessfulTileIndex {
|
||||
if (! [_plays count]) {
|
||||
if (!_plays.count) {
|
||||
return NSNotFound;
|
||||
}
|
||||
return [[_plays lastObject] integerValue];
|
||||
return ((NSNumber *)_plays.lastObject).integerValue;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
|
||||
- (void)resetTilesAnimated:(BOOL)animated {
|
||||
NSArray *currentViews = _tileViews;
|
||||
NSInteger numberOfTilesOld = [_tileViews count];
|
||||
NSInteger numberOfTilesNew = _gridSize.width*_gridSize.height;
|
||||
NSInteger numberOfTilesOld = _tileViews.count;
|
||||
NSInteger numberOfTilesNew = _gridSize.width * _gridSize.height;
|
||||
NSMutableArray *newViews = [NSMutableArray arrayWithCapacity:numberOfTilesNew];
|
||||
NSArray *viewsToRemove = nil;
|
||||
if (numberOfTilesOld <= numberOfTilesNew) {
|
||||
@@ -123,15 +123,15 @@
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
CGRect bounds = [self bounds];
|
||||
CGRect bounds = self.bounds;
|
||||
CGFloat gridItemEdgeLength = ORKFloorToViewScale(MIN(bounds.size.width / _gridSize.width, bounds.size.height / _gridSize.height), self);
|
||||
|
||||
gridItemEdgeLength = MIN(gridItemEdgeLength, 114);
|
||||
CGSize gridItemSize = (CGSize){gridItemEdgeLength, gridItemEdgeLength};
|
||||
|
||||
CGPoint centeringOffset = CGPointZero;
|
||||
centeringOffset.x = 0.5*(bounds.size.width - (gridItemSize.width * _gridSize.width));
|
||||
centeringOffset.y = 0.5*(bounds.size.height - (gridItemSize.height * _gridSize.height));
|
||||
centeringOffset.x = 0.5 * (bounds.size.width - (gridItemSize.width * _gridSize.width));
|
||||
centeringOffset.y = 0.5 * (bounds.size.height - (gridItemSize.height * _gridSize.height));
|
||||
|
||||
NSInteger tileIndex = 0;
|
||||
for (NSInteger x = 0; x < _gridSize.width; x++) {
|
||||
@@ -168,8 +168,6 @@
|
||||
@implementation ORKSpatialSpanMemoryContentView {
|
||||
ORKQuantityPairView *_quantityPairView;
|
||||
ORKNavigationContainerView *_continueView;
|
||||
NSArray *_constraints;
|
||||
NSLayoutConstraint *_topConstraint;
|
||||
}
|
||||
|
||||
- (ORKActiveStepQuantityView *)countView {
|
||||
@@ -221,7 +219,7 @@
|
||||
[self countView].backgroundColor = [[UIColor purpleColor] colorWithAlphaComponent:0.2];
|
||||
#endif
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -274,29 +272,25 @@
|
||||
[self updateMargins];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if (_constraints) {
|
||||
[NSLayoutConstraint deactivateConstraints:_constraints];
|
||||
_constraints = nil;
|
||||
}
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_gameView, _quantityPairView, _continueView);
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[_gameView][_quantityPairView]|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_gameView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:1000.0];
|
||||
constraint1.priority = UILayoutPriorityDefaultLow-1;
|
||||
[constraints addObject:constraint1];
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[_gameView][_quantityPairView]|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
NSLayoutConstraint *gameViewHeightConstraint = [NSLayoutConstraint constraintWithItem:_gameView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
gameViewHeightConstraint.priority = UILayoutPriorityDefaultLow - 1;
|
||||
[constraints addObject:gameViewHeightConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_gameView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
@@ -333,13 +327,10 @@
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired-1;
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
_constraints = constraints;
|
||||
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -76,45 +76,45 @@
|
||||
|
||||
if ( self.initialSpan < ORKSpatialSpanMemoryTaskMinimumInitialSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"initialSpan can not be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumInitialSpan)]
|
||||
reason:[NSString stringWithFormat:@"initialSpan cannot be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumInitialSpan)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if ( self.minimumSpan > self.initialSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"initialSpan can not be less than minimumSpan." userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"initialSpan cannot be less than minimumSpan." userInfo:nil];
|
||||
}
|
||||
|
||||
if ( self.initialSpan > self.maximumSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"maximumSpan can not be less than initialSpan." userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"maximumSpan cannot be less than initialSpan." userInfo:nil];
|
||||
}
|
||||
|
||||
if ( self.maximumSpan > ORKSpatialSpanMemoryTaskMaximumSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"maximumSpan can not be more than %@.", @(ORKSpatialSpanMemoryTaskMaximumSpan)]
|
||||
reason:[NSString stringWithFormat:@"maximumSpan cannot be more than %@.", @(ORKSpatialSpanMemoryTaskMaximumSpan)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.playSpeed < ORKSpatialSpanMemoryTaskMinimumPlaySpeed) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"playSpeed can not be shorter than %@ seconds.", @(ORKSpatialSpanMemoryTaskMinimumPlaySpeed)]
|
||||
reason:[NSString stringWithFormat:@"playSpeed cannot be shorter than %@ seconds.", @(ORKSpatialSpanMemoryTaskMinimumPlaySpeed)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.playSpeed > ORKSpatialSpanMemoryTaskMaximumPlaySpeed) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"playSpeed can not be longer than %@ seconds.", @(ORKSpatialSpanMemoryTaskMaximumPlaySpeed)]
|
||||
reason:[NSString stringWithFormat:@"playSpeed cannot be longer than %@ seconds.", @(ORKSpatialSpanMemoryTaskMaximumPlaySpeed)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.maxTests < ORKSpatialSpanMemoryTaskMinimumMaxTests) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"maxTests can not be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxTests)]
|
||||
reason:[NSString stringWithFormat:@"maxTests cannot be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxTests)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.maxConsecutiveFailures < ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"maxConsecutiveFailures can not be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures)]
|
||||
reason:[NSString stringWithFormat:@"maxConsecutiveFailures cannot be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures)]
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
|
||||
static const NSTimeInterval kMemoryGameActivityTimeout = 20;
|
||||
static const NSTimeInterval MemoryGameActivityTimeout = 20;
|
||||
|
||||
typedef NS_ENUM(NSInteger, ORKSpatialSpanStepState) {
|
||||
ORKSpatialSpanStepStateInitial,
|
||||
@@ -172,7 +172,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (! _state) {
|
||||
if (!_state) {
|
||||
[self transitionToState:ORKSpatialSpanStepStateInitial];
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
#pragma mark UpdateGameRecord
|
||||
|
||||
- (ORKSpatialSpanMemoryGameRecord *)currentGameRecord {
|
||||
return _gameRecords? [_gameRecords lastObject] : nil;
|
||||
return _gameRecords? _gameRecords.lastObject : nil;
|
||||
}
|
||||
|
||||
- (void)createGameRecord {
|
||||
@@ -341,7 +341,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
#pragma mark ORKSpatialSpanStepStateInitial
|
||||
|
||||
- (ORKGridSize)gridSizeForSpan:(NSInteger)span {
|
||||
NSInteger numberOfGridEntriesDesired = span*2;
|
||||
NSInteger numberOfGridEntriesDesired = span * 2;
|
||||
NSInteger value = (NSInteger)ceil(sqrt(numberOfGridEntriesDesired));
|
||||
value = MAX(value, 2);
|
||||
value = MIN(value, 6);
|
||||
@@ -377,7 +377,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
NSInteger sequenceLength = _nextGameSequenceLength;
|
||||
_gridSize = [self gridSizeForSpan:sequenceLength];
|
||||
|
||||
ORKSpatialSpanGame *game = [[ORKSpatialSpanGame alloc] initWithGameSize:_gridSize.width*_gridSize.height sequenceLength:sequenceLength seed:0];
|
||||
ORKSpatialSpanGame *game = [[ORKSpatialSpanGame alloc] initWithGameSize:_gridSize.width * _gridSize.height sequenceLength:sequenceLength seed:0];
|
||||
ORKSpatialSpanGameState *gameState = [[ORKSpatialSpanGameState alloc] initWithGame:game];
|
||||
|
||||
_currentGameState = gameState;
|
||||
@@ -413,7 +413,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
ORKSpatialSpanMemoryStep *step = [self spatialSpanStep];
|
||||
|
||||
NSInteger index = _playbackIndex;
|
||||
NSInteger previousIndex = index-1;
|
||||
NSInteger previousIndex = index - 1;
|
||||
if (step.requireReversal) {
|
||||
// Play the indexes in reverse order when we require reversal. The participant
|
||||
// is then required to tap the sequence in the forward direction, which
|
||||
@@ -426,7 +426,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
[self applyTargetState:ORKSpatialSpanTargetStateQuiescent toSequenceIndex:previousIndex duration:0];
|
||||
|
||||
// The active display should be visible for half the timer interval
|
||||
[self applyTargetState:ORKSpatialSpanTargetStateActive toSequenceIndex:index duration:step.playSpeed/2];
|
||||
[self applyTargetState:ORKSpatialSpanTargetStateActive toSequenceIndex:index duration:(step.playSpeed / 2)];
|
||||
}
|
||||
_playbackIndex++;
|
||||
}
|
||||
@@ -471,7 +471,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
[_activityTimer invalidate];
|
||||
_activityTimer = nil;
|
||||
|
||||
_activityTimer = [NSTimer scheduledTimerWithTimeInterval:kMemoryGameActivityTimeout target:self selector:@selector(activityTimeout) userInfo:nil repeats:NO];
|
||||
_activityTimer = [NSTimer scheduledTimerWithTimeInterval:MemoryGameActivityTimeout target:self selector:@selector(activityTimeout) userInfo:nil repeats:NO];
|
||||
}
|
||||
|
||||
- (void)startGameplay {
|
||||
@@ -522,8 +522,8 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
[gameView setState:ORKSpatialSpanTargetStateCorrect forTileIndex:tileIndex animated:YES];
|
||||
NSInteger stepIndex = [_currentGameState currentStepIndex];
|
||||
|
||||
[self setNumberOfItems:_numberOfItems+1];
|
||||
[self setScore:_score + (round(log2(stepIndex))+1)*5];
|
||||
[self setNumberOfItems:_numberOfItems + 1];
|
||||
[self setScore:_score + (round(log2(stepIndex)) + 1) * 5];
|
||||
|
||||
[self resetActivityTimer];
|
||||
if ([_currentGameState isComplete]) {
|
||||
@@ -544,7 +544,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
ORKSpatialSpanMemoryStep *step = [self spatialSpanStep];
|
||||
if (success) {
|
||||
NSInteger sequenceLength = [_currentGameState.game sequenceLength];
|
||||
[self setScore:_score + (round(log2(sequenceLength))+1)*5];
|
||||
[self setScore:_score + (round(log2(sequenceLength)) + 1) * 5];
|
||||
_gamesCounter++;
|
||||
_consecutiveGamesFailed = 0;
|
||||
_nextGameSequenceLength = MIN(_nextGameSequenceLength + 1, step.maximumSpan);
|
||||
|
||||
@@ -144,7 +144,7 @@ static UIBezierPath *ORKErrorBezierPath() {
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGRect bounds = [self bounds];
|
||||
CGRect bounds = self.bounds;
|
||||
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
[[UIColor clearColor] setFill];
|
||||
@@ -284,7 +284,7 @@ static UIBezierPath *ORKErrorBezierPath() {
|
||||
|
||||
case ORKSpatialSpanTargetStateIncorrect:
|
||||
_flowerView.tintColor = [UIColor ork_redColor];
|
||||
newTransform = CGAffineTransformMakeScale(0.9*_flowerScaleFactor, 0.9*_flowerScaleFactor);
|
||||
newTransform = CGAffineTransformMakeScale(0.9 * _flowerScaleFactor, 0.9 * _flowerScaleFactor);
|
||||
oldCircleAlpha = 0;
|
||||
newCircleAlpha = 1;
|
||||
oldCircleTransform = CGAffineTransformMakeScale(0.2, 0.2);
|
||||
@@ -296,7 +296,7 @@ static UIBezierPath *ORKErrorBezierPath() {
|
||||
|
||||
case ORKSpatialSpanTargetStateCorrect:
|
||||
_flowerView.tintColor = [self tintColor];
|
||||
newTransform = CGAffineTransformMakeScale(1.1*_flowerScaleFactor, 1.1*_flowerScaleFactor);
|
||||
newTransform = CGAffineTransformMakeScale(1.1 * _flowerScaleFactor, 1.1 * _flowerScaleFactor);
|
||||
oldCircleAlpha = 0;
|
||||
newCircleAlpha = 1;
|
||||
oldCircleTransform = CGAffineTransformMakeScale(0.2, 0.2);
|
||||
@@ -336,14 +336,14 @@ static UIBezierPath *ORKErrorBezierPath() {
|
||||
|
||||
CGFloat designWidth = ORKFlowerBezierPathSize.width + _ORKFlowerMargins.left + _ORKFlowerMargins.right;
|
||||
CGFloat scaleFactor = bounds.size.width / designWidth;
|
||||
CGAffineTransform tfm = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
|
||||
CGAffineTransform transform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
|
||||
|
||||
CGRect checkRect = CGRectApplyAffineTransform((CGRect){CGPointZero,ORKCheckBezierPathSize}, tfm);
|
||||
CGRect checkRect = CGRectApplyAffineTransform((CGRect){CGPointZero, ORKCheckBezierPathSize}, transform);
|
||||
[_checkView setBounds:checkRect];
|
||||
_checkView.layer.cornerRadius = checkRect.size.width/2;
|
||||
CGRect errorRect = CGRectApplyAffineTransform((CGRect){CGPointZero,ORKErrorBezierPathSize}, tfm);
|
||||
_checkView.layer.cornerRadius = checkRect.size.width / 2;
|
||||
CGRect errorRect = CGRectApplyAffineTransform((CGRect){CGPointZero, ORKErrorBezierPathSize}, transform);
|
||||
[_errorView setBounds:errorRect];
|
||||
_errorView.layer.cornerRadius = errorRect.size.width/2;
|
||||
_errorView.layer.cornerRadius = errorRect.size.width / 2;
|
||||
_errorView.center = _flowerView.center;
|
||||
_checkView.center = _flowerView.center;
|
||||
}
|
||||
|
||||
@@ -51,17 +51,17 @@
|
||||
|
||||
|
||||
@implementation ORKTappingContentView {
|
||||
|
||||
NSArray *_constraints;
|
||||
ORKScreenType _screenType;
|
||||
UIView *_buttonContainer;
|
||||
NSNumberFormatter *_formatter;
|
||||
NSLayoutConstraint *_topToProgressViewConstraint;
|
||||
NSLayoutConstraint *_topToCaptionLabelConstraint;
|
||||
NSLayoutConstraint *_captionLabelToTapCountLabelConstraint;
|
||||
NSLayoutConstraint *_tapButtonToBottomConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_screenType = ORKScreenTypeiPhone4;
|
||||
_tapCaptionLabel = [ORKSubheadlineLabel new];
|
||||
_tapCaptionLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_tapCaptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
@@ -97,7 +97,8 @@
|
||||
_tapCaptionLabel.text = ORKLocalizedString(@"TOTAL_TAPS_LABEL", nil);
|
||||
[self setTapCount:0];
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
|
||||
_tapCountLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
|
||||
|
||||
@@ -147,13 +148,12 @@
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
_screenType = ORKGetVerticalScreenTypeForWindow(newWindow);
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)updateLayoutMargins {
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets) { .left=margin*2, .right=margin*2 };
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin * 2, .right=margin * 2};
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
@@ -166,104 +166,96 @@
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([_constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:_constraints];
|
||||
_constraints = nil;
|
||||
}
|
||||
|
||||
ORKScreenType screenType = _screenType;
|
||||
const CGFloat HeaderBaselineToCaptionTop = ORKGetMetricForScreenType(ORKScreenMetricCaptionBaselineToTappingLabelTop, screenType);
|
||||
const CGFloat AssumedHeaderBaselineToStepViewTop = ORKGetMetricForScreenType(ORKScreenMetricLearnMoreBaselineToStepViewTop, screenType);
|
||||
|
||||
static const CGFloat CaptionBaselineToTapCountBaseline = 56;
|
||||
static const CGFloat TapButtonBottomToBottom = 36;
|
||||
|
||||
// On the iPhone, _progressView is positioned outside the bounds of this view, to be in-between the header and this view.
|
||||
// On the iPad, we want to stretch this out a bit so it feels less compressed.
|
||||
CGFloat progressViewOffset, topCaptionLabelOffset;
|
||||
if (screenType == ORKScreenTypeiPad) {
|
||||
progressViewOffset = 0;
|
||||
topCaptionLabelOffset = AssumedHeaderBaselineToStepViewTop;
|
||||
} else {
|
||||
progressViewOffset = (HeaderBaselineToCaptionTop/3) - AssumedHeaderBaselineToStepViewTop;
|
||||
topCaptionLabelOffset = HeaderBaselineToCaptionTop - AssumedHeaderBaselineToStepViewTop;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_buttonContainer, _tapCaptionLabel, _tapCountLabel, _progressView, _tapButton1, _tapButton2);
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1 constant:progressViewOffset]];
|
||||
_topToProgressViewConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topToProgressViewConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_tapCaptionLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1 constant:topCaptionLabelOffset]];
|
||||
_topToCaptionLabelConstraint = [NSLayoutConstraint constraintWithItem:_tapCaptionLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topToCaptionLabelConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_tapCountLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapCaptionLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
multiplier:1 constant:CaptionBaselineToTapCountBaseline]];
|
||||
_captionLabelToTapCountLabelConstraint = [NSLayoutConstraint constraintWithItem:_tapCountLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapCaptionLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_captionLabelToTapCountLabelConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_buttonContainer
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1 constant:TapButtonBottomToBottom]];
|
||||
_tapButtonToBottomConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_buttonContainer
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_tapButtonToBottomConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_tapCountLabel]-(>=10)-[_buttonContainer]"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
NSLayoutConstraint *wideProgress = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:2000];
|
||||
wideProgress.priority = UILayoutPriorityRequired-1;
|
||||
[constraints addObject:wideProgress];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
NSLayoutConstraint *progressViewWidthConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
progressViewWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[constraints addObject:progressViewWidthConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_tapCaptionLabel]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_tapCountLabel]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_tapButton1]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_tapButton2]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_tapButton1]-(>=24)-[_tapButton2(==_tapButton1)]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_tapButton1
|
||||
@@ -271,13 +263,42 @@
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapButton2
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1 constant:0]];
|
||||
|
||||
|
||||
_constraints = constraints;
|
||||
[self addConstraints:_constraints];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat HeaderBaselineToCaptionTop = ORKGetMetricForWindow(ORKScreenMetricCaptionBaselineToTappingLabelTop, window);
|
||||
const CGFloat AssumedHeaderBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin * 2, .right = margin * 2};
|
||||
|
||||
static const CGFloat CaptionBaselineToTapCountBaseline = 56;
|
||||
static const CGFloat TapButtonBottomToBottom = 36;
|
||||
|
||||
// On the iPhone, _progressView is positioned outside the bounds of this view, to be in-between the header and this view.
|
||||
// On the iPad, we want to stretch this out a bit so it feels less compressed.
|
||||
CGFloat topToProgressViewOffset = 0.0;
|
||||
CGFloat topToCaptionLabelOffset = 0.0;
|
||||
ORKScreenType screenType = ORKGetVerticalScreenTypeForWindow(window);
|
||||
if (screenType == ORKScreenTypeiPad) {
|
||||
topToProgressViewOffset = 0;
|
||||
topToCaptionLabelOffset = AssumedHeaderBaselineToStepViewTop;
|
||||
} else {
|
||||
topToProgressViewOffset = (HeaderBaselineToCaptionTop / 3) - AssumedHeaderBaselineToStepViewTop;
|
||||
topToCaptionLabelOffset = HeaderBaselineToCaptionTop - AssumedHeaderBaselineToStepViewTop;
|
||||
}
|
||||
|
||||
_topToProgressViewConstraint.constant = topToProgressViewOffset;
|
||||
_topToCaptionLabelConstraint.constant = topToCaptionLabelOffset;
|
||||
_captionLabelToTapCountLabelConstraint.constant = CaptionBaselineToTapCountBaseline;
|
||||
_tapButtonToBottomConstraint.constant = TapButtonBottomToBottom;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
NSTimeInterval const ORKTwoFingerTappingMinimumDuration = 5.0;
|
||||
|
||||
if ( self.stepDuration < ORKTwoFingerTappingMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKTwoFingerTappingMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKTwoFingerTappingMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
NSTimeInterval mediaTime = CACurrentMediaTime();
|
||||
NSTimeInterval mediaTime = touch.timestamp;
|
||||
|
||||
if (_tappingStart == 0) {
|
||||
_tappingStart = mediaTime;
|
||||
|
||||
@@ -90,29 +90,23 @@
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([self.constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
self.constraints = nil;
|
||||
}
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _imageView);
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_progressView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_progressView]-(>=10)-[_imageView]-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil views:views]];
|
||||
|
||||
self.constraints = constraintsArray;
|
||||
[self addConstraints:self.constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.constraints];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
}
|
||||
|
||||
if (self.stepDuration < ORKTimedWalkMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKTimedWalkMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKTimedWalkMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,9 +38,6 @@
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
|
||||
double const kDistanceInMetersTrackingThreshold = 100.0;
|
||||
|
||||
|
||||
@interface ORKTimedWalkStepViewController ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *samples;
|
||||
@@ -101,7 +98,7 @@ double const kDistanceInMetersTrackingThreshold = 100.0;
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKTimedWalkResult *timedWalkResult = [[ORKTimedWalkResult alloc] initWithIdentifier:(NSString *__nonnull)self.step.identifier];
|
||||
ORKTimedWalkResult *timedWalkResult = [[ORKTimedWalkResult alloc] initWithIdentifier:self.step.identifier];
|
||||
timedWalkResult.distanceInMeters = [self timedWalkStep].distanceInMeters;
|
||||
timedWalkResult.timeLimit = [self timedWalkStep].stepDuration;
|
||||
timedWalkResult.duration = self.trialDuration;
|
||||
|
||||
@@ -38,20 +38,19 @@
|
||||
@property (nonatomic, strong) ORKUnitLabel *captionLabel;
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
|
||||
- (void)setupConstraints;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKToneAudiometryContentView {
|
||||
ORKScreenType _screenType;
|
||||
NSLayoutConstraint *_topToProgressViewConstraint;
|
||||
NSLayoutConstraint *_topToCaptionLabelConstraint;
|
||||
NSLayoutConstraint *_tapButtonToBottomConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
_screenType = ORKGetVerticalScreenTypeForWindow(self.window);
|
||||
_captionLabel = [ORKUnitLabel new];
|
||||
_captionLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_captionLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
@@ -73,13 +72,18 @@
|
||||
|
||||
_captionLabel.text = nil;
|
||||
|
||||
[self setupConstraints];
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.progressView.progressTintColor = [self tintColor];
|
||||
@@ -101,86 +105,105 @@
|
||||
self.tapButton.enabled = NO;
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat HeaderBaselineToCaptionTop = ORKGetMetricForWindow(ORKScreenMetricCaptionBaselineToTappingLabelTop, window);
|
||||
const CGFloat AssumedHeaderBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
static const CGFloat TapButtonBottomToBottom = 36.0;
|
||||
|
||||
_topToProgressViewConstraint.constant = (HeaderBaselineToCaptionTop / 3) - AssumedHeaderBaselineToStepViewTop;
|
||||
_topToCaptionLabelConstraint.constant = HeaderBaselineToCaptionTop - AssumedHeaderBaselineToStepViewTop;
|
||||
_tapButtonToBottomConstraint.constant = TapButtonBottomToBottom;
|
||||
}
|
||||
|
||||
- (void)updateLayoutMargins {
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets) { .left=margin*2, .right=margin*2 };
|
||||
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)setupConstraints {
|
||||
ORKScreenType screenType = _screenType;
|
||||
const CGFloat HeaderBaselineToCaptionTop = ORKGetMetricForScreenType(ORKScreenMetricCaptionBaselineToTappingLabelTop, screenType);
|
||||
const CGFloat AssumedHeaderBaselineToStepViewTop = ORKGetMetricForScreenType(ORKScreenMetricLearnMoreBaselineToStepViewTop, screenType);
|
||||
|
||||
static const CGFloat TapButtonBottomToBottom = 36;
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _captionLabel, _tapButton);
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1 constant:(HeaderBaselineToCaptionTop/3) - AssumedHeaderBaselineToStepViewTop]];
|
||||
_topToProgressViewConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant will be set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topToProgressViewConstraint];
|
||||
|
||||
_topToCaptionLabelConstraint = [NSLayoutConstraint constraintWithItem:_captionLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant will be set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topToCaptionLabelConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_captionLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1 constant:(HeaderBaselineToCaptionTop - AssumedHeaderBaselineToStepViewTop)]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapButton
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1 constant:TapButtonBottomToBottom]];
|
||||
_tapButtonToBottomConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapButton
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant will be set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_tapButtonToBottomConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_captionLabel]-(>=10)-[_tapButton]"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
NSLayoutConstraint *wideProgress = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:2000];
|
||||
wideProgress.priority = UILayoutPriorityRequired-1;
|
||||
[constraints addObject:wideProgress];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
NSLayoutConstraint *progressWidthConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
progressWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[constraints addObject:progressWidthConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_captionLabel]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_tapButton
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1 constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[self addConstraints:constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
NSTimeInterval const ORKToneAudiometryTaskToneMinimumDuration = 5.0;
|
||||
|
||||
if (self.toneDuration < ORKToneAudiometryTaskToneMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"tone duration can not be shorter than %@ seconds.", @(ORKToneAudiometryTaskToneMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"tone duration cannot be shorter than %@ seconds.", @(ORKToneAudiometryTaskToneMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKToneAudiometryResult *toneResult = [[ORKToneAudiometryResult alloc] initWithIdentifier:(NSString *__nonnull)self.step.identifier];
|
||||
ORKToneAudiometryResult *toneResult = [[ORKToneAudiometryResult alloc] initWithIdentifier:self.step.identifier];
|
||||
toneResult.startDate = sResult.startDate;
|
||||
toneResult.endDate = now;
|
||||
toneResult.samples = [self.samples copy];
|
||||
@@ -180,8 +180,7 @@
|
||||
self.currentTestIndex ++;
|
||||
if (self.currentTestIndex == (self.testingFrequencies.count * 2)) {
|
||||
[self finish];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
[self startCurrentTest];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +113,10 @@
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
@@ -203,11 +203,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKTouchRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKTouchRecorderConfiguration
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKTowerOfHanoiStep : ORKActiveStep
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
#import "ORKTowerOfHanoiStepViewController.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
static const NSUInteger kMaximumNumberOfDisks = 8;
|
||||
|
||||
static const NSUInteger MaximumNumberOfDisks = 8;
|
||||
|
||||
@implementation ORKTowerOfHanoiStep
|
||||
|
||||
@@ -95,8 +96,8 @@ static const NSUInteger kMaximumNumberOfDisks = 8;
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
if (self.numberOfDisks > kMaximumNumberOfDisks) {
|
||||
ORK_Log_Oops(@"Having a large number of disks provides a poor user experience, consider reducing the number below %@.", @(kMaximumNumberOfDisks));
|
||||
if (self.numberOfDisks > MaximumNumberOfDisks) {
|
||||
ORK_Log_Warning(@"Having a large number of disks provides a poor user experience, consider reducing the number below %@.", @(MaximumNumberOfDisks));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
#import "ORKTowerOfHanoiStep.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
static const NSUInteger kNumberOfTowers = 3;
|
||||
|
||||
static const NSUInteger NumberOfTowers = 3;
|
||||
|
||||
@interface ORKTowerOfHanoiViewController () <ORKTowerOfHanoiTowerViewDataSource, ORKTowerOfHanoiTowerViewDelegate>
|
||||
|
||||
@@ -46,10 +47,11 @@ static const NSUInteger kNumberOfTowers = 3;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKTowerOfHanoiViewController {
|
||||
ORKActiveStepCustomView *_towerOfHanoiCustomView;
|
||||
NSNumber *_selectedIndex;
|
||||
NSArray *_currentConstraints;
|
||||
NSArray *_variableConstraints;
|
||||
NSMutableArray *_towers;
|
||||
NSArray *_towerViews;
|
||||
NSTimer *_timer;
|
||||
@@ -62,12 +64,12 @@ static const NSUInteger kNumberOfTowers = 3;
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
_towerOfHanoiCustomView = [ORKActiveStepCustomView new];
|
||||
[_towerOfHanoiCustomView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
_towerOfHanoiCustomView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.activeStepView.activeCustomView = _towerOfHanoiCustomView;
|
||||
self.activeStepView.minimumStepHeaderHeight = ORKGetMetricForWindow(ORKScreenMetricMinimumStepHeaderHeightForTowerOfHanoiPuzzle, self.view.window);
|
||||
|
||||
[self setupTowers];
|
||||
[self setupTowerViews];
|
||||
[self setUpTowers];
|
||||
[self setUpTowerViews];
|
||||
[self reloadData];
|
||||
NSString *title = ORKLocalizedString(@"TOWER_OF_HANOI_TASK_ACTIVE_STEP_INTRO_TEXT",nil);
|
||||
NSString *text = ORKLocalizedString(@"TOWER_OF_HANOI_TASK_INTRO_TEXT",nil);
|
||||
@@ -83,14 +85,19 @@ static const NSUInteger kNumberOfTowers = 3;
|
||||
|
||||
- (void)updateViewConstraints {
|
||||
[super updateViewConstraints];
|
||||
if (_currentConstraints) {
|
||||
[NSLayoutConstraint deactivateConstraints:_currentConstraints];
|
||||
_currentConstraints = nil;
|
||||
|
||||
[NSLayoutConstraint deactivateConstraints:_variableConstraints];
|
||||
_variableConstraints = nil;
|
||||
|
||||
if (!_variableConstraints) {
|
||||
_variableConstraints = [NSMutableArray new];
|
||||
}
|
||||
BOOL needCompactLayout = self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
|
||||
self.traitCollection.verticalSizeClass != UIUserInterfaceSizeClassCompact;
|
||||
_currentConstraints = needCompactLayout ? [self compactConstraints] : [self regularConstraints];
|
||||
[NSLayoutConstraint activateConstraints:_currentConstraints];
|
||||
|
||||
BOOL needCompactLayout =
|
||||
(self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) &&
|
||||
(self.traitCollection.verticalSizeClass != UIUserInterfaceSizeClassCompact);
|
||||
_variableConstraints = needCompactLayout ? [self compactConstraints] : [self regularConstraints];
|
||||
[NSLayoutConstraint activateConstraints:_variableConstraints];
|
||||
}
|
||||
|
||||
#pragma mark - ORKStepViewController
|
||||
@@ -195,7 +202,7 @@ static const NSUInteger kNumberOfTowers = 3;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupTowers {
|
||||
- (void)setUpTowers {
|
||||
NSMutableArray *diskStack = [NSMutableArray array];
|
||||
for (NSInteger disk = [self numberOfDisks] ; disk > 0 ; disk--) {
|
||||
[diskStack addObject: @(disk)];
|
||||
@@ -203,15 +210,15 @@ static const NSUInteger kNumberOfTowers = 3;
|
||||
_towers = [@[[[ORKTowerOfHanoiTower alloc] initWithDisks:diskStack], [ORKTowerOfHanoiTower emptyTower], [ORKTowerOfHanoiTower emptyTower]] mutableCopy];
|
||||
}
|
||||
|
||||
- (void)setupTowerViews {
|
||||
- (void)setUpTowerViews {
|
||||
NSMutableArray *towerViews = [NSMutableArray array];
|
||||
for (NSInteger index = 0 ; index < 3 ; index++) {
|
||||
ORKTowerOfHanoiTowerView *towerView = [[ORKTowerOfHanoiTowerView alloc] initWithFrame:CGRectZero maximumNumberOfDisks:[self numberOfDisks]];
|
||||
towerView.delegate = self;
|
||||
towerView.dataSource = self;
|
||||
towerView.targeted = (index == kNumberOfTowers - 1);
|
||||
towerView.targeted = (index == NumberOfTowers - 1);
|
||||
[towerViews addObject:towerView];
|
||||
[towerView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
towerView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[_towerOfHanoiCustomView addSubview:towerView];
|
||||
}
|
||||
_towerViews = towerViews;
|
||||
@@ -222,8 +229,7 @@ static const NSUInteger kNumberOfTowers = 3;
|
||||
ORKTowerOfHanoiTower *recipientTower = _towers[recipientTowerIndex];
|
||||
if ([recipientTower recieveDiskFrom:donorTower]) {
|
||||
[self makeMoveFromTowerAtIndex:donorTowerIndex toTowerAtIndex:recipientTowerIndex];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
NSNumber *donorSize = [self towerOfHanoiView:_towerViews[donorTowerIndex] diskAtIndex:0];
|
||||
NSNumber *recipientSize = [self towerOfHanoiView:_towerViews[recipientTowerIndex] diskAtIndex:0];
|
||||
|
||||
@@ -265,13 +271,41 @@ static const NSUInteger kNumberOfTowers = 3;
|
||||
NSDictionary *views = @{ @"A" : _towerViews[0], @"B" : _towerViews[1], @"C" : _towerViews[2]};
|
||||
NSMutableArray *newConstraints = [NSMutableArray new];
|
||||
|
||||
[newConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:|-[A]-[B]-[C]-|"] options:0 metrics:nil views:views]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[0] attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:_towerViews[1] attribute:NSLayoutAttributeHeight multiplier:1 constant:0]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[2] attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:_towerViews[1] attribute:NSLayoutAttributeHeight multiplier:1 constant:0]];
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:|-[A]-[B]-[C]-|"]
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[0]
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerViews[1]
|
||||
attribute:NSLayoutAttributeHeight
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[2]
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerViews[1]
|
||||
attribute:NSLayoutAttributeHeight
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
for (int index = 0 ; index < _towerViews.count ; index++) {
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[index] attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:compactWidth]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[index] attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_towerOfHanoiCustomView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[index]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:compactWidth]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[index]
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerOfHanoiCustomView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
}
|
||||
|
||||
return newConstraints;
|
||||
@@ -281,13 +315,41 @@ static const NSUInteger kNumberOfTowers = 3;
|
||||
NSDictionary *views = @{ @"A" : _towerViews[0], @"B" : _towerViews[1], @"C" : _towerViews[2]};
|
||||
NSMutableArray *newConstraints = [NSMutableArray new];
|
||||
|
||||
[newConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[A]-|" options:0 metrics:nil views:views]];
|
||||
[newConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[B]-|" options:0 metrics:nil views:views]];
|
||||
[newConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[C]-|" options:0 metrics:nil views:views]];
|
||||
[newConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[A]-[B]-[C]-|" options:0 metrics:nil views:views]];
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[A]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[B]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[C]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[A]-[B]-[C]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[0] attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:_towerViews[1] attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[2] attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:_towerViews[1] attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[0]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerViews[1]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[2]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerViews[1]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
return newConstraints;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
}
|
||||
|
||||
- (BOOL)canRecieveDisk:(NSNumber *)disk {
|
||||
return _disks.count == 0 || [_disks.lastObject integerValue] > disk.integerValue;
|
||||
return _disks.count == 0 || ((NSNumber *)_disks.lastObject).integerValue > disk.integerValue;
|
||||
}
|
||||
|
||||
- (BOOL)recieveDiskFrom:(ORKTowerOfHanoiTower*)donorTower {
|
||||
|
||||
@@ -56,7 +56,7 @@ static const CGFloat BaseSpacing = 10;
|
||||
_maximumNumberOfDisks = maximumNumberOfDisks;
|
||||
_base = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
_base.backgroundColor = [UIColor ork_midGrayTintColor];
|
||||
[_base setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
_base.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_base.layer.cornerRadius = 2.5;
|
||||
_base.layer.masksToBounds = YES;
|
||||
_diskViews = [NSMutableArray new];
|
||||
@@ -78,7 +78,6 @@ static const CGFloat BaseSpacing = 10;
|
||||
}
|
||||
|
||||
CGFloat height = (DiskHeight * _maximumNumberOfDisks) + (DiskSpacing * _maximumNumberOfDisks);
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
@@ -101,7 +100,7 @@ static const CGFloat BaseSpacing = 10;
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:2]];
|
||||
constant:2.0]];
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_base
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
@@ -123,7 +122,7 @@ static const CGFloat BaseSpacing = 10;
|
||||
for (NSInteger index = 0 ; index < _diskSizes.count ; index++) {
|
||||
UIView *disk = _diskViews[index];
|
||||
CGFloat divide = 1.0 / _maximumNumberOfDisks;
|
||||
CGFloat multiply = [(NSNumber *)_diskSizes[index] floatValue] * divide;
|
||||
CGFloat multiply = ((NSNumber *)_diskSizes[index]).floatValue * divide;
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:disk
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
@@ -133,8 +132,6 @@ static const CGFloat BaseSpacing = 10;
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:disk
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
@@ -261,7 +258,7 @@ static const CGFloat BaseSpacing = 10;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityValue {
|
||||
|
||||
|
||||
NSString *disksString = @"";
|
||||
|
||||
for (NSNumber *diskSize in _diskSizes) {
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:text];
|
||||
float speechRate = AVSpeechUtteranceDefaultSpeechRate;
|
||||
if (! [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 9, .minorVersion = 0, .patchVersion = 0}]) {
|
||||
if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 9, .minorVersion = 0, .patchVersion = 0}]) {
|
||||
speechRate = AVSpeechUtteranceDefaultSpeechRate / 2.5;
|
||||
}
|
||||
utterance.rate = speechRate;
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
if ( self.numberOfStepsPerLeg < ORKShortWalkTaskMinimumNumberOfStepsPerLeg) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"numberOfStepsPerLeg can not be less than %@.", @(ORKShortWalkTaskMinimumNumberOfStepsPerLeg)]
|
||||
reason:[NSString stringWithFormat:@"numberOfStepsPerLeg cannot be less than %@.", @(ORKShortWalkTaskMinimumNumberOfStepsPerLeg)]
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
|
||||
|
||||
@interface ORKWalkingContentView : ORKActiveStepCustomView {
|
||||
ORKScreenType _screenType;
|
||||
NSLayoutConstraint *_topConstraint;
|
||||
}
|
||||
|
||||
@@ -56,18 +55,11 @@
|
||||
|
||||
@implementation ORKWalkingContentView
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
_screenType = ORKGetVerticalScreenTypeForWindow(newWindow);
|
||||
[self updateConstraintConstants];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_progressView = [ORKProgressView new];
|
||||
_progressView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_screenType = ORKScreenTypeiPhone4;
|
||||
|
||||
#if LAYOUT_DEBUG
|
||||
self.backgroundColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
|
||||
@@ -75,29 +67,53 @@
|
||||
#endif
|
||||
|
||||
[self addSubview:_progressView];
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
[self setUpConstraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstants {
|
||||
|
||||
ORKScreenType screenType = _screenType;
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat CaptionBaselineToProgressTop = 100;
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForScreenType(ORKScreenMetricLearnMoreBaselineToStepViewTop, screenType);
|
||||
[_topConstraint setConstant:(CaptionBaselineToProgressTop - CaptionBaselineToStepViewTop)];
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
_topConstraint.constant = CaptionBaselineToProgressTop - CaptionBaselineToStepViewTop;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView);
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_progressView]-(>=0)-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
_topConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant will be set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[self removeConstraints:[self constraints]];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView);
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_progressView]-(>=0)-|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
|
||||
_topConstraint = [NSLayoutConstraint constraintWithItem:_progressView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:0];
|
||||
[self updateConstraintConstants];
|
||||
[self addConstraint:_topConstraint];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_progressView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
|
||||
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest1@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "retina4",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"filename" : "holepegtest1@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest1@2x~ipad.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest2@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "retina4",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"filename" : "holepegtest2@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest2@2x~ipad.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest3@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "retina4",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"filename" : "holepegtest3@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest3@2x~ipad.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 39 KiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest4@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "retina4",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"filename" : "holepegtest4@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest4@2x~ipad.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest5@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "retina4",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"filename" : "holepegtest5@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest5@2x~ipad.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest6@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "retina4",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"filename" : "holepegtest6@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"filename" : "holepegtest6@2x~ipad.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.5 KiB |