{"id":471912,"date":"2025-08-23T21:00:29","date_gmt":"2025-08-23T21:00:29","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=471912"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=471912","title":{"rendered":"<span>\u0422\u0440\u0435\u043d\u0430\u0436\u0435\u0440 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0445 \u0447\u0438\u0441\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043d\u0430 Jetpack Compose<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0422\u0435, \u043a\u0442\u043e \u0443\u0447\u0438\u043b \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0439, \u0437\u043d\u0430\u044e\u0442, \u043a\u0430\u043a \u0441\u043b\u043e\u0436\u043d\u043e \u043e\u0441\u0432\u043e\u0438\u0442\u044c\u0441\u044f \u0432 \u043a\u0440\u0443\u0433\u0443 \u0438\u0445 \u0447\u0438\u0441\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445. \u0412\u043e \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435 <a href=\"https:\/\/5respublika.com\/kultura\/les-chiffres-etranges.html\" rel=\"noopener noreferrer nofollow\">\u0443\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f<\/a> \u0441\u0440\u0430\u0437\u0443 \u0434\u0432\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0441\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f &#8212; \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u0430\u044f \u043d\u0430\u043c \u0434\u0435\u0441\u044f\u0442\u0435\u0440\u0438\u0447\u043d\u0430\u044f \u0438 \u043a\u0435\u043b\u044c\u0442\u0441\u043a\u043e-\u043d\u043e\u0440\u043c\u0430\u043d\u043d\u0441\u043a\u0430\u044f \u0434\u0432\u0430\u0434\u0446\u0430\u0442\u0435\u0440\u0438\u0447\u043d\u0430\u044f, \u043e\u043d\u0430 \u0436\u0435 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%94%D0%B2%D0%B0%D0%B4%D1%86%D0%B0%D1%82%D0%B5%D1%80%D0%B8%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F\" rel=\"noopener noreferrer nofollow\">\u0432\u0438\u0433\u0435\u0437\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f<\/a>.<\/p>\n<blockquote>\n<p>Mille quatre cent quatre-vingt-deux<\/p>\n<\/blockquote>\n<p>&#8212; \u043f\u043e\u0435\u0442 \u0413\u0440\u0438\u043d\u0433\u0443\u0430\u0440 \u043f\u0440\u043e 1482 \u0433\u043e\u0434. \u0417\u0434\u0435\u0441\u044c 400 &#8212; \u0432 \u0434\u0435\u0441\u044f\u0442\u0435\u0440\u0438\u0447\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 (quatre cent), \u0430 80 &#8212; \u0443\u0436\u0435 \u0432 20-\u0440\u0438\u0447\u043d\u043e\u0439 (quatre-vingt).<\/p>\n<p>\u0418 \u0445\u043e\u0442\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0441\u0442\u0430 \u043d\u0435 \u0437\u0430\u043f\u0443\u0433\u0430\u0435\u0448\u044c \u0434\u0430\u0436\u0435 16-\u0440\u0438\u0447\u043d\u043e\u0439, \u0432\u0441\u0435-\u0442\u0430\u043a\u0438 \u043c\u0435\u0442\u0430\u0442\u044c\u0441\u044f \u043c\u0435\u0436\u0434\u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u043c\u0438 \u0441\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u0432 \u0443\u043c\u0435, \u043a\u043e\u0433\u0434\u0430 \u0442\u044b \u0443\u0436\u0435 \u0441\u0442\u043e\u0438\u0448\u044c \u0443 \u043a\u0430\u0441\u0441\u044b \u0438 \u0434\u043e\u043b\u0436\u0435\u043d \u0440\u0430\u0441\u043f\u043b\u0430\u0442\u0438\u0442\u044c\u0441\u044f, \u043f\u0440\u0438\u0447\u0435\u043c, \u043f\u043e \u043d\u044b\u043d\u0435\u0448\u043d\u0438\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0430\u043c, \u043d\u0430\u043b\u0438\u0447\u043d\u044b\u043c\u0438, &#8212; \u0442\u043e\u0442 \u0435\u0449\u0435 \u043a\u0432\u0435\u0441\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0432\u0435\u0434\u0435\u043c \u0437\u043d\u0430\u043d\u0438\u0435 \u0447\u0438\u0441\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0434\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u043c\u0430.<\/p>\n<h2>\u0413\u0440\u0430\u043c\u043c\u0430\u0442\u0438\u043a\u0430<\/h2>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043c\u0438\u043d\u0438\u0430\u0442\u044e\u0440\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0441 \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0433\u043e \u043d\u0430 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0439. \u0422\u043e\u0447\u043d\u0435\u0435, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0435\u0440 \u0438\u0437 \u0446\u0435\u043b\u043e\u0433\u043e \u0447\u0438\u0441\u043b\u0430 \u043e\u0442 0 \u0434\u043e 100 \u0432 \u0441\u0442\u0440\u043e\u043a\u0443.<\/p>\n<p>\u0412\u043e\u0442 \u043d\u0430\u0448\u0438 \u043a\u0438\u0440\u043f\u0438\u0447\u0438\u043a\u0438, \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u044d\u0442\u043e\u0433\u043e \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430:<\/p>\n<pre><code class=\"kotlin\">    private val units = arrayOf(         \"z\u00e9ro\", \"un\", \"deux\", \"trois\", \"quatre\", \"cinq\", \"six\", \"sept\", \"huit\", \"neuf\"     )      private val tenToSixteen = arrayOf(         \"dix\", \"onze\", \"douze\", \"treize\", \"quatorze\", \"quinze\", \"seize\"     )      private val tens = arrayOf(         \"\", \"dix\", \"vingt\", \"trente\", \"quarante\", \"cinquante\", \"soixante\"     )<\/code><\/pre>\n<p>\u0418\u0445 \u0432\u0441\u0435\u0433\u043e \u0442\u0440\u0438 \u0432\u0438\u0434\u0430 &#8212; \u043e\u0442 0 \u0434\u043e 9, \u043e\u0442 10 \u0434\u043e 16, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043e\u0442 10 \u0434\u043e 60.<\/p>\n<p>\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0447\u0438\u0441\u0435\u043b \u043e\u0442 0 \u0434\u043e 16 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0438\u0437 \u044d\u0442\u0438\u0445 \u043c\u0430\u0441\u0441\u0438\u0432\u043e\u0432, \u0430 \u043e\u0442 17 \u0434\u043e 19 &#8212; \u043f\u0440\u0438\u0431\u0430\u0432\u043a\u043e\u0439 &#171;dix-&#187; \u043a \u0447\u0438\u0441\u043b\u0443 \u0435\u0434\u0438\u043d\u0438\u0446: dix-sept, dix-huit, dix-neuf:<\/p>\n<pre><code class=\"kotlin\">private fun from0to19(n: Int): String = when (n) {     in 0..9   -&gt; units[n]     in 10..16 -&gt; tenToSixteen[n - 10]     in 17..19 -&gt; \"dix-\" + units[n - 10]     else      -&gt; error(\"Bad 1..19 input: $n\") }<\/code><\/pre>\n<p>\u041e\u0442 20 \u0434\u043e 69 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0434\u0435\u0441\u044f\u0442\u0435\u0440\u0438\u0447\u043d\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0441\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f. \u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u0447\u0438\u0441\u043b\u0430 \u0432 \u043d\u0435\u0435 \u0432\u0441\u0435 \u043c\u044b, \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435, \u043f\u0438\u0441\u0430\u043b\u0438, \u043a\u043e\u0433\u0434\u0430 \u0443\u0447\u0438\u043b\u0438\u0441\u044c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c: \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0447\u0430\u0441\u0442\u043d\u043e\u0435 \u0438 \u043e\u0441\u0442\u0430\u0442\u043e\u043a \u043e\u0442 \u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043d\u0430 10, \u0442\u043e \u0435\u0441\u0442\u044c, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u0438 \u0435\u0434\u0438\u043d\u0438\u0446\u044b. \u0411\u0435\u0440\u0435\u043c \u0438\u0445 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0438\u0437 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043c\u0430\u0441\u0441\u0438\u0432\u043e\u0432. \u0423\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c, \u0447\u0442\u043e \u0434\u043b\u044f \u0447\u0438\u0441\u0435\u043b, \u043e\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u044e\u0449\u0438\u0445\u0441\u044f \u0435\u0434\u0438\u043d\u0438\u0446\u0435\u0439 (21\/31\/41\/51\/61), \u043d\u0430\u0434\u043e \u043f\u0440\u0438\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043b\u043e\u0432\u043e &#171;et&#187;:<\/p>\n<pre><code class=\"kotlin\">private fun from20to69(n: Int): String {     val d = n \/ 10       \/\/ 2..6     val r = n % 10       \/\/ 0..9     return when {         r == 0 -&gt; tens[d]         r == 1 -&gt; \"${tens[d]} et un\"         else   -&gt; \"${tens[d]}-${units[r]}\"     } }<\/code><\/pre>\n<p>\u0427\u0438\u0441\u043b\u0430 \u043e\u0442 70 \u0434\u043e 79 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f &#171;60 + x&#187;. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u044b\u0447\u0442\u0435\u043c \u0438\u0437 \u0442\u0430\u043a\u043e\u0433\u043e \u0447\u0438\u0441\u043b\u0430 60, \u0430 \u0434\u043b\u044f \u043e\u0441\u0442\u0430\u0442\u043a\u0430 \u0432\u044b\u0437\u043e\u0432\u0435\u043c \u0443\u0436\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0443\u044e \u043d\u0430\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>from0to19()<\/code>:<\/p>\n<pre><code class=\"kotlin\">private fun from70to79(n: Int): String {     val r = n - 60     return when (r) {         11        -&gt; \"soixante et onze\"         in 10..19 -&gt; \"soixante-\" + from0to19(r)         else      -&gt; error(\"Unexpected 70s remainder $r\")     } }<\/code><\/pre>\n<p>\u041e\u0442 81 \u0434\u043e 99 \u0447\u0438\u0441\u043b\u0430 \u0438\u043c\u0435\u043d\u0443\u044e\u0442\u0441\u044f \u043f\u043e \u0441\u0445\u0435\u043c\u0435 &#171;4-20-x&#187;, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u0432\u044b\u0447\u0438\u0442\u0430\u0435\u043c \u0438\u0437 \u0447\u0438\u0441\u043b\u0430 80 \u0438 \u0441\u043d\u043e\u0432\u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>from0to19()<\/code>:<\/p>\n<pre><code class=\"kotlin\">private fun from80to99(n: Int): String {     return if (n == 80) {         \"quatre-vingts\"     } else {         \"quatre-vingt-\" + from0to19(n - 80)     } }<\/code><\/pre>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u0432 \u043e\u0441\u043e\u0431\u044b\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u0434\u043b\u044f \u0447\u0438\u0441\u043b\u0430 100 (&#171;cent&#187;), \u0441\u0432\u0435\u0434\u0435\u043c \u0432\u0441\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044e:<\/p>\n<pre><code class=\"kotlin\">fun toFrench(n: Int): String {     require(n in 0..100) { \"Only 0..100 supported in this demo\" }     return when {         n &lt;= 19      -&gt; from0to19(n)         n in 20..69  -&gt; from20to69(n)         n in 70..79  -&gt; from70to79(n)         n in 80..99  -&gt; from80to99(n)         n == 100     -&gt; \"cent\"         else         -&gt; error(\"Unhandled number $n\")     } }<\/code><\/pre>\n<p>\u0412 Java \u043c\u044b \u0431\u044b \u0441\u0432\u0435\u043b\u0438 \u044d\u0442\u0443 \u0433\u0440\u0443\u043f\u043f\u0443 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0432 \u043e\u0434\u0438\u043d \u043a\u043b\u0430\u0441\u0441 \u0441 \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c <code>public static<\/code>, \u043d\u043e \u0432 Kotlin \u0432 \u0442\u0430\u043a\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u043f\u0440\u0438\u043d\u044f\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0438\u043d\u0433\u043b\u0442\u043e\u043d:<\/p>\n<pre><code class=\"kotlin\">object FrenchNumbers {      private val units = arrayOf(         \"z\u00e9ro\", \"un\", \"deux\", \"trois\", \"quatre\", \"cinq\", \"six\", \"sept\", \"huit\", \"neuf\"     )      private val tenToSixteen = arrayOf(         \"dix\", \"onze\", \"douze\", \"treize\", \"quatorze\", \"quinze\", \"seize\"     )      private val tens = arrayOf(         \"\", \"dix\", \"vingt\", \"trente\", \"quarante\", \"cinquante\", \"soixante\"     )      fun toFrench(n: Int): String { ... }      private fun from80to99(n: Int): String { ... }      private fun from20to69(n: Int): String { ... }      private fun from70to79(n: Int): String { ... }      private fun from0to19(n: Int): String = ... }<\/code><\/pre>\n<h2>SRS-\u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c<\/h2>\n<p>\u0418\u043c\u0445\u043e, \u0441\u043c\u0430\u0440\u0442\u0444\u043e\u043d \u0438 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%B5%D0%BD%D0%B8%D1%8F\" rel=\"noopener noreferrer nofollow\">SRS-\u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/a> &#8212; \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u0430\u0440\u0430. \u0427\u0435\u043c\u0443, \u043a\u0430\u043a \u043d\u0435 \u0441\u043c\u0430\u0440\u0442\u0444\u043e\u043d\u0443, \u0443\u0434\u043e\u0431\u043d\u043e \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0437\u0430 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u043e\u043c \u0441\u0432\u043e\u0435\u0433\u043e \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0430 \u0438 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0442\u044c \u043d\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u044f, \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u0440\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e?<\/p>\n<p>\u0412\u043e\u0437\u044c\u043c\u0435\u043c \u043e\u0431\u0449\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c <a href=\"https:\/\/en.wikipedia.org\/wiki\/SuperMemo#Description_of_SM-2_algorithm\" rel=\"noopener noreferrer nofollow\">SuperMemo<\/a> \u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043c \u043d\u0430 \u0435\u0433\u043e \u043e\u0441\u043d\u043e\u0432\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0439. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0447\u0438\u0441\u043b\u0430, \u0433\u0434\u0435 \u0431\u0443\u0434\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"kotlin\">data class CardSrs(     var ef: Double = 2.5,     var interval: Int = 0,     var reps: Int = 0,     var lapses: Int = 0,     var due: Long = todayEpoch() )<\/code><\/pre>\n<p>\u0433\u0434\u0435<\/p>\n<ul>\n<li>\n<p><code>ef<\/code> &#8212; \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u043b\u0435\u0433\u043a\u043e\u0441\u0442\u0438 (easy factor), \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043e\u0431\u044b\u0447\u043d\u043e \u0431\u0435\u0440\u0443\u0442 2.5;<\/p>\n<\/li>\n<li>\n<p><code>interval<\/code> &#8212; \u0447\u0438\u0441\u043b\u043e \u0434\u043d\u0435\u0439 \u0434\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u043f\u043e\u043a\u0430\u0437\u0430;<\/p>\n<\/li>\n<li>\n<p><code>reps<\/code> &#8212; \u0447\u0438\u0441\u043b\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 \u043f\u043e\u0434\u0440\u044f\u0434;<\/p>\n<\/li>\n<li>\n<p><code>lapses<\/code> &#8212; \u0447\u0438\u0441\u043b\u043e \u043f\u0440\u043e\u0432\u0430\u043b\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p><code>due<\/code> &#8212; \u0434\u0430\u0442\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u043f\u043e\u043a\u0430\u0437\u0430 (Epoch Day &#8212; \u0447\u0438\u0441\u043b\u043e \u0441\u0443\u0442\u043e\u043a \u0441 1.01.1970), \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0430\u043a:<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"kotlin\">fun todayEpoch(): Long = LocalDate.now().toEpochDay()<\/code><\/pre>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a \u0443 \u043d\u0430\u0441 \u0432\u0441\u0435\u0433\u043e \u043f\u043e 5 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f 100 \u0447\u0438\u0441\u0435\u043b, \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u0440\u044f\u043c\u043e \u0432 Preferences <a href=\"https:\/\/habr.com\/ru\/companies\/tbank\/articles\/525010\/\" rel=\"noopener noreferrer nofollow\">DataStore<\/a>, \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<pre><code class=\"kotlin\">private val Context.srsDataStore by preferencesDataStore(\"french_srs_numbers\")<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c <code>preferencesDataStore()<\/code> &#8212; \u0434\u0435\u043b\u0435\u0433\u0430\u0442 \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Jetpack DataStore, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u0435 \u043b\u0435\u043d\u0438\u0432\u043e \u0438 \u043f\u043e\u0442\u043e\u043a\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0438 \u043a\u044d\u0448\u0438\u0440\u0443\u0435\u0442 \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <code>DataStore&lt;Preferences&gt;<\/code> \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0438\u043c\u0435\u043d\u0438 \u0444\u0430\u0439\u043b\u0430 (<code>\"french_srs_numbers\"<\/code>). \u041f\u0440\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0434\u043e\u0441\u0442\u0443\u043f\u0430\u0445 \u044d\u0442\u043e\u0442 \u0434\u0435\u043b\u0435\u0433\u0430\u0442 \u0432\u0435\u0440\u043d\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440.<\/p>\n<p><code>srsDataStore <\/code>\u043c\u044b \u043e\u0431\u044a\u044f\u0432\u0438\u043b\u0438 \u043a\u0430\u043a \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e-\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u0430 <code>Context<\/code>, \u0447\u0442\u043e\u0431\u044b \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c <code>appContext.srsDataStore<\/code>.<\/p>\n<p>\u0421\u0430\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0431\u0443\u0434\u0443\u0449\u0438\u043c\u0438 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c\u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 \u0441\u043b\u043e\u0436\u0438\u043c \u0432 \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u0441\u0438\u043d\u0433\u043b\u0442\u043e\u043d:<\/p>\n<pre><code class=\"kotlin\">object Srs {     private lateinit var appCtx: Context        private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)          private val numMap = mutableMapOf&lt;Int, CardSrs&gt;().apply {       for (n in 0..100) put(n, CardSrs()) }<\/code><\/pre>\n<p>\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0438 \u0441\u043a\u043e\u0443\u043f \u043b\u0443\u0447\u0448\u0435 \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0438\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043e \u0432\u0441\u0435\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u0445.<\/p>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0441 \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c <code>lateinit<\/code>: \u044d\u0442\u043e <a href=\"https:\/\/habr.com\/ru\/companies\/maxilect\/articles\/590061\/\" rel=\"noopener noreferrer nofollow\">\u0438\u0434\u0438\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431<\/a> \u043e\u0431\u044a\u044f\u0432\u0438\u0442\u044c non-null \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043b\u0435\u0433\u0430\u043b\u044c\u043d\u043e \u043e\u0442\u043b\u043e\u0436\u0438\u0442\u044c \u0435\u0433\u043e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0434\u043e \u0442\u0435\u0445 \u043f\u043e\u0440, \u043f\u043e\u043a\u0430 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f.<\/p>\n<p>\u0423\u0434\u043e\u0431\u043d\u043e \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0435\u0434\u0438\u043d\u044b\u0439 \u0441\u043a\u043e\u0443\u043f <code>scope<\/code> \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439. \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0435\u0433\u043e \u0441\u0442\u0440\u043e\u0438\u043c \u043d\u0430 \u0431\u0430\u0437\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430, \u0441\u043e\u0441\u0442\u043e\u044f\u0449\u0435\u0433\u043e \u0438\u0437 <code>SupervisorJob()<\/code> \u0438 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430 IO. <code>SupervisorJob (Job<\/code>-\u043d\u0430\u0434\u0437\u0438\u0440\u0430\u0442\u0435\u043b\u044c) \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439. \u041a\u0430\u0436\u0434\u0430\u044f \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430, \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u0430\u044f \u043f\u043e\u0434 \u043d\u0430\u0434\u0437\u043e\u0440\u043e\u043c\u00a0<code>SupervisorJob<\/code>, \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u0441 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043e\u0448\u0438\u0431\u043e\u043a. \u0427\u0442\u043e \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430 IO, \u0442\u043e \u043e\u043d \u0443\u0434\u043e\u0431\u0435\u043d \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u0432 \u043c\u043e\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u043e\u043c <a href=\"https:\/\/stepik.org\/a\/205482\" rel=\"noopener noreferrer nofollow\">\u043a\u0443\u0440\u0441\u0435 \u043f\u043e \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430\u043c<\/a>.<\/p>\n<p>\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u043e <code>numMap<\/code> \u0445\u0440\u0430\u043d\u0438\u0442 \u0447\u0438\u0441\u043b\u0430 \u0438 \u0438\u0445 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0432 \u0432\u0438\u0434\u0435 MutableMap.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0438\u0437 DataStore, \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a \u043d\u0435\u043c\u0443 \u0447\u0435\u0440\u0435\u0437 \u043d\u0430\u0448\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 <code>srsDataStore<\/code>:<\/p>\n<pre><code class=\"kotlin\">    suspend fun init(context: Context) {         appCtx = context.applicationContext         val prefs = appCtx.srsDataStore.data.first()         for (n in 0..100) {             val p = \"n$n\"             val ef = prefs[doublePreferencesKey(\"$p.ef\")] ?: 2.5             val interval = prefs[intPreferencesKey(\"$p.interval\")] ?: 0             val reps = prefs[intPreferencesKey(\"$p.reps\")] ?: 0             val lapses = prefs[intPreferencesKey(\"$p.lapses\")] ?: 0             val due = prefs[longPreferencesKey(\"$p.due\")] ?: todayEpoch()             numMap[n] = CardSrs(ef, interval, reps, lapses, due)         }     }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u043c\u044b \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0435\u0435 \u043f\u043e\u043a\u0430\u0437\u0430. \u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0435\u0435 \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043b, \u0442\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u0432 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 &#8212; \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u0434\u043e \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0445:<\/p>\n<pre><code class=\"kotlin\">    fun updateNumber(n: Int, ok: Boolean) {         val st = numMap.getValue(n)         if (!ok) {             st.reps = 0             st.interval = 1             st.due = todayEpoch() + 1             st.lapses += 1         } else {             if (st.reps == 0) st.interval = 1             else if (st.reps == 1) st.interval = 6             else st.interval = round(st.interval * st.ef).toInt()             st.ef = maxOf(1.3, st.ef + 0.1)             st.reps += 1             st.due = todayEpoch() + st.interval         }         saveNumberAsync(n, st)     }<\/code><\/pre>\n<p>\u041d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b I<sup>t+1<\/sup> \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0447\u0438\u0441\u043b\u0430 \u0438\u0434\u0443\u0449\u0438\u0445 \u043f\u043e\u0434\u0440\u044f\u0434 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 r<sub>t<\/sub>:<\/p>\n<p><img decoding=\"async\" class=\"formula\" source=\"I_{t+1} = \\begin{cases} 1, &amp; r_t = 0,\\\\[4pt] 6, &amp; r_t = 1,\\\\[4pt] \\left\\lfloor I_t \\cdot EF_t \\right\\rfloor, &amp; r_t \\ge 2~ \\end{cases}\" alt=\"I_{t+1} = \\begin{cases} 1, &amp; r_t = 0,\\\\[4pt] 6, &amp; r_t = 1,\\\\[4pt] \\left\\lfloor I_t \\cdot EF_t \\right\\rfloor, &amp; r_t \\ge 2~ \\end{cases}\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e84\/723\/a88\/e84723a88aa7194b384b843b39170d37.svg\" width=\"369\" height=\"126\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e84\/723\/a88\/e84723a88aa7194b384b843b39170d37.svg 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e84\/723\/a88\/e84723a88aa7194b384b843b39170d37.svg 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<p>EF (ease factor) \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0443\u0441\u043f\u0435\u0445\u0430 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 0.1. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0434\u043b\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f EF \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u043e\u0433, \u0440\u0430\u0432\u043d\u044b\u0439 1.3.<\/p>\n<p><img decoding=\"async\" class=\"formula\" source=\"EF_{t+1} = \\max\\!\\bigl(1.3,\\ EF_t + 0.1\\bigr)\" alt=\"EF_{t+1} = \\max\\!\\bigl(1.3,\\ EF_t + 0.1\\bigr)\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/formulas\/d\/df\/dfa\/dfa414bad0797f5e6506f96a47aa1239.svg\" width=\"224\" height=\"16\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/formulas\/d\/df\/dfa\/dfa414bad0797f5e6506f96a47aa1239.svg 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/formulas\/d\/df\/dfa\/dfa414bad0797f5e6506f96a47aa1239.svg 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c, \u043a\u0430\u043a \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442 suspend-\u043c\u0435\u0442\u043e\u0434 <code>DataStore.edit()<\/code>. \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0443 \u0438\u0437 \u0440\u0430\u043d\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043a\u043e\u0443\u043f\u0430 \u0438 \u0432 \u043d\u0435\u0439 \u0432\u044b\u0437\u043e\u0432\u0435\u043c <code>edit()<\/code>:<\/p>\n<pre><code class=\"kotlin\">    private fun saveNumberAsync(n: Int, st: CardSrs) {         if (!this::appCtx.isInitialized) return         val p = \"n$n\"         scope.launch {             appCtx.srsDataStore.edit { e -&gt;                 e[doublePreferencesKey(\"$p.ef\")] = st.ef                 e[intPreferencesKey(\"$p.interval\")] = st.interval                 e[intPreferencesKey(\"$p.reps\")] = st.reps                 e[intPreferencesKey(\"$p.lapses\")] = st.lapses                 e[longPreferencesKey(\"$p.due\")] = st.due             }         }     }<\/code><\/pre>\n<p>\u0412\u043f\u0440\u043e\u0447\u0435\u043c, \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u043b\u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"kotlin\">fun isReady(): Boolean = this::appCtx.isInitialized<\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0447\u0438\u0441\u0435\u043b, \u0441\u043e\u0437\u0440\u0435\u0432\u0448\u0438\u0445 \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0439, \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0432\u0441\u0435 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u043f\u043e \u0434\u0430\u0442\u0435 <code>due <\/code>\u0438 \u0431\u0435\u0440\u0435\u043c \u043f\u0435\u0440\u0432\u044b\u0435 <code>limit <\/code>\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0430\u0442\u044b:<\/p>\n<pre><code class=\"kotlin\">    @RequiresApi(Build.VERSION_CODES.O)     fun topDueNumbers(limit: Int = 5): List&lt;Int&gt; =         numMap.entries             .sortedBy { it.value.due }             .filter { it.value.due &lt;= todayEpoch() }             .map { it.key }             .take(limit)<\/code><\/pre>\n<p>\u0412\u043e\u0442 \u043c\u044b \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u043b\u043e\u0433\u0438\u043a\u0443 \u043d\u0430\u0448\u0435\u0439 SRS-\u0441\u0438\u0441\u0442\u0435\u043c\u044b.<\/p>\n<h2>\u0423\u0434\u0430\u0440\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c<\/h2>\n<p>\u0427\u0442\u043e \u0442\u0430\u043a \u043f\u0440\u0438\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043d\u0430\u0441 \u0432 \u0414\u0443\u043e\u043b\u0438\u043d\u0433\u043e? \u041a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0443\u0434\u0430\u0440\u043d\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430 aka \u0441\u0442\u0440\u0438\u043a!<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0437\u0430\u0438\u043c\u0441\u0442\u0432\u0443\u0435\u043c \u044d\u0442\u0443 \u0438\u0434\u0435\u044e.<\/p>\n<pre><code class=\"kotlin\">data class StreakSnapshot(     val current: Int,     val best: Int,     val week: List&lt;Boolean&gt;,     val todayActive: Boolean )<\/code><\/pre>\n<p>\u042d\u0442\u043e \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u044b\u0439 \u0441\u043d\u0438\u043c\u043e\u043a (snapshot) \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0441\u0435\u0440\u0438\u0438 \u0437\u0430\u043d\u044f\u0442\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0434\u043e\u0431\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0432 UI \u043e\u0434\u043d\u0438\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c.<\/p>\n<p>\u0421\u043d\u043e\u0432\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0438\u043d\u0433\u043b\u0442\u043e\u043d:<\/p>\n<pre><code class=\"kotlin\">object Streak {     private lateinit var appCtx: Context     private val scope = CoroutineScope(Dispatchers.IO)     fun init(context: Context) { appCtx = context.applicationContext }     private fun todayEpoch(): Long = LocalDate.now().toEpochDay()     \/\/ ... }<\/code><\/pre>\n<p>\u041f\u043e\u043a\u0430 \u0432\u0441\u0435 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c\u0443 \u0441\u043b\u0443\u0447\u0430\u044e.<\/p>\n<p>\u041e\u0431\u043d\u043e\u0432\u0438\u043c \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438, \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0432 \u0432 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u043c \u0432\u044b\u0448\u0435 \u0441\u043a\u043e\u0443\u043f\u0435 (\u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c UI) \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0443 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 DataStore:<\/p>\n<pre><code class=\"kotlin\">    fun markTodayActive() {         if (!::appCtx.isInitialized) return         scope.launch {             val ds = appCtx.streakDataStore             val prefs = ds.data.first()              val cur = prefs[KEY_CUR] ?: 0             val best = prefs[KEY_BEST] ?: 0             val last = prefs[KEY_LAST] ?: Long.MIN_VALUE             val set = (prefs[KEY_DAYS] ?: emptySet()).toMutableSet()              val today = todayEpoch()             if (!set.contains(today.toString())) set += today.toString()              val newCur = when {                 last == today -&gt; cur                  last == today - 1 -&gt; (cur + 1)                 else -&gt; 1             }             val newBest = maxOf(best, newCur)                          val keepAfter = today - 35             set.removeAll { it.toLongOrNull()?.let { d -&gt; d &lt; keepAfter } == true }              ds.edit {                 it[KEY_CUR] = newCur                 it[KEY_BEST] = newBest                 it[KEY_LAST] = today                 it[KEY_DAYS] = set             }         }     }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u0435\u0440\u043d\u0435\u0442 \u0441\u043d\u0438\u043c\u043e\u043a \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0441\u0442\u0440\u0438\u043a\u0430:<\/p>\n<pre><code class=\"kotlin\">    suspend fun snapshot(): StreakSnapshot {         val prefs = appCtx.streakDataStore.data.first()         val cur = prefs[KEY_CUR] ?: 0         val best = prefs[KEY_BEST] ?: 0         val last = prefs[KEY_LAST] ?: Long.MIN_VALUE         val set = prefs[KEY_DAYS] ?: emptySet()          val today = LocalDate.now()         val monday = today.with(DayOfWeek.MONDAY)         val week = (0..6).map { i -&gt;             val d = monday.plusDays(i.toLong()).toEpochDay().toString()             set.contains(d)         }         val todayActive = last == today.toEpochDay()         return StreakSnapshot(cur, best, week, todayActive)     }<\/code><\/pre>\n<h2>ViewModel aka VM<\/h2>\n<p>\u0421\u043e\u0433\u043b\u0430\u0441\u043d\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435 MVVM \u0441\u043e\u0437\u0434\u0430\u0435\u043c LessonViewModel \u0434\u043b\u044f \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0446\u0438\u0438 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u044d\u043a\u0440\u0430\u043d\u0430. VM \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0431\u0438\u0440\u0430\u0442\u044c \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0435 \u0447\u0438\u0441\u043b\u043e, \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0435\u0433\u043e \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435, \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u044d\u0442\u043e \u0432 UI, \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432 <code>Srs<\/code>.<\/p>\n<pre><code class=\"kotlin\">class LessonViewModel : ViewModel() {     var screen by mutableStateOf&lt;Screen&gt;(Screen.Menu); private set     var state by mutableStateOf(QuizState()); private set     var ttsEnabled by mutableStateOf(true); private set     var sessionGoal by mutableIntStateOf(20); private set          private val lastNums: ArrayDeque&lt;Int&gt; = ArrayDeque()     private var customQueue: List&lt;Int&gt;? = null<\/code><\/pre>\n<p>\u041f\u0435\u0440\u0432\u044b\u0435 \u0447\u0435\u0442\u044b\u0440\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u044b \u0441 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u043c \u0441\u0435\u0442\u0442\u0435\u0440\u043e\u043c (<code>private set<\/code>), \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0432\u043d\u0435 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 UI) \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0445 \u0447\u0442\u0435\u043d\u0438\u0435. \u0412 \u043d\u0438\u0445 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f:<\/p>\n<ul>\n<li>\n<p><code>screen<\/code> &#8212; \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u044d\u043a\u0440\u0430\u043d,<\/p>\n<\/li>\n<li>\n<p><code>state<\/code> &#8212; \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0443\u0440\u043e\u043a\u0430 (\u0432\u043e\u043f\u0440\u043e\u0441, \u043e\u0442\u0432\u0435\u0442, \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430 \u043f\u043e \u0443\u0440\u043e\u043a\u0443 \u0438 \u0442.\u0434.),<\/p>\n<\/li>\n<li>\n<p><code>ttsEnabled<\/code> &#8212; \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043b\u0438 \u043e\u0437\u0432\u0443\u0447\u0438\u0432\u0430\u043d\u0438\u0435 (Text-to-Speech),<\/p>\n<\/li>\n<li>\n<p><code>sessionGoal<\/code> &#8212; \u0447\u0438\u0441\u043b\u043e \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u043e\u0434\u043d\u043e\u043c \u0443\u0440\u043e\u043a\u0435.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u0441\u0435 \u0447\u0435\u0442\u044b\u0440\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f\u043c\u0438 Compose. \u041e\u043d\u0438 \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0435\u043b\u0435\u0433\u0430\u0442\u0430 <code>by mutableStateOf(...)<\/code> \/ <code>by mutableIntStateOf(...)<\/code>, \u0447\u0442\u043e\u0431\u044b \u043b\u044e\u0431\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0438\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0432\u044b\u0437\u044b\u0432\u0430\u043b\u043e \u0440\u0435\u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0438\u044e \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u043d\u0438\u043c\u0438 Composable.<\/p>\n<p>\u0414\u0432\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 &#8212; <code>lastNums <\/code>\u0438 <code>customQueue <\/code>&#8212; \u0441\u0442\u043e\u044f\u0442 \u043e\u0441\u043e\u0431\u043d\u044f\u043a\u043e\u043c.<code>lastNums<\/code> &#8212; \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0445\u0440\u0430\u043d\u0438\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0432\u044b\u0434\u0430\u043d\u043d\u044b\u0435 \u0447\u0438\u0441\u043b\u0430, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0442\u044c \u0438\u0445 \u0441\u0440\u0430\u0437\u0443, \u0434\u043b\u044f \u0447\u0435\u0433\u043e \u043d\u0430\u043c \u0438 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u0430\u0441\u044c \u0434\u0432\u0443\u0441\u0442\u043e\u0440\u043e\u043d\u043d\u044f\u044f \u043e\u0447\u0435\u0440\u0435\u0434\u044c <code>ArrayDeque<\/code>. <code>customQueue<\/code>  &#8212; \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0447\u0438\u0441\u0435\u043b \u0434\u043b\u044f \u0442\u0435\u0445 \u0441\u043b\u0443\u0447\u0430\u0435\u0432, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u043f\u043e \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u043d\u0430\u0431\u043e\u0440\u0443 \u0447\u0438\u0441\u0435\u043b, \u0446\u0438\u043a\u043b\u0438\u0447\u0435\u0441\u043a\u0438 \u0438 \u0432 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435, \u043c\u0438\u043d\u0443\u044f \u043e\u0431\u044b\u0447\u043d\u044b\u0439 <em>\u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u043e\u0442\u0431\u043e\u0440\u0430<\/em>, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0441\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c.<\/p>\n<pre><code class=\"kotlin\">    private fun pickNextNumber(): Int {         customQueue?.let { q -&gt;             if (q.isNotEmpty()) return q[state.totalAsked % q.size]         }         val due = if (Srs.isReady()) Srs.topDueNumbers(5) else emptyList()         val candidates = due.ifEmpty { (1..100).shuffled().take(5) }          val exclude = lastNums.toSet()         val pool = candidates.filterNot { it in exclude }.ifEmpty { candidates }         val chosen = pool.random()         lastNums.addLast(chosen); if (lastNums.size &gt; 6) lastNums.removeFirst()         return chosen     }<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0437\u0430\u0434\u0430\u043d\u0430 <code>customQueue<\/code>, \u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0431\u0435\u0440\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u0430 \u0435\u0441\u043b\u0438 \u043e\u043d \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432 \u0441\u043f\u0438\u0441\u043a\u0435, \u0442\u043e \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u0441\u043d\u0430\u0447\u0430\u043b\u0430 (\u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u0447\u0435\u0440\u0435\u0437 \u043e\u0441\u0442\u0430\u0442\u043e\u043a \u043e\u0442 \u0434\u0435\u043b\u0435\u043d\u0438\u044f).<\/p>\n<p>\u0412 \u043e\u0431\u0449\u0435\u043c \u0436\u0435 \u0441\u043b\u0443\u0447\u0430\u0435 \u043c\u044b \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0447\u0438\u0441\u043b\u0430 \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u044f: \u043f\u0440\u0435\u0436\u0434\u0435 \u0432\u0441\u0435\u0433\u043e \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442 5 \u0441\u043e\u0437\u0440\u0435\u0432\u0448\u0438\u0445 (<code>dueNums<\/code>), \u0430 \u0435\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0432\u044b\u0445 \u043d\u0435 \u043d\u0430\u0448\u043b\u043e\u0441\u044c, \u0442\u043e \u0431\u0435\u0440\u0435\u043c 5 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0445. \u0418\u0441\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0447\u0438\u0441\u043b\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u0434\u0430\u0432\u043d\u043e \u043f\u043e\u043f\u0430\u0434\u0430\u043b\u0438\u0441\u044c (<code>lastNums<\/code>). \u0417\u0430\u043e\u0434\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u043d\u0430\u0448 \u043a\u043e\u043b\u044c\u0446\u0435\u0432\u043e\u0439 \u0431\u0443\u0444\u0435\u0440, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u0435\u0440\u0435\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0443\u0431\u0438\u0440\u0430\u044f \u0438\u0437 \u043d\u0435\u0433\u043e \u0441\u0430\u043c\u044b\u0439 \u0441\u0442\u0430\u0440\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442. \u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0434\u043d\u043e \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u0438\u0437 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0445 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432.<\/p>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 \u043c\u044b \u043d\u0435 \u0445\u0440\u0430\u043d\u0438\u043c \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u043e\u0442\u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u043e <code>due<\/code> \u0441\u043f\u0438\u0441\u043e\u043a, \u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0438 \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \u043f\u0440\u0438 \u0432\u044b\u0431\u043e\u0440\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0447\u0438\u0441\u043b\u0430? \u0414\u0435\u043b\u043e \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e, \u043a\u0430\u043a \u043c\u044b \u0441\u043a\u043e\u0440\u043e \u0443\u0432\u0438\u0434\u0438\u043c, \u044d\u0442\u043e\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0431\u0443\u0434\u0435\u0442 \u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u043c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0435\u0433\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0437\u0430\u043d\u043e\u0432\u043e.<\/p>\n<p>\u0424\u043e\u0440\u043c\u0438\u0440\u0443\u044f \u0432\u043e\u043f\u0440\u043e\u0441, \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435 \u043f\u0440\u0438\u0433\u043e\u0434\u043d\u043e\u0435 \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u044f \u0447\u0438\u0441\u043b\u043e, \u043d\u043e \u0435\u0449\u0435 \u0438 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0432\u0441\u044e \u0441\u043e\u043f\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0430 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435:<\/p>\n<pre><code class=\"kotlin\">    private fun makeQuestion(number: Int): Pair&lt;String, String&gt; {         val fr = FrenchNumbers.toFrench(number)         return number.toString() to fr     }      private fun makeOptions(correct: String): List&lt;String&gt; {         val pool = mutableSetOf(correct)         while (pool.size &lt; 4) pool += FrenchNumbers.toFrench(Random.nextInt(0, 101))         return pool.shuffled()     }      @RequiresApi(Build.VERSION_CODES.O)     fun next() {         if (state.totalAsked &gt;= sessionGoal) screen = Screen.Result         else {             val n = pickNextNumber()             val (q, a) = makeQuestion(n)             val opts = if (state.mode == Mode.MultipleChoice) makeOptions(a) else emptyList()             state = state.copy(                 currentNumber = n,                 currentQuestion = q,                 currentAnswerCanonical = a,                 options = opts,                 feedback = null             )         }     }<\/code><\/pre>\n<p>\u041c\u0435\u0442\u043e\u0434 <code>makeQuestion()<\/code> \u043e\u0431\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043a \u043d\u0430\u0448\u0435\u043c\u0443 \u043a\u0440\u043e\u0445\u043e\u0442\u043d\u043e\u043c\u0443 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a\u0443 \u0437\u0430 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c \u0447\u0438\u0441\u043b\u0430 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043f\u0430\u0440\u0443 \u0438\u0437 \u0447\u0438\u0441\u043b\u0430 \u0438 \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435. <code>makeOptions()<\/code> \u0434\u0435\u043b\u0430\u0435\u0442 \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0434\u043b\u044f \u0435\u0449\u0435 3-\u0445 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0445 \u0447\u0438\u0441\u0435\u043b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c \u043e\u0442\u0432\u0435\u0442\u043e\u043c \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u044e\u0442 \u043f\u0443\u043b \u0438\u0437 4-\u0445 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u043e\u0442\u0432\u0435\u0442\u0430. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e (<code>mutableSetOf<\/code>), \u0442\u043e \u0435\u0441\u0442\u044c \u0434\u0443\u0431\u043b\u0435\u0439 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442.<\/p>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u0432 \u043c\u0435\u0442\u043e\u0434\u0435 <code>next()<\/code> \u043c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0432\u0441\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0438\u0445 \u0432 <code>state<\/code>, \u0434\u0435\u043b\u0430\u044f \u0435\u0433\u043e \u043a\u043e\u043f\u0438\u044e \u0434\u043b\u044f \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0440\u0435\u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0438\u0438.<\/p>\n<p>\u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f, \u0437\u0430\u0434\u0430\u043d \u043b\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0447\u0438\u0441\u0435\u043b \u0432 <code>customQueue<\/code>, \u043e\u0431\u043d\u0443\u043b\u0438\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u0447\u0438\u0441\u0435\u043b \u0438 \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>next()<\/code>:<\/p>\n<pre><code class=\"kotlin\">    fun start(nums: List&lt;Int&gt;? = null) {         customQueue = nums         lastNums.clear()         state = state.copy(score = 0, totalAsked = 0, feedback = null)         screen = Screen.Quiz         next()     }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u043e\u0442\u0432\u0435\u0442\u0430 \u043d\u0430\u043c \u043b\u0443\u0447\u0448\u0435 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0438\u0430\u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0437\u043d\u0430\u043a\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e:<\/p>\n<pre><code class=\"kotlin\">    private fun normalize(s: String): String {         val lower = s.lowercase().trim()         val decomposed = java.text.Normalizer.normalize(lower, java.text.Normalizer.Form.NFD)         val noAccents = decomposed.replace(Regex(\"\\\\p{Mn}+\"), \"\")         return noAccents.replace(Regex(\"[\\\\s\\\\-\u2019'`]\"), \"\")     }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0430\u043c \u043e\u0442\u0432\u0435\u0442:<\/p>\n<pre><code class=\"kotlin\">    fun checkAnswer(answerRaw: String) {         val canonUser = normalize(answerRaw)         val canonCorrect = normalize(state.currentAnswerCanonical)         val ok = canonUser == canonCorrect          Srs.updateNumber(state.currentNumber, ok)          state = state.copy(             score = state.score + if (ok) 1 else 0,             totalAsked = state.totalAsked + 1,             feedback = if (ok) \"\u2714 \u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e: ${state.currentAnswerCanonical}\"             else \"\u2718 \u041d\u0435\u0432\u0435\u0440\u043d\u043e. \u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e: ${state.currentAnswerCanonical}\"         )     }<\/code><\/pre>\n<p>\u0412 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f \u043e\u0442\u0432\u0435\u0442\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430 \u043c\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0443 SRS \u0434\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0447\u0438\u0441\u043b\u0430 \u0438 <code>state<\/code>.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u044d\u043a\u0440\u0430\u043d\u043e\u0432,<\/p>\n<pre><code class=\"kotlin\">    fun toMenu() { screen = Screen.Menu }     fun openStats() { screen = Screen.Stats }     fun openSettings() { screen = Screen.Settings }<\/code><\/pre>\n<p>\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u0430 \u0432\u0432\u043e\u0434\u0430 (\u0432\u044b\u0431\u043e\u0440 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u0438\u043b\u0438 \u0440\u0443\u0447\u043d\u043e\u0439)<\/p>\n<pre><code class=\"kotlin\">    fun setMode(m: Mode) { state = state.copy(mode = m) }<\/code><\/pre>\n<p>\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f\/\u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043e\u0437\u0432\u0443\u0447\u043a\u0438:<\/p>\n<pre><code class=\"kotlin\">    fun toggleTts() { ttsEnabled = !ttsEnabled }<\/code><\/pre>\n<h2>\u042d\u043a\u0440\u0430\u043d\u044b<\/h2>\n<p>\u041d\u0430\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432\u0441\u0435\u0433\u043e \u043f\u044f\u0442\u0438 \u044d\u043a\u0440\u0430\u043d\u043e\u0432:<\/p>\n<pre><code class=\"kotlin\">sealed interface Screen {     data object Home : Screen     data object Quiz : Screen     data object Result : Screen     data object Stats : Screen     data object Settings : Screen }<\/code><\/pre>\n<p>\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>Screen<\/code> \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d \u0441 \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c <code>sealed<\/code>, \u0447\u0442\u043e\u0431\u044b \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0442\u044c \u043a\u0440\u0443\u0433 \u0435\u0433\u043e \u043d\u0430\u0441\u043b\u0435\u0434\u043d\u0438\u043a\u043e\u0432 \u043d\u0430\u0431\u043e\u0440\u043e\u043c \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0445 \u044d\u043a\u0440\u0430\u043d\u043e\u0432.<\/p>\n<p>\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:<\/p>\n<div class=\"floating-image\">\n<figure class=\"float bordered full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/131\/c77\/09c\/131c7709c55aa6099e09fe3401861b91.jpg\" width=\"1080\" height=\"2408\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/131\/c77\/09c\/131c7709c55aa6099e09fe3401861b91.jpg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/131\/c77\/09c\/131c7709c55aa6099e09fe3401861b91.jpg 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0412\u043e\u0442 \u043a\u043e\u0434 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0438 \u044d\u0442\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430:<\/p>\n<\/div>\n<pre><code class=\"kotlin\">@Composable fun HomeScreen(     onStart: () -&gt; Unit,     onStats: () -&gt; Unit,     onSettings: () -&gt; Unit ) {     Box(         modifier = Modifier             .fillMaxSize()             .background(                 Brush.linearGradient(                     listOf(                         MaterialTheme.colorScheme.primary.copy(alpha = 0.08f),                         MaterialTheme.colorScheme.secondary.copy(alpha = 0.08f)                     )                 )             )             .statusBarsPadding()             .padding(24.dp)     ) {         Column(             modifier = Modifier.fillMaxSize(),             verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),             horizontalAlignment = Alignment.Start         ) {             Card(                 modifier = Modifier.fillMaxWidth(),                 shape = RoundedCornerShape(24.dp),                 colors = CardDefaults.cardColors(                     containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.85f)                 )             ) {                 Row(                     modifier = Modifier                         .fillMaxWidth()                         .padding(20.dp),                     verticalAlignment = Alignment.CenterVertically,                     horizontalArrangement = Arrangement.SpaceBetween                 ) {                     Column(Modifier.weight(1f)) {                         Text(                             \"\u0422\u0440\u0435\u043d\u0438\u0440\u043e\u0432\u043a\u0430 \u0447\u0438\u0441\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445\",                             style = MaterialTheme.typography.headlineLarge                         )                         Spacer(Modifier.height(6.dp))                         Text(                             \"\u0423\u0447\u0438\u0441\u044c \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043f\u043e-\u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438 \u043b\u0435\u0433\u043a\u043e \u0438 \u0431\u044b\u0441\u0442\u0440\u043e\",                             style = MaterialTheme.typography.titleMedium,                             color = MaterialTheme.colorScheme.onSurfaceVariant                         )                     }                     Column(                         horizontalAlignment = Alignment.End,                         verticalArrangement = Arrangement.Center,                         modifier = Modifier.padding(start = 12.dp)                     ) {                         Text(\"\ud83c\uddeb\ud83c\uddf7\", style = MaterialTheme.typography.displaySmall)                         Spacer(Modifier.height(4.dp))                         Text(\"1 2 3\", style = MaterialTheme.typography.titleLarge)                     }                 }             }              StreakCard()              Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth()) {                 BigActionButton(                     text = \"\u041d\u0430\u0447\u0430\u0442\u044c \u0442\u0440\u0435\u043d\u0438\u0440\u043e\u0432\u043a\u0443\",                     leading = { Icon(Icons.Filled.PlayArrow, contentDescription = null) },                     onClick = onStart                 )                 BigActionButton(                     text = \"\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430\",                     leading = { Icon(Icons.Filled.DateRange, contentDescription = null) },                     onClick = onStats                 )                 BigActionButton(                     text = \"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\",                     leading = { Icon(Icons.Filled.Settings, contentDescription = null) },                     onClick = onSettings                 )             }         }     } }<\/code><\/pre>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0442\u0440\u0438 \u043b\u044f\u043c\u0431\u0434\u044b, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u043c \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438. \u041a\u0430\u043a \u0438 \u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f \u0432 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435 MVVM, \u044d\u043a\u0440\u0430\u043d \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u043e \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 \u0438 \u043b\u0438\u0448\u044c \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u044f\u0445 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043a\u043d\u043e\u043f\u043e\u043a.<\/p>\n<p>\u0414\u043b\u044f \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u043c\u044b \u0437\u0430\u0434\u0430\u0435\u043c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0444\u043e\u043d\u0430 \u0434\u0438\u0430\u0433\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u0437 \u0432\u0435\u0440\u0445\u043d\u0435\u0433\u043e \u043b\u0435\u0432\u043e\u0433\u043e \u043a \u043d\u0438\u0436\u043d\u0435\u043c\u0443 \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u0443\u0433\u043b\u0443). \u0426\u0432\u0435\u0442\u0430 \u0431\u0435\u0440\u0435\u043c \u0438\u0437 \u043f\u0430\u043b\u0438\u0442\u0440\u044b Material3  (<code>colorScheme.primary\/secondary<\/code>) \u0438 \u0434\u0435\u043b\u0430\u0435\u043c \u043f\u043e\u043b\u0443\u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u044b\u043c\u0438 (<code>copy(alpha=0.08f)<\/code>), \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430\u0441\u044c \u043b\u0435\u0433\u043a\u0430\u044f \u0434\u044b\u043c\u043a\u0430.<\/p>\n<p>\u0410 \u0432\u043e\u0442 \u0442\u0430 \u0441\u0430\u043c\u0430\u044f \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430:<\/p>\n<pre><code class=\"kotlin\">@Composable private fun BigActionButton(     text: String,     leading: @Composable (() -&gt; Unit)? = null,     onClick: () -&gt; Unit ) {     Button(         onClick = onClick,         modifier = Modifier.fillMaxWidth(),         shape = RoundedCornerShape(28.dp),         contentPadding = PaddingValues(vertical = 18.dp, horizontal = 20.dp),         elevation = ButtonDefaults.buttonElevation(defaultElevation = 2.dp)     ) {         if (leading != null) {             leading()             Spacer(Modifier.width(12.dp))         }         Text(text, style = MaterialTheme.typography.headlineSmall)     } }<\/code><\/pre>\n<p>\u0427\u0442\u043e \u0441\u0430\u043c\u043e\u0435 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043d\u0430 \u044d\u0442\u043e\u043c \u044d\u043a\u0440\u0430\u043d\u0435? \u041a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0443\u0434\u0430\u0440\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c!<\/p>\n<pre><code class=\"kotlin\">@Composable fun StreakCard(     modifier: Modifier = Modifier ) {     var snapshot by remember { mutableStateOf(StreakSnapshot(0, 0, List(7) { false }, false)) }      LaunchedEffect(Unit) {         snapshot = Streak.snapshot()     }      Card(         modifier = modifier.fillMaxWidth(),         shape = RoundedCornerShape(20.dp),         colors = CardDefaults.cardColors(             containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f)         )     ) {         Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {             Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {                 Text(\"\u26a1\", style = MaterialTheme.typography.titleLarge)                 Column {                     Text(\"\u0414\u043d\u0435\u0439 \u0431\u0435\u0437 \u043f\u0435\u0440\u0435\u0440\u044b\u0432\u0430: ${snapshot.current}\", style = MaterialTheme.typography.titleLarge)                     Text(\"\u0420\u0435\u043a\u043e\u0440\u0434 \u2014 ${snapshot.best}\", style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant)                 }             }              Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically) {                 val labels = listOf(\"\u043f\u043d\",\"\u0432\u0442\",\"\u0441\u0440\",\"\u0447\u0442\",\"\u043f\u0442\",\"\u0441\u0431\",\"\u0432\u0441\")                 snapshot.week.forEachIndexed { i, done -&gt;                     Box(                         modifier = Modifier                             .size(22.dp)                             .background(                                 color = if (done) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,                                 shape = RoundedCornerShape(6.dp)                             ),                         contentAlignment = Alignment.Center                     ) {                         Text(labels[i], style = MaterialTheme.typography.labelSmall, color = if (done) Color.White else MaterialTheme.colorScheme.onSurfaceVariant)                     }                 }             }         }     } }<\/code><\/pre>\n<p>\u0412 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 <code>snapshot <\/code>\u043c\u044b \u0445\u0440\u0430\u043d\u0438\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438. \u0415\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0432\u044b\u0437\u043e\u0432\u0435\u0442 \u0440\u0435\u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0438\u044e.<\/p>\n<p><code>LaunchedEffect <\/code>\u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u043e\u043c\u0443 \u0446\u0438\u043a\u043b\u0443 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 Composable-\u0444\u0443\u043d\u043a\u0446\u0438\u0438. \u042d\u0442\u043e \u0440\u043e\u0432\u043d\u043e \u0442\u043e, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u0443\u0434\u0430\u0440\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430. \u041e\u043d\u0438 \u043d\u0435 \u043d\u0443\u0436\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u0431\u043e\u043b\u0435\u0435 \u0447\u0430\u0441\u0442\u043e\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0443\u0434\u0430\u0440\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u043d\u0435 \u043e\u0431\u043d\u043e\u0432\u0438\u0448\u044c, \u043f\u043e\u043a\u0430 \u043d\u0435 \u0437\u0430\u0439\u0434\u0435\u0448\u044c \u0432 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u0435 \u0438 \u043d\u0435 \u0432\u0435\u0440\u043d\u0435\u0448\u044c\u0441\u044f \u0438\u0437 \u043d\u0435\u0433\u043e, \u0430 \u043a \u044d\u0442\u043e\u043c\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043c\u044b \u0443\u0436\u0435 \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u044b\u0439 \u044d\u043a\u0440\u0430\u043d.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u044d\u043a\u0440\u0430\u043d \u0441 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u0435\u043c.<\/p>\n<div class=\"floating-image\">\n<figure class=\"float full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/78c\/a75\/eb4\/78ca75eb4ed40e946f38f758b7a869b2.jpg\" width=\"1080\" height=\"2408\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/78c\/a75\/eb4\/78ca75eb4ed40e946f38f758b7a869b2.jpg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/78c\/a75\/eb4\/78ca75eb4ed40e946f38f758b7a869b2.jpg 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<\/div>\n<p>\u0412 \u0432\u0435\u0440\u0445\u043d\u0435\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u044f \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 &#171;x \u0438\u0437 y&#187; \u0438 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0445 \u043e\u0442\u0432\u0435\u0442\u043e\u0432:<\/p>\n<pre><code class=\"kotlin\">@Composable private fun QuizHeader(     currentIndex: Int,     totalQuestions: Int,     score: Int ) {     Row(         modifier = Modifier             .fillMaxWidth()             .padding(top = 4.dp, start = 4.dp, end = 4.dp),         verticalAlignment = Alignment.CenterVertically     ) {         \/\/ \u041b\u0435\u0432\u0430\u044f \u0447\u0430\u0441\u0442\u044c: \u0438\u043a\u043e\u043d\u043a\u0430 + \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441 \"x \u0438\u0437 y\"         Row(             verticalAlignment = Alignment.CenterVertically,             modifier = Modifier.weight(1f)         ) {             Icon(                 imageVector = Icons.Outlined.CheckCircle,                 contentDescription = null,                 modifier = Modifier.size(22.dp),                 tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.85f)             )             Spacer(Modifier.width(6.dp))             Text(                 \"$currentIndex \u0438\u0437 $totalQuestions\",                 style = MaterialTheme.typography.titleMedium             )         }          \/\/ \u041f\u0440\u0430\u0432\u0430\u044f \u0447\u0430\u0441\u0442\u044c: \u043e\u0447\u043a\u0438 + \u043d\u043e\u0442\u0430         Row(verticalAlignment = Alignment.CenterVertically) {             Icon(                 imageVector = Icons.Outlined.Star,                 contentDescription = null,                 modifier = Modifier.size(22.dp),                 tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.85f)             )             Spacer(Modifier.width(6.dp))             Text(\"$score\", style = MaterialTheme.typography.titleMedium)         }     } }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c <code>currentIndex <\/code>\u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u0441 1, \u0430 \u043d\u0435 \u0441 0, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u0431\u044b\u043b \u0433\u043e\u0442\u043e\u0432 \u0434\u043b\u044f \u0432\u044b\u0432\u043e\u0434\u0430.<\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u044d\u043a\u0440\u0430\u043d\u0430 \u0434\u043b\u0438\u043d\u043d\u0430\u044f, \u043d\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430\u044f:<\/p>\n<pre><code class=\"kotlin\">@Composable fun QuizScreen(     state: QuizState,     totalQuestions: Int,     ttsEnabled: Boolean,     onAnswer: (String) -&gt; Unit,     onNext: () -&gt; Unit,     onSpeak: (String) -&gt; Unit ) {     val focus = LocalFocusManager.current     var input by remember { mutableStateOf(\"\") }     var selectedAnswer by remember { mutableStateOf&lt;String?&gt;(null) }      \/\/ \u0421\u0431\u0440\u043e\u0441 \u043f\u0440\u0438 \u043d\u043e\u0432\u043e\u043c \u0432\u043e\u043f\u0440\u043e\u0441\u0435     LaunchedEffect(state.currentQuestion) {         input = \"\"         selectedAnswer = null     }      \/\/ \u0410\u0432\u0442\u043e\u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u043a\u0430\u0437\u0430 \u0444\u0438\u0434\u0431\u044d\u043a\u0430     LaunchedEffect(state.totalAsked) {         if (state.feedback != null) {             val baseDelay = 1500L             val extraDelay = if (ttsEnabled)                 (state.currentAnswerCanonical.length * 40L).coerceAtMost(2000L)             else 0L             delay(baseDelay + extraDelay)             onNext()         }     }      Column(         modifier = Modifier             .fillMaxSize()             .padding(20.dp),         verticalArrangement = Arrangement.spacedBy(16.dp),         horizontalAlignment = Alignment.CenterHorizontally     ) {         \/\/ \u041f\u043e\u0434\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a: \u0432\u043e\u043f\u0440\u043e\u0441 \/ \u043e\u0447\u043a\u0438 \/ \u0437\u0432\u0443\u043a         QuizHeader(             currentIndex = state.totalAsked + if (state.feedback == null) 1 else 0,             totalQuestions = totalQuestions,             score = state.score         )          \/\/ \u041a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u0437\u0430\u0434\u0430\u043d\u0438\u044f         Card(             modifier = Modifier.fillMaxWidth(),             colors = CardDefaults.cardColors(                 containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f)             ),             shape = RoundedCornerShape(20.dp)         ) {             Column(                 Modifier.padding(16.dp),                 horizontalAlignment = Alignment.CenterHorizontally             ) {                 Text(\"\u041a\u0430\u043a \u0431\u0443\u0434\u0435\u0442 \u043f\u043e-\u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438?\", style = MaterialTheme.typography.titleLarge)                 Spacer(Modifier.height(8.dp))                 Text(state.currentQuestion, style = MaterialTheme.typography.displayLarge)             }         }          \/\/ \u0420\u0435\u0436\u0438\u043c \u0432\u044b\u0431\u043e\u0440\u0430         when (state.mode) {             Mode.MultipleChoice -&gt; {                 Column(                     verticalArrangement = Arrangement.spacedBy(12.dp),                     modifier = Modifier.fillMaxWidth()                 ) {                     state.options.forEach { opt -&gt;                         val isSelected = selectedAnswer == opt                         val isCorrectOption = opt == state.currentAnswerCanonical                         val isCorrectSelected = state.feedback != null &amp;&amp; isSelected &amp;&amp; isCorrectOption                         val isWrongSelected = state.feedback != null &amp;&amp; isSelected &amp;&amp; !isCorrectOption                         val isCorrectUnselected = state.feedback != null &amp;&amp; !isSelected &amp;&amp; isCorrectOption                          Button(                             onClick = {                                 if (state.feedback == null) {                                     selectedAnswer = opt                                     onSpeak(opt)                                     onAnswer(opt)                                 }                             },                             enabled = state.feedback == null,                             modifier = Modifier                                 .fillMaxWidth()                                 .height(56.dp),                             shape = RoundedCornerShape(28.dp),                             colors = ButtonDefaults.buttonColors(                                 containerColor = when {                                     isCorrectSelected || isCorrectUnselected -&gt; Color(0xFF4CAF50)                                     isWrongSelected -&gt; Color(0xFFF44336)                                     else -&gt; MaterialTheme.colorScheme.primary                                 },                                 disabledContainerColor = when {                                     isCorrectSelected || isCorrectUnselected -&gt; Color(0xFF4CAF50)                                     isWrongSelected -&gt; Color(0xFFF44336)                                     else -&gt; MaterialTheme.colorScheme.primary                                 }                             ),                             contentPadding = PaddingValues(horizontal = 20.dp)                         ) {                             Text(opt, style = MaterialTheme.typography.titleLarge)                         }                     }                 }             }              Mode.Typing -&gt; {                 OutlinedTextField(                     value = input,                     onValueChange = { input = it },                     modifier = Modifier.fillMaxWidth(),                     label = { Text(\"\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043e\u0442\u0432\u0435\u0442 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, vingt et un)\") },                     enabled = state.feedback == null,                     keyboardOptions = KeyboardOptions(                         capitalization = KeyboardCapitalization.None,                         imeAction = ImeAction.Done                     ),                     keyboardActions = KeyboardActions(onDone = {                         focus.clearFocus()                         if (state.feedback == null) onAnswer(input)                     })                 )             }         }          \/\/ \u0424\u0438\u0434\u0431\u044d\u043a         if (state.feedback != null) {             Text(                 state.feedback,                 style = MaterialTheme.typography.titleLarge,                 modifier = Modifier.fillMaxWidth()             )         }     } } <\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043e\u0431\u0430 <code>LaunchedEffect<\/code> \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b, \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u043a \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u043e\u043c\u0443 \u0446\u0438\u043a\u043b\u0443 \u044d\u0442\u043e\u0439 Composable-\u0444\u0443\u043d\u043a\u0446\u0438\u0438. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0443 \u043d\u0438\u0445 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u043a\u043b\u044e\u0447\u0438, \u0432 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0430, \u0433\u0434\u0435 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u043b\u0438 <code>Unit<\/code>. \u041f\u0440\u0438 \u0441\u043c\u0435\u043d\u0435 \u043a\u043b\u044e\u0447\u0430 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0449\u0435\u043d\u044b.<\/p>\n<pre><code class=\"kotlin\">    LaunchedEffect(state.currentQuestion) {         input = \"\"         selectedAnswer = null     }<\/code><\/pre>\n<p>&#8212; \u044d\u0442\u043e\u0442 \u043a\u043e\u0434 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0443, \u043e\u0431\u043d\u0443\u043b\u044f\u044f \u0432\u0432\u043e\u0434 \u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442, \u043f\u0440\u0438 \u043f\u0440\u0438\u0445\u043e\u0434\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0432\u043e\u043f\u0440\u043e\u0441\u0430.<\/p>\n<pre><code class=\"kotlin\">    LaunchedEffect(state.totalAsked) {         if (state.feedback != null) {             val baseDelay = 1500L             val extraDelay = if (ttsEnabled)                 (state.currentAnswerCanonical.length * 40L).coerceAtMost(2000L)             else 0L             delay(baseDelay + extraDelay)             onNext()         }     }<\/code><\/pre>\n<p>&#8212; \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0442\u0430\u0439\u043c\u0435\u0440, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0432\u043e\u043f\u0440\u043e\u0441\u0443 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0430 \u043d\u0435 \u043d\u0430\u0436\u0438\u043c\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443. \u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u0439 \u043e\u0437\u0432\u0443\u0447\u043a\u0435 \u0434\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f, \u0447\u0442\u043e\u0431\u044b \u0443\u0441\u043b\u044b\u0448\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442 (\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c <code>extraDelay<\/code> \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0434\u043b\u0438\u043d\u044b \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430). \u0422\u0430\u0439\u043c\u0435\u0440 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u0441\u0447\u0435\u0442\u0447\u0438\u043a\u0430 <code>state.totalAsked<\/code>, \u0442\u043e \u0435\u0441\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430. \u041f\u043e\u0441\u043b\u0435 <code>delay()<\/code> \u043c\u044b \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 <code>onNext()<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043c \u0435\u0449\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u043e\u0438\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c.<\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0440\u0438 \u044d\u043a\u0440\u0430\u043d\u0430 &#8212; \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u044f, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430 &#8212; \u0443\u0436\u0435 \u043d\u0435 \u043e\u0442\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043a SRS-\u0442\u0440\u0435\u043d\u0430\u0436\u0435\u0440\u0443, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0438\u0445 \u043a\u043e\u0434\u0435. \u041a\u043d\u043e\u043f\u043a\u0430 &#171;\u041f\u043e\u0442\u0440\u0435\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0430\u0431\u044b\u0435&#187; \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u0443\u0440\u043e\u043a, \u043d\u043e \u0441 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0435\u0439 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u0432 \u043c\u0435\u0442\u043e\u0434 VM <code>start()<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0443\u0436\u0435 \u043e\u0431\u0441\u0443\u0434\u0438\u043b\u0438. \u0421\u043f\u0438\u0441\u043e\u043a \u0431\u0435\u0440\u0435\u043c \u043e\u0442\u0441\u044e\u0434\u0430 \u0436\u0435, \u0438\u0437 \u044d\u0442\u043e\u0439 \u0442\u0435\u043f\u043b\u043e\u0432\u043e\u0439 \u043a\u0430\u0440\u0442\u044b, \u043e\u0442\u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0432 \u043f\u043e \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u044d\u043c\u043f\u0438\u0440\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043c\u0435\u0442\u0440\u0438\u043a\u0435 \u0438 \u0432\u0437\u044f\u0432 \u043f\u0435\u0440\u0432\u044b\u0435 <code>n<\/code> \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439.<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/986\/0ed\/5f0\/9860ed5f07b86b9c2d18ceb105cd7067.jpg\" width=\"2160\" height=\"2408\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/986\/0ed\/5f0\/9860ed5f07b86b9c2d18ceb105cd7067.jpg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/986\/0ed\/5f0\/9860ed5f07b86b9c2d18ceb105cd7067.jpg 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h2>MainActivity<\/h2>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u0435 \u0432\u043c\u0435\u0441\u0442\u0435. \u0412 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0430\u043a\u0442\u0438\u0432\u0438\u0442\u0438 \u0432\u0430\u0436\u043d\u043e \u043d\u0435 \u0437\u0430\u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0438\u0436\u043e\u043a TTS \u0432 \u043c\u0435\u0442\u043e\u0434\u0435 <code>onCreate()<\/code> \u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0435\u0433\u043e \u0432 \u043c\u0435\u0442\u043e\u0434\u0435 <code>onDestroy()<\/code>:<\/p>\n<pre><code class=\"kotlin\">class MainActivity : ComponentActivity() {     private var tts: TextToSpeech? = null      @RequiresApi(Build.VERSION_CODES.O)     override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         enableEdgeToEdge()         lifecycleScope.launch { Srs.init(applicationContext) }          tts = TextToSpeech(this) { status -&gt;             if (status == TextToSpeech.SUCCESS) tts?.language = Locale.FRANCE         }         Streak.init(applicationContext)         setContent { App(tts = tts) }     }      override fun onDestroy() {         tts?.stop()         tts?.shutdown()         super.onDestroy()     } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043c\u043e\u0434\u043d\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430 <a href=\"https:\/\/habr.com\/ru\/companies\/avito\/articles\/905154\/\" rel=\"noopener noreferrer nofollow\">edge-to-edge<\/a>, \u0442\u043e \u0435\u0441\u0442\u044c \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0438 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0430 \u043d\u0430 \u0432\u0441\u044e \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u044d\u043a\u0440\u0430\u043d\u0430. <\/p>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u044d\u043a\u0440\u0430\u043d \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 <code>screen<\/code> \u0438\u0437 VM, \u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u044f \u0432 \u044d\u0442\u043e\u0442 \u044d\u043a\u0440\u0430\u043d \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438\u0437 \u0442\u043e\u0439 \u0436\u0435 VM. \u041f\u0430\u0442\u0442\u0435\u0440\u043d MVVM \u0432 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438!<\/p>\n<p>\u041a\u0430\u043a \u0438 \u0432\u0441\u0435 Composable-\u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043d\u0430\u0448\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f App() \u0434\u043b\u0438\u043d\u043d\u0430\u044f, \u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430\u044f:<\/p>\n<pre><code class=\"kotlin\">@Composable fun App(tts: TextToSpeech?, vm: LessonViewModel = viewModel()) {     MaterialTheme(colorScheme = lightColorScheme()) {         Scaffold(             topBar = {                 if (vm.screen != Screen.Home) {                     TopAppBar(                         title = {                             if (vm.screen != Screen.Home) {                                 Text(                                     when (vm.screen) {                                         Screen.Quiz -&gt; \"\u0422\u0440\u0435\u043d\u0438\u0440\u043e\u0432\u043a\u0430\"                                         Screen.Result -&gt; \"\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\"                                         Screen.Stats -&gt; \"\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430 \u043f\u043e \u0447\u0438\u0441\u043b\u0430\u043c\"                                         Screen.Settings -&gt; \"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\"                                         else -&gt; \"\"                                     },                                     style = MaterialTheme.typography.titleLarge                                 )                             }                         },                         navigationIcon = {                             IconButton(onClick = { vm.toMenu() }) {                                 Icon(Icons.Filled.ArrowBack, contentDescription = \"\u0412 \u043c\u0435\u043d\u044e\")                             }                         },                         actions = {                             if (vm.screen == Screen.Quiz) {                                 \/\/ \u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c TTS \u0432 \u0448\u0430\u043f\u043a\u0435                                 FilledTonalIconButton(                                     onClick = { vm.toggleTts() },                                     modifier = Modifier.size(44.dp)                                 ) {                                     val icon =                                         if (vm.ttsEnabled) R.drawable.sound_on else R.drawable.sound_off                                     Icon(                                         painter = painterResource(icon),                                         contentDescription = if (vm.ttsEnabled) \"\u041e\u0437\u0432\u0443\u0447\u043a\u0430 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430\" else \"\u041e\u0437\u0432\u0443\u0447\u043a\u0430 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0430\",                                         modifier = Modifier.size(26.dp)                                     )                                 }                             }                         }                      )                 }             },             contentWindowInsets = WindowInsets(0)         ) { padding -&gt;             Box(Modifier.fillMaxSize().padding(padding)) {                 when (vm.screen) {                     Screen.Home -&gt; HomeScreen(                         onStart = { vm.start() },                         onStats = { vm.openStats() },                         onSettings = { vm.openSettings() }                     )                     Screen.Quiz -&gt; QuizScreen(                         state = vm.state,                         totalQuestions = vm.sessionGoal,                         ttsEnabled = vm.ttsEnabled,                         onAnswer = { vm.checkAnswer(it) },                         onNext = { vm.next() },                         onSpeak = { text -&gt; if (vm.ttsEnabled) tts?.speak(text, TextToSpeech.QUEUE_FLUSH, null, \"ans\") }                     )                     Screen.Result -&gt; ResultScreen(                         score = vm.state.score,                         total = vm.state.totalAsked,                         onRestart = { vm.toMenu() }                     )                     Screen.Stats -&gt; StatsScreen(                         onBack = { vm.toMenu() },                         onPracticeWeak = { weakList -&gt; vm.start(weakList) }                     )                     Screen.Settings -&gt; SettingsScreen(                         mode = vm.state.mode,                         onModeChange = { vm.setMode(it) },                         ttsEnabled = vm.ttsEnabled,                         onToggleTts = { vm.toggleTts() },                         sessionGoal = vm.sessionGoal,                         onSessionGoalChange = vm::changeSessionGoal                     )                  }             }         }     } }<\/code><\/pre>\n<p>\u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c \u043e\u0437\u0432\u0443\u0447\u043a\u0438 \u043c\u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0443\u0434\u043e\u0431\u043d\u044b\u043c \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 <code>TopAppBar<\/code>, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u0431\u044b\u043b \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0432\u0438\u0434\u0443.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432 \u044d\u0442\u043e\u043c \u043a\u043e\u0434\u0435 \u0431\u044b\u043b \u0437\u0430\u0434\u0430\u043d \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>contentWindowInsets<\/code>, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0448 \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u043d\u044b\u0439 \u0444\u043e\u043d \u043d\u0435 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u043b\u0441\u044f \u0438\u043d\u0441\u0435\u0442\u043e\u043c <code>Scaffold<\/code>&#8216;\u0430.<\/p>\n<pre><code class=\"kotlin\">contentWindowInsets = WindowInsets(0)<\/code><\/pre>\n<h2>\u0418\u0442\u043e\u0433\u0438<\/h2>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u043d\u043e <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=com.astfreelancer.frnumbers\" rel=\"noopener noreferrer nofollow\">\u0441\u043a\u0430\u0447\u0430\u0442\u044c \u0432 Google Play<\/a>.<\/p>\n<p>\u0418\u0437\u0443\u0447\u0435\u043d\u0438\u0435 \u0438\u043d\u043e\u0441\u0442\u0440\u0430\u043d\u043d\u044b\u0445 \u044f\u0437\u044b\u043a\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e SRS-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 &#8212; \u043c\u043e\u0439 \u043a\u043e\u043d\u0435\u043a. \u0418 \u0443\u0447\u0438\u0442\u044c \u044f\u0437\u044b\u043a\u0438, \u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0435 \u0442\u0440\u0435\u043d\u0430\u0436\u0435\u0440\u044b \u044f \u043c\u043e\u0433\u0443 \u0434\u043e \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0441\u0442\u0438. \u0412\u043e\u0442 \u0435\u0449\u0435 <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=com.astfreelancer.glossarylotr\" rel=\"noopener noreferrer nofollow\">\u043e\u0434\u0438\u043d \u043f\u0440\u0438\u043c\u0435\u0440<\/a>, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u044f \u043a\u043e\u0433\u0434\u0430-\u0442\u043e <a href=\"https:\/\/habr.com\/ru\/articles\/706224\/\" rel=\"noopener noreferrer nofollow\">\u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u043b\u0430<\/a> \u043d\u0430 \u0425\u0430\u0431\u0440\u0435 \u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u0442\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0430 \u043d\u0430 Jetpack Compose.<\/p>\n<p>\u0410 \u0435\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043b\u0443\u0447\u0448\u0435 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0432 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430\u0445 \u0438 Flow, \u0442\u043e \u0443 \u043c\u0435\u043d\u044f \u0435\u0441\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u0439 <a href=\"https:\/\/stepik.org\/a\/205482?utm_source=habr_article\" rel=\"noopener noreferrer nofollow\">\u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043a\u0443\u0440\u0441 \u043d\u0430 \u0421\u0442\u0435\u043f\u0438\u043a\u0435<\/a> \u043f\u043e \u044d\u0442\u043e\u0439 \u0442\u0435\u043c\u0435.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/940032\/\"> https:\/\/habr.com\/ru\/articles\/940032\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0422\u0435, \u043a\u0442\u043e \u0443\u0447\u0438\u043b \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0439, \u0437\u043d\u0430\u044e\u0442, \u043a\u0430\u043a \u0441\u043b\u043e\u0436\u043d\u043e \u043e\u0441\u0432\u043e\u0438\u0442\u044c\u0441\u044f \u0432 \u043a\u0440\u0443\u0433\u0443 \u0438\u0445 \u0447\u0438\u0441\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445. \u0412\u043e \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435 <a href=\"https:\/\/5respublika.com\/kultura\/les-chiffres-etranges.html\" rel=\"noopener noreferrer nofollow\">\u0443\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f<\/a> \u0441\u0440\u0430\u0437\u0443 \u0434\u0432\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0441\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f &#8212; \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u0430\u044f \u043d\u0430\u043c \u0434\u0435\u0441\u044f\u0442\u0435\u0440\u0438\u0447\u043d\u0430\u044f \u0438 \u043a\u0435\u043b\u044c\u0442\u0441\u043a\u043e-\u043d\u043e\u0440\u043c\u0430\u043d\u043d\u0441\u043a\u0430\u044f \u0434\u0432\u0430\u0434\u0446\u0430\u0442\u0435\u0440\u0438\u0447\u043d\u0430\u044f, \u043e\u043d\u0430 \u0436\u0435 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%94%D0%B2%D0%B0%D0%B4%D1%86%D0%B0%D1%82%D0%B5%D1%80%D0%B8%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F\" rel=\"noopener noreferrer nofollow\">\u0432\u0438\u0433\u0435\u0437\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f<\/a>.<\/p>\n<blockquote>\n<p>Mille quatre cent quatre-vingt-deux<\/p>\n<\/blockquote>\n<p>&#8212; \u043f\u043e\u0435\u0442 \u0413\u0440\u0438\u043d\u0433\u0443\u0430\u0440 \u043f\u0440\u043e 1482 \u0433\u043e\u0434. \u0417\u0434\u0435\u0441\u044c 400 &#8212; \u0432 \u0434\u0435\u0441\u044f\u0442\u0435\u0440\u0438\u0447\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 (quatre cent), \u0430 80 &#8212; \u0443\u0436\u0435 \u0432 20-\u0440\u0438\u0447\u043d\u043e\u0439 (quatre-vingt).<\/p>\n<p>\u0418 \u0445\u043e\u0442\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0441\u0442\u0430 \u043d\u0435 \u0437\u0430\u043f\u0443\u0433\u0430\u0435\u0448\u044c \u0434\u0430\u0436\u0435 16-\u0440\u0438\u0447\u043d\u043e\u0439, \u0432\u0441\u0435-\u0442\u0430\u043a\u0438 \u043c\u0435\u0442\u0430\u0442\u044c\u0441\u044f \u043c\u0435\u0436\u0434\u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u043c\u0438 \u0441\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u0432 \u0443\u043c\u0435, \u043a\u043e\u0433\u0434\u0430 \u0442\u044b \u0443\u0436\u0435 \u0441\u0442\u043e\u0438\u0448\u044c \u0443 \u043a\u0430\u0441\u0441\u044b \u0438 \u0434\u043e\u043b\u0436\u0435\u043d \u0440\u0430\u0441\u043f\u043b\u0430\u0442\u0438\u0442\u044c\u0441\u044f, \u043f\u0440\u0438\u0447\u0435\u043c, \u043f\u043e \u043d\u044b\u043d\u0435\u0448\u043d\u0438\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0430\u043c, \u043d\u0430\u043b\u0438\u0447\u043d\u044b\u043c\u0438, &#8212; \u0442\u043e\u0442 \u0435\u0449\u0435 \u043a\u0432\u0435\u0441\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0432\u0435\u0434\u0435\u043c \u0437\u043d\u0430\u043d\u0438\u0435 \u0447\u0438\u0441\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0434\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u043c\u0430.<\/p>\n<h2>\u0413\u0440\u0430\u043c\u043c\u0430\u0442\u0438\u043a\u0430<\/h2>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043c\u0438\u043d\u0438\u0430\u0442\u044e\u0440\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0441 \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0433\u043e \u043d\u0430 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0439. \u0422\u043e\u0447\u043d\u0435\u0435, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0435\u0440 \u0438\u0437 \u0446\u0435\u043b\u043e\u0433\u043e \u0447\u0438\u0441\u043b\u0430 \u043e\u0442 0 \u0434\u043e 100 \u0432 \u0441\u0442\u0440\u043e\u043a\u0443.<\/p>\n<p>\u0412\u043e\u0442 \u043d\u0430\u0448\u0438 \u043a\u0438\u0440\u043f\u0438\u0447\u0438\u043a\u0438, \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u044d\u0442\u043e\u0433\u043e \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430:<\/p>\n<pre><code class=\"kotlin\">    private val units = arrayOf(         \"z\u00e9ro\", \"un\", \"deux\", \"trois\", \"quatre\", \"cinq\", \"six\", \"sept\", \"huit\", \"neuf\"     )      private val tenToSixteen = arrayOf(         \"dix\", \"onze\", \"douze\", \"treize\", \"quatorze\", \"quinze\", \"seize\"     )      private val tens = arrayOf(         \"\", \"dix\", \"vingt\", \"trente\", \"quarante\", \"cinquante\", \"soixante\"     )<\/code><\/pre>\n<p>\u0418\u0445 \u0432\u0441\u0435\u0433\u043e \u0442\u0440\u0438 \u0432\u0438\u0434\u0430 &#8212; \u043e\u0442 0 \u0434\u043e 9, \u043e\u0442 10 \u0434\u043e 16, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043e\u0442 10 \u0434\u043e 60.<\/p>\n<p>\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0447\u0438\u0441\u0435\u043b \u043e\u0442 0 \u0434\u043e 16 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0438\u0437 \u044d\u0442\u0438\u0445 \u043c\u0430\u0441\u0441\u0438\u0432\u043e\u0432, \u0430 \u043e\u0442 17 \u0434\u043e 19 &#8212; \u043f\u0440\u0438\u0431\u0430\u0432\u043a\u043e\u0439 &#171;dix-&#187; \u043a \u0447\u0438\u0441\u043b\u0443 \u0435\u0434\u0438\u043d\u0438\u0446: dix-sept, dix-huit, dix-neuf:<\/p>\n<pre><code class=\"kotlin\">private fun from0to19(n: Int): String = when (n) {     in 0..9   -&gt; units[n]     in 10..16 -&gt; tenToSixteen[n - 10]     in 17..19 -&gt; \"dix-\" + units[n - 10]     else      -&gt; error(\"Bad 1..19 input: $n\") }<\/code><\/pre>\n<p>\u041e\u0442 20 \u0434\u043e 69 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0434\u0435\u0441\u044f\u0442\u0435\u0440\u0438\u0447\u043d\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0441\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f. \u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u0447\u0438\u0441\u043b\u0430 \u0432 \u043d\u0435\u0435 \u0432\u0441\u0435 \u043c\u044b, \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435, \u043f\u0438\u0441\u0430\u043b\u0438, \u043a\u043e\u0433\u0434\u0430 \u0443\u0447\u0438\u043b\u0438\u0441\u044c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c: \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0447\u0430\u0441\u0442\u043d\u043e\u0435 \u0438 \u043e\u0441\u0442\u0430\u0442\u043e\u043a \u043e\u0442 \u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043d\u0430 10, \u0442\u043e \u0435\u0441\u0442\u044c, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u0438 \u0435\u0434\u0438\u043d\u0438\u0446\u044b. \u0411\u0435\u0440\u0435\u043c \u0438\u0445 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0438\u0437 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043c\u0430\u0441\u0441\u0438\u0432\u043e\u0432. \u0423\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c, \u0447\u0442\u043e \u0434\u043b\u044f \u0447\u0438\u0441\u0435\u043b, \u043e\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u044e\u0449\u0438\u0445\u0441\u044f \u0435\u0434\u0438\u043d\u0438\u0446\u0435\u0439 (21\/31\/41\/51\/61), \u043d\u0430\u0434\u043e \u043f\u0440\u0438\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043b\u043e\u0432\u043e &#171;et&#187;:<\/p>\n<pre><code class=\"kotlin\">private fun from20to69(n: Int): String {     val d = n \/ 10       \/\/ 2..6     val r = n % 10       \/\/ 0..9     return when {         r == 0 -&gt; tens[d]         r == 1 -&gt; \"${tens[d]} et un\"         else   -&gt; \"${tens[d]}-${units[r]}\"     } }<\/code><\/pre>\n<p>\u0427\u0438\u0441\u043b\u0430 \u043e\u0442 70 \u0434\u043e 79 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f &#171;60 + x&#187;. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u044b\u0447\u0442\u0435\u043c \u0438\u0437 \u0442\u0430\u043a\u043e\u0433\u043e \u0447\u0438\u0441\u043b\u0430 60, \u0430 \u0434\u043b\u044f \u043e\u0441\u0442\u0430\u0442\u043a\u0430 \u0432\u044b\u0437\u043e\u0432\u0435\u043c \u0443\u0436\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0443\u044e \u043d\u0430\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>from0to19()<\/code>:<\/p>\n<pre><code class=\"kotlin\">private fun from70to79(n: Int): String {     val r = n - 60     return when (r) {         11        -&gt; \"soixante et onze\"         in 10..19 -&gt; \"soixante-\" + from0to19(r)         else      -&gt; error(\"Unexpected 70s remainder $r\")     } }<\/code><\/pre>\n<p>\u041e\u0442 81 \u0434\u043e 99 \u0447\u0438\u0441\u043b\u0430 \u0438\u043c\u0435\u043d\u0443\u044e\u0442\u0441\u044f \u043f\u043e \u0441\u0445\u0435\u043c\u0435 &#171;4-20-x&#187;, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u0432\u044b\u0447\u0438\u0442\u0430\u0435\u043c \u0438\u0437 \u0447\u0438\u0441\u043b\u0430 80 \u0438 \u0441\u043d\u043e\u0432\u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>from0to19()<\/code>:<\/p>\n<pre><code class=\"kotlin\">private fun from80to99(n: Int): String {     return if (n == 80) {         \"quatre-vingts\"     } else {         \"quatre-vingt-\" + from0to19(n - 80)     } }<\/code><\/pre>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u0432 \u043e\u0441\u043e\u0431\u044b\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u0434\u043b\u044f \u0447\u0438\u0441\u043b\u0430 100 (&#171;cent&#187;), \u0441\u0432\u0435\u0434\u0435\u043c \u0432\u0441\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044e:<\/p>\n<pre><code class=\"kotlin\">fun toFrench(n: Int): String {     require(n in 0..100) { \"Only 0..100 supported in this demo\" }     return when {         n &lt;= 19      -&gt; from0to19(n)         n in 20..69  -&gt; from20to69(n)         n in 70..79  -&gt; from70to79(n)         n in 80..99  -&gt; from80to99(n)         n == 100     -&gt; \"cent\"         else         -&gt; error(\"Unhandled number $n\")     } }<\/code><\/pre>\n<p>\u0412 Java \u043c\u044b \u0431\u044b \u0441\u0432\u0435\u043b\u0438 \u044d\u0442\u0443 \u0433\u0440\u0443\u043f\u043f\u0443 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0432 \u043e\u0434\u0438\u043d \u043a\u043b\u0430\u0441\u0441 \u0441 \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c <code>public static<\/code>, \u043d\u043e \u0432 Kotlin \u0432 \u0442\u0430\u043a\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u043f\u0440\u0438\u043d\u044f\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0438\u043d\u0433\u043b\u0442\u043e\u043d:<\/p>\n<pre><code class=\"kotlin\">object FrenchNumbers {      private val units = arrayOf(         \"z\u00e9ro\", \"un\", \"deux\", \"trois\", \"quatre\", \"cinq\", \"six\", \"sept\", \"huit\", \"neuf\"     )      private val tenToSixteen = arrayOf(         \"dix\", \"onze\", \"douze\", \"treize\", \"quatorze\", \"quinze\", \"seize\"     )      private val tens = arrayOf(         \"\", \"dix\", \"vingt\", \"trente\", \"quarante\", \"cinquante\", \"soixante\"     )      fun toFrench(n: Int): String { ... }      private fun from80to99(n: Int): String { ... }      private fun from20to69(n: Int): String { ... }      private fun from70to79(n: Int): String { ... }      private fun from0to19(n: Int): String = ... }<\/code><\/pre>\n<h2>SRS-\u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c<\/h2>\n<p>\u0418\u043c\u0445\u043e, \u0441\u043c\u0430\u0440\u0442\u0444\u043e\u043d \u0438 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%B5%D0%BD%D0%B8%D1%8F\" rel=\"noopener noreferrer nofollow\">SRS-\u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/a> &#8212; \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u0430\u0440\u0430. \u0427\u0435\u043c\u0443, \u043a\u0430\u043a \u043d\u0435 \u0441\u043c\u0430\u0440\u0442\u0444\u043e\u043d\u0443, \u0443\u0434\u043e\u0431\u043d\u043e \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0437\u0430 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u043e\u043c \u0441\u0432\u043e\u0435\u0433\u043e \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0430 \u0438 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0442\u044c \u043d\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u044f, \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u0440\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e?<\/p>\n<p>\u0412\u043e\u0437\u044c\u043c\u0435\u043c \u043e\u0431\u0449\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c <a href=\"https:\/\/en.wikipedia.org\/wiki\/SuperMemo#Description_of_SM-2_algorithm\" rel=\"noopener noreferrer nofollow\">SuperMemo<\/a> \u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043c \u043d\u0430 \u0435\u0433\u043e \u043e\u0441\u043d\u043e\u0432\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0439. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0447\u0438\u0441\u043b\u0430, \u0433\u0434\u0435 \u0431\u0443\u0434\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"kotlin\">data class CardSrs(     var ef: Double = 2.5,     var interval: Int = 0,     var reps: Int = 0,     var lapses: Int = 0,     var due: Long = todayEpoch() )<\/code><\/pre>\n<p>\u0433\u0434\u0435<\/p>\n<ul>\n<li>\n<p><code>ef<\/code> &#8212; \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u043b\u0435\u0433\u043a\u043e\u0441\u0442\u0438 (easy factor), \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043e\u0431\u044b\u0447\u043d\u043e \u0431\u0435\u0440\u0443\u0442 2.5;<\/p>\n<\/li>\n<li>\n<p><code>interval<\/code> &#8212; \u0447\u0438\u0441\u043b\u043e \u0434\u043d\u0435\u0439 \u0434\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u043f\u043e\u043a\u0430\u0437\u0430;<\/p>\n<\/li>\n<li>\n<p><code>reps<\/code> &#8212; \u0447\u0438\u0441\u043b\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 \u043f\u043e\u0434\u0440\u044f\u0434;<\/p>\n<\/li>\n<li>\n<p><code>lapses<\/code> &#8212; \u0447\u0438\u0441\u043b\u043e \u043f\u0440\u043e\u0432\u0430\u043b\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p><code>due<\/code> &#8212; \u0434\u0430\u0442\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u043f\u043e\u043a\u0430\u0437\u0430 (Epoch Day &#8212; \u0447\u0438\u0441\u043b\u043e \u0441\u0443\u0442\u043e\u043a \u0441 1.01.1970), \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0430\u043a:<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"kotlin\">fun todayEpoch(): Long = LocalDate.now().toEpochDay()<\/code><\/pre>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a \u0443 \u043d\u0430\u0441 \u0432\u0441\u0435\u0433\u043e \u043f\u043e 5 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f 100 \u0447\u0438\u0441\u0435\u043b, \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043c\u043e\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u0440\u044f\u043c\u043e \u0432 Preferences <a href=\"https:\/\/habr.com\/ru\/companies\/tbank\/articles\/525010\/\" rel=\"noopener noreferrer nofollow\">DataStore<\/a>, \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<pre><code class=\"kotlin\">private val Context.srsDataStore by preferencesDataStore(\"french_srs_numbers\")<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c <code>preferencesDataStore()<\/code> &#8212; \u0434\u0435\u043b\u0435\u0433\u0430\u0442 \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Jetpack DataStore, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u0435 \u043b\u0435\u043d\u0438\u0432\u043e \u0438 \u043f\u043e\u0442\u043e\u043a\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0438 \u043a\u044d\u0448\u0438\u0440\u0443\u0435\u0442 \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 <code>DataStore&lt;Preferences&gt;<\/code> \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0438\u043c\u0435\u043d\u0438 \u0444\u0430\u0439\u043b\u0430 (<code>\"french_srs_numbers\"<\/code>). \u041f\u0440\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0434\u043e\u0441\u0442\u0443\u043f\u0430\u0445 \u044d\u0442\u043e\u0442 \u0434\u0435\u043b\u0435\u0433\u0430\u0442 \u0432\u0435\u0440\u043d\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440.<\/p>\n<p><code>srsDataStore <\/code>\u043c\u044b \u043e\u0431\u044a\u044f\u0432\u0438\u043b\u0438 \u043a\u0430\u043a \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e-\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u0430 <code>Context<\/code>, \u0447\u0442\u043e\u0431\u044b \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c <code>appContext.srsDataStore<\/code>.<\/p>\n<p>\u0421\u0430\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0431\u0443\u0434\u0443\u0449\u0438\u043c\u0438 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c\u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 \u0441\u043b\u043e\u0436\u0438\u043c \u0432 \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u0441\u0438\u043d\u0433\u043b\u0442\u043e\u043d:<\/p>\n<pre><code class=\"kotlin\">object Srs {     private lateinit var appCtx: Context        private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)          private val numMap = mutableMapOf&lt;Int, CardSrs&gt;().apply {       for (n in 0..100) put(n, CardSrs()) }<\/code><\/pre>\n<p>\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0438 \u0441\u043a\u043e\u0443\u043f \u043b\u0443\u0447\u0448\u0435 \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0438\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043e \u0432\u0441\u0435\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u0445.<\/p>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0441 \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c <code>lateinit<\/code>: \u044d\u0442\u043e <a href=\"https:\/\/habr.com\/ru\/companies\/maxilect\/articles\/590061\/\" rel=\"noopener noreferrer nofollow\">\u0438\u0434\u0438\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431<\/a> \u043e\u0431\u044a\u044f\u0432\u0438\u0442\u044c non-null \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043b\u0435\u0433\u0430\u043b\u044c\u043d\u043e \u043e\u0442\u043b\u043e\u0436\u0438\u0442\u044c \u0435\u0433\u043e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0434\u043e \u0442\u0435\u0445 \u043f\u043e\u0440, \u043f\u043e\u043a\u0430 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f.<\/p>\n<p>\u0423\u0434\u043e\u0431\u043d\u043e \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0435\u0434\u0438\u043d\u044b\u0439 \u0441\u043a\u043e\u0443\u043f <code>scope<\/code> \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439. \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0435\u0433\u043e \u0441\u0442\u0440\u043e\u0438\u043c \u043d\u0430 \u0431\u0430\u0437\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430, \u0441\u043e\u0441\u0442\u043e\u044f\u0449\u0435\u0433\u043e \u0438\u0437 <code>SupervisorJob()<\/code> \u0438 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430 IO. <code>SupervisorJob (Job<\/code>-\u043d\u0430\u0434\u0437\u0438\u0440\u0430\u0442\u0435\u043b\u044c) \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439. \u041a\u0430\u0436\u0434\u0430\u044f \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430, \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u0430\u044f \u043f\u043e\u0434 \u043d\u0430\u0434\u0437\u043e\u0440\u043e\u043c\u00a0<code>SupervisorJob<\/code>, \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u0441 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043e\u0448\u0438\u0431\u043e\u043a. \u0427\u0442\u043e \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430 IO, \u0442\u043e \u043e\u043d \u0443\u0434\u043e\u0431\u0435\u043d \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u0432 \u043c\u043e\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u043e\u043c <a href=\"https:\/\/stepik.org\/a\/205482\" rel=\"noopener noreferrer nofollow\">\u043a\u0443\u0440\u0441\u0435 \u043f\u043e \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430\u043c<\/a>.<\/p>\n<p>\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u043e <code>numMap<\/code> \u0445\u0440\u0430\u043d\u0438\u0442 \u0447\u0438\u0441\u043b\u0430 \u0438 \u0438\u0445 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0432 \u0432\u0438\u0434\u0435 MutableMap.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0438\u0437 DataStore, \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a \u043d\u0435\u043c\u0443 \u0447\u0435\u0440\u0435\u0437 \u043d\u0430\u0448\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 <code>srsDataStore<\/code>:<\/p>\n<pre><code class=\"kotlin\">    suspend fun init(context: Context) {         appCtx = context.applicationContext         val prefs = appCtx.srsDataStore.data.first()         for (n in 0..100) {             val p = \"n$n\"             val ef = prefs[doublePreferencesKey(\"$p.ef\")] ?: 2.5             val interval = prefs[intPreferencesKey(\"$p.interval\")] ?: 0             val reps = prefs[intPreferencesKey(\"$p.reps\")] ?: 0             val lapses = prefs[intPreferencesKey(\"$p.lapses\")] ?: 0             val due = prefs[longPreferencesKey(\"$p.due\")] ?: todayEpoch()             numMap[n] = CardSrs(ef, interval, reps, lapses, due)         }     }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u043c\u044b \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0435\u0435 \u043f\u043e\u043a\u0430\u0437\u0430. \u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0435\u0435 \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043b, \u0442\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u0432 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 &#8212; \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u0434\u043e \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0445:<\/p>\n<pre><code class=\"kotlin\">    fun updateNumber(n: Int, ok: Boolean) {         val st = numMap.getValue(n)         if (!ok) {             st.reps = 0             st.interval = 1             st.due = todayEpoch() + 1             st.lapses += 1         } else {             if (st.reps == 0) st.interval = 1             else if (st.reps == 1) st.interval = 6             else st.interval = round(st.interval * st.ef).toInt()             st.ef = maxOf(1.3, st.ef + 0.1)             st.reps += 1             st.due = todayEpoch() + st.interval         }         saveNumberAsync(n, st)     }<\/code><\/pre>\n<p>\u041d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b I<sup>t+1<\/sup> \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0447\u0438\u0441\u043b\u0430 \u0438\u0434\u0443\u0449\u0438\u0445 \u043f\u043e\u0434\u0440\u044f\u0434 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 r<sub>t<\/sub>:<\/p>\n<p>EF (ease factor) \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0443\u0441\u043f\u0435\u0445\u0430 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 0.1. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0434\u043b\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f EF \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u043e\u0433, \u0440\u0430\u0432\u043d\u044b\u0439 1.3.<\/p>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c, \u043a\u0430\u043a \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442 suspend-\u043c\u0435\u0442\u043e\u0434 <code>DataStore.edit()<\/code>. \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0443 \u0438\u0437 \u0440\u0430\u043d\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043a\u043e\u0443\u043f\u0430 \u0438 \u0432 \u043d\u0435\u0439 \u0432\u044b\u0437\u043e\u0432\u0435\u043c <code>edit()<\/code>:<\/p>\n<pre><code class=\"kotlin\">    private fun saveNumberAsync(n: Int, st: CardSrs) {         if (!this::appCtx.isInitialized) return         val p = \"n$n\"         scope.launch {             appCtx.srsDataStore.edit { e -&gt;                 e[doublePreferencesKey(\"$p.ef\")] = st.ef                 e[intPreferencesKey(\"$p.interval\")] = st.interval                 e[intPreferencesKey(\"$p.reps\")] = st.reps                 e[intPreferencesKey(\"$p.lapses\")] = st.lapses                 e[longPreferencesKey(\"$p.due\")] = st.due             }         }     }<\/code><\/pre>\n<p>\u0412\u043f\u0440\u043e\u0447\u0435\u043c, \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u043b\u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"kotlin\">fun isReady(): Boolean = this::appCtx.isInitialized<\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0447\u0438\u0441\u0435\u043b, \u0441\u043e\u0437\u0440\u0435\u0432\u0448\u0438\u0445 \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0439, \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0432\u0441\u0435 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u043f\u043e \u0434\u0430\u0442\u0435 <code>due <\/code>\u0438 \u0431\u0435\u0440\u0435\u043c \u043f\u0435\u0440\u0432\u044b\u0435 <code>limit <\/code>\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0430\u0442\u044b:<\/p>\n<pre><code class=\"kotlin\">    @RequiresApi(Build.VERSION_CODES.O)     fun topDueNumbers(limit: Int = 5): List&lt;Int&gt; =         numMap.entries             .sortedBy { it.value.due }             .filter { it.value.due &lt;= todayEpoch() }             .map { it.key }             .take(limit)<\/code><\/pre>\n<p>\u0412\u043e\u0442 \u043c\u044b \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u043b\u043e\u0433\u0438\u043a\u0443 \u043d\u0430\u0448\u0435\u0439 SRS-\u0441\u0438\u0441\u0442\u0435\u043c\u044b.<\/p>\n<h2>\u0423\u0434\u0430\u0440\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c<\/h2>\n<p>\u0427\u0442\u043e \u0442\u0430\u043a \u043f\u0440\u0438\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043d\u0430\u0441 \u0432 \u0414\u0443\u043e\u043b\u0438\u043d\u0433\u043e? \u041a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0443\u0434\u0430\u0440\u043d\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430 aka \u0441\u0442\u0440\u0438\u043a!<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0437\u0430\u0438\u043c\u0441\u0442\u0432\u0443\u0435\u043c \u044d\u0442\u0443 \u0438\u0434\u0435\u044e.<\/p>\n<pre><code class=\"kotlin\">data class StreakSnapshot(     val current: Int,     val best: Int,     val week: List&lt;Boolean&gt;,     val todayActive: Boolean )<\/code><\/pre>\n<p>\u042d\u0442\u043e \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u044b\u0439 \u0441\u043d\u0438\u043c\u043e\u043a<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-471912","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/471912","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=471912"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/471912\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=471912"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=471912"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=471912"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}