mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Improve devices menu
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
package ru.fromchat.platform
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
private fun String?.trimIfNotBlank(): String? =
|
||||||
|
this?.trim()?.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
|
actual fun currentDeviceInfo(): CurrentDeviceInfo {
|
||||||
|
val osName = "Android"
|
||||||
|
val osVersion = Build.VERSION.RELEASE.trimIfNotBlank() ?: Build.VERSION.SDK_INT.toString()
|
||||||
|
val brand = Build.BRAND.trimIfNotBlank() ?: Build.MANUFACTURER.trimIfNotBlank()
|
||||||
|
val model = Build.MODEL.trimIfNotBlank()
|
||||||
|
|
||||||
|
return CurrentDeviceInfo(
|
||||||
|
osName = osName,
|
||||||
|
osVersion = osVersion,
|
||||||
|
deviceType = "mobile",
|
||||||
|
deviceName = model?.let { if (it.contains(brand.orEmpty(), ignoreCase = true)) it else listOfNotNull(brand, it).joinToString(" ") },
|
||||||
|
brand = brand,
|
||||||
|
model = model
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="256" height="256" viewBox="0 0 256 256">
|
||||||
|
<line x1="1.407" y1="1.353" x2="1.407" y2="1.46" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0, 0, 0); fill-rule: nonzero; opacity: 1;"/>
|
||||||
|
<g transform="matrix(1.329817, 0, 0, 1.329817, -42.024433, -38.371166)" style="">
|
||||||
|
<g transform="matrix(1, 0, 0, 1, -0.000001, 0.000008)">
|
||||||
|
<path d="M 187.533 167.601 C 184.614 169.278 180.394 175.015 176.028 176.522 L 173.703 176.879 C 173.692 176.878 173.682 176.878 173.671 176.877 C 170.911 176.954 167.048 175.793 164.888 173.621 C 164.62 173.351 164.378 173.064 164.167 172.763 C 163.081 170.76 162.591 168.959 162.078 167.519 C 163.681 138.029 142.006 97.682 141.345 89.031 L 140.535 89.081 L 143.379 87.758 C 146.633 86.246 146.577 81.601 143.289 80.165 L 126.072 72.64 C 124.802 72.084 123.338 72.196 122.169 72.941 L 109.131 81.222 C 106.644 82.801 106.537 86.395 108.928 88.118 L 120.16 96.21 C 121.382 97.093 122.987 97.244 124.353 96.607 L 131.993 93.054 L 124.35 96.609 C 122.984 97.244 121.38 97.093 120.157 96.213 L 112.629 90.787 L 112.182 90.815 C 112.182 96.766 92.754 126.354 85.61 153.184 C 82.2 144.438 85.83 134.593 92.372 122.051 C 83.667 131.338 75.779 146.462 83.605 158.699 C 87.499 164.679 93.454 168.284 99.138 171.235 C 103.255 173.904 113.854 176.441 110.918 182.885 C 110.925 182.876 110.931 182.867 110.938 182.859 C 110.931 182.869 110.925 182.88 110.918 182.89 C 108.679 187.681 104.866 190.521 100.012 192.077 C 98.585 189.825 96.894 187.456 94.937 184.967 L 90.627 179.198 C 89.739 177.993 88.66 176.155 87.266 173.682 C 85.934 171.212 84.729 169.31 83.779 167.978 C 82.956 166.519 81.624 165.061 79.912 163.603 C 79.401 163.15 78.861 162.743 78.299 162.386 C 75.967 160.899 72.963 160.587 70.538 161.919 C 69.819 162.313 69.262 162.771 68.883 163.288 C 67.998 164.493 67.489 165.825 67.363 167.219 C 67.172 168.551 66.792 169.439 66.157 169.883 C 66.152 169.886 66.141 169.891 66.135 169.894 C 65.64 163.51 67.408 157.038 70.867 151.489 C 73.595 147.111 75.664 142.699 76.428 138.228 C 76.779 130.993 78.71 123.934 82.113 117.038 C 86.159 107.861 95.837 101.976 98.644 92.659 C 102.474 80.66 83.964 28.579 126.067 28.984 C 150.53 28.984 159.32 50.152 159.32 84.644 C 159.32 107.391 201.316 122.18 187.533 167.601 Z M 151.486 211.429 C 151.801 212.025 152.146 212.581 152.52 213.101 C 136.094 207.361 120.468 206.548 104.498 212.435 C 105.326 211.043 105.778 209.435 105.778 207.781 C 105.778 207.779 105.778 207.778 105.778 207.776 C 105.778 207.761 105.778 207.746 105.778 207.731 C 105.778 207.729 105.778 207.727 105.778 207.725 C 105.778 204.739 104.88 201.359 103.085 197.582 C 107.83 198.93 113.536 199.677 120.393 199.677 C 136.171 200.012 146.507 196.345 152.958 190.163 C 151.666 200.295 149.855 207.938 151.486 211.429 Z M 168.963 155.718 C 167.694 156.465 166.457 157.278 165.336 158.247 C 164.063 159.306 162.925 160.573 162.122 161.99 C 164.732 160.051 167.618 158.932 170.555 157.952 L 170.855 157.851 C 172.058 156.221 173.112 154.49 173.983 152.649 C 179.496 140.288 172.1 126.454 162.265 118.749 C 168.908 130.233 175.535 143.047 168.963 155.718 Z M 110.325 80.46 L 121.571 73.32 C 121.829 72.168 121.972 70.94 121.972 69.664 C 121.972 63.111 118.283 57.8 113.731 57.8 C 109.179 57.8 105.489 63.111 105.489 69.664 C 105.489 74.469 107.476 78.594 110.325 80.46 Z M 140.237 78.831 C 142.075 76.656 143.244 73.357 143.244 69.664 C 143.244 63.111 139.555 57.8 135.002 57.8 C 130.45 57.8 126.761 63.111 126.761 69.664 C 126.761 70.859 126.887 72.008 127.115 73.095 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
<path d="M 97.708 188.673 C 97.691 188.648 97.674 188.626 97.658 188.6 C 97.677 188.626 97.691 188.651 97.708 188.673 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
<path d="M 178.569 117.583 L 162.265 118.749 C 173.339 131.799 177.532 144.523 169.998 156.693 C 166.446 158.264 163.822 160.028 162.122 161.987 C 162.189 163.875 162.178 165.735 162.08 167.551 C 161.65 166.34 161.203 165.379 160.377 164.766 C 160.374 164.774 160.368 164.786 160.363 164.794 C 161.832 165.882 162.13 168.073 163.274 170.855 C 163.316 170.956 163.353 171.052 163.397 171.156 C 163.619 171.676 163.867 172.21 164.167 172.763 C 166.07 175.489 170.571 176.97 173.676 176.883 C 174.039 176.905 174.404 176.88 174.767 176.829 C 174.882 176.812 175 176.781 175.115 176.759 C 175.365 176.708 175.615 176.649 175.863 176.571 C 175.995 176.529 176.124 176.481 176.256 176.43 C 176.495 176.34 176.734 176.236 176.973 176.124 C 177.099 176.065 177.223 176.006 177.349 175.941 C 177.616 175.801 177.883 175.646 178.147 175.48 C 178.237 175.424 178.327 175.376 178.414 175.317 C 178.768 175.087 179.122 174.84 179.471 174.576 C 179.532 174.531 179.591 174.48 179.651 174.432 C 179.943 174.208 180.229 173.977 180.516 173.738 C 180.628 173.646 180.738 173.55 180.848 173.454 C 181.086 173.249 181.325 173.041 181.558 172.831 C 181.668 172.732 181.778 172.631 181.887 172.533 C 182.14 172.302 182.39 172.069 182.638 171.836 C 182.713 171.766 182.789 171.693 182.865 171.622 C 183.202 171.302 183.537 170.987 183.863 170.675 C 184.141 170.411 184.413 170.153 184.68 169.903 C 184.773 169.818 184.86 169.737 184.95 169.655 C 185.155 169.467 185.358 169.282 185.557 169.107 C 185.658 169.017 185.759 168.933 185.861 168.849 C 186.043 168.694 186.223 168.545 186.397 168.408 C 186.496 168.329 186.594 168.253 186.69 168.18 C 186.869 168.045 187.046 167.924 187.218 167.812 C 187.297 167.761 187.378 167.705 187.454 167.66 C 187.482 167.643 187.513 167.621 187.538 167.604 C 194.434 144.871 187.358 129.81 178.569 117.583 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
<path d="M 97.708 188.676 C 97.708 188.676 97.708 188.676 97.708 188.676 C 97.711 188.679 97.711 188.682 97.714 188.685 C 97.714 188.685 97.714 188.685 97.714 188.685 C 98.54 189.845 99.296 190.975 99.995 192.079 C 104.857 190.525 108.676 187.684 110.918 182.888 C 119.916 168.72 59.028 169.675 92.372 122.051 L 80.221 121.306 C 79.123 124.043 78.257 126.806 77.622 129.593 C 77.611 129.638 77.6 129.683 77.589 129.728 C 77.299 131.021 77.069 132.322 76.883 133.628 C 76.849 133.864 76.821 134.103 76.79 134.339 C 76.625 135.629 76.493 136.924 76.431 138.228 C 75.666 142.699 73.598 147.111 70.87 151.489 C 70.46 152.149 70.075 152.823 69.715 153.506 C 69.414 154.08 69.136 154.661 68.872 155.248 C 68.827 155.344 68.776 155.44 68.734 155.535 C 68.127 156.915 67.616 158.328 67.208 159.767 C 66.273 163.066 65.876 166.488 66.141 169.894 C 66.146 169.891 66.157 169.886 66.163 169.883 C 66.798 169.439 67.177 168.551 67.369 167.219 C 67.495 165.825 68.004 164.493 68.889 163.288 C 69.268 162.771 69.824 162.313 70.544 161.919 C 72.969 160.587 75.973 160.899 78.305 162.386 C 78.867 162.743 79.407 163.15 79.918 163.603 C 81.629 165.061 82.961 166.519 83.785 167.978 C 84.734 169.31 85.94 171.212 87.272 173.682 C 88.666 176.155 89.745 177.993 90.633 179.198 L 94.943 184.967 C 95.688 185.914 96.373 186.836 97.037 187.746 C 97.261 188.058 97.492 188.37 97.708 188.676 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
<path d="M 122.169 72.941 C 118.834 75.093 115.509 77.26 112.241 79.513 L 109.8 81.219 C 107.29 82.543 107.108 86.283 109.592 87.601 C 110.213 88.014 120.685 94.772 120.823 94.921 C 121.686 95.556 122.672 95.629 123.597 95.235 C 123.597 95.235 128.913 92.698 128.913 92.698 C 132.521 91.046 136.14 89.416 139.799 87.879 C 140.951 87.263 143.674 86.679 144.295 85.44 C 145.102 84.229 144.885 82.292 143.618 81.301 C 140.94 79.665 136.031 77.493 133.153 75.984 C 133.153 75.984 127.767 73.449 127.767 73.449 C 126.033 72.567 123.979 71.716 122.169 72.941 Z M 122.169 72.941 C 123.948 71.676 126.072 72.486 127.817 73.337 C 127.817 73.337 133.339 75.568 133.339 75.568 C 136.525 76.889 141.575 78.485 144.585 79.985 C 147.94 82.014 147.237 87.637 143.626 89.039 C 139.26 91.341 134.738 93.535 130.282 95.634 C 127.727 96.584 123.675 99.481 120.826 98.191 C 118.682 97.396 110.668 90.321 108.647 88.91 C 105.613 86.881 106.132 81.958 109.417 80.618 C 109.417 80.618 112 79.134 112 79.134 C 115.428 77.13 118.803 75.043 122.169 72.941 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
<path d="M 137.309 83.658 C 129.815 90.793 116.833 92.487 108.451 85.83 C 118.092 89.511 128.081 88.126 137.309 83.658 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
<path d="M 135.177 76.619 C 136.199 75.787 136.995 74.354 137.234 72.634 C 137.638 69.737 136.337 67.158 134.322 66.877 C 132.31 66.596 130.349 68.717 129.942 71.614 C 129.801 72.612 129.877 73.562 130.102 74.402 L 135.177 76.619 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
<path d="M 115.226 77.35 L 118.477 75.284 C 118.94 74.267 119.14 72.98 118.949 71.612 C 118.544 68.715 116.583 66.593 114.568 66.874 C 112.556 67.155 111.252 69.735 111.657 72.632 C 112.008 75.138 113.523 77.055 115.226 77.35 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
<path d="M 59.565 208.06 C 67.843 208.624 75.959 211.078 83.813 213.494 C 87.536 214.444 91.254 216.223 95.12 215.916 C 110.114 213.418 101.209 197.528 95.48 190.025 C 91.948 185.386 87.606 179.844 84.661 174.59 C 82.773 171.676 80.913 167.472 77.864 165.353 C 76.043 163.476 71.974 162.855 70.552 165.308 C 69.26 167.528 70.055 170.085 67.534 171.853 C 66.008 172.746 64.775 172.898 63.246 173.241 C 63.03 173.227 61.69 173.283 61.453 173.275 C 58.157 173.502 53.65 172.238 52.563 176.27 C 51.548 180.165 53.532 184.234 53.389 188.345 C 53.512 192.045 51.677 195.729 49.702 198.539 C 48.876 199.939 48.05 201.462 47.923 202.836 C 47.923 202.836 47.895 202.625 47.895 202.625 C 48.39 204.522 51 205.533 52.799 206.213 C 54.976 206.989 57.264 207.565 59.565 208.043 C 57.219 207.877 54.858 207.683 52.526 207.127 C 50.104 206.433 47.33 205.812 46.257 203.049 C 46.176 198.978 48.98 195.946 49.725 192.984 C 51.425 187.937 48.719 183.45 48.387 178.296 C 48.379 169.56 54.704 168.554 61.099 168.652 C 61.099 168.652 62.988 168.554 62.988 168.554 L 62.49 168.607 C 63.215 168.411 64.415 168.155 64.924 167.826 C 64.362 168.315 64.946 167.716 64.949 166.739 C 65.267 162.259 69.116 158.446 73.638 158.494 C 76.42 158.37 79.069 159.514 81.166 161.259 C 84.762 163.752 87.005 168.211 89.362 171.945 C 94.294 181.041 102.184 188.401 106.051 198.258 C 110.586 208.352 107.428 218.445 95.457 219.623 C 91.046 219.825 86.937 217.515 82.888 216.127 C 75.248 212.887 67.68 209.875 59.565 208.06 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
<path d="M 173.676 176.883 C 170.04 177.085 166.101 176.093 163.628 173.137 C 162.616 171.504 161.905 169.658 161.242 167.975 C 160.756 166.418 159.579 165.201 158.46 165.297 C 154.203 166.106 156.179 175.222 155.81 178.734 C 155.754 184.413 155.22 190.07 154.523 195.628 C 154.133 200.068 152.424 206.837 153.947 210.561 C 155.895 214.048 159.787 216.161 163.743 215.908 C 166.519 215.635 168.883 213.823 171.541 212.008 C 178.285 206.981 184.38 201.231 191.84 196.904 C 194.948 195.108 198.368 193.566 201.861 192.501 C 203.075 192.231 204.952 190.952 205.103 190.143 C 205.053 189.705 204.471 189.193 204.145 188.879 C 201.195 186.423 195.49 184.627 193.473 182.365 C 190.983 180.314 189.73 176.486 189.986 173.477 C 189.927 170.948 189.772 167.112 188.134 168.394 C 183.601 170.858 179.6 177.209 173.676 176.883 C 176.63 176.759 178.805 174.629 180.834 172.746 C 183.436 170.431 189.337 161.785 191.947 168.818 C 193.237 172.586 192.152 177.414 195.718 179.766 C 196.949 180.974 200.987 182.446 202.777 183.326 C 209.203 185.979 212.499 191.309 205.674 195.839 C 201.861 197.685 197.972 198.767 194.414 201.184 C 187.519 205.522 181.325 211.283 174.736 216.332 C 171.822 218.443 168.059 221.036 163.892 221.219 C 157.221 221.539 149.907 217.024 148.541 210.28 C 147.889 205.092 149.28 199.972 149.974 194.959 C 150.904 189.491 151.747 184.065 152.079 178.594 C 152.748 173.682 151.132 163.049 158.578 162.858 C 163.322 163.372 162.987 169.245 164.743 172.446 C 166.393 174.955 170.386 176.7 173.676 176.883 Z" style="stroke: none; stroke-width: 2.81; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(255, 255, 255);" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="200px" height="200px" viewBox="0 0 200 200">
|
||||||
|
<path d="M 176.226 68.183 C 175.066 69.083 154.581 80.625 154.581 106.291 C 154.581 135.977 180.646 146.48 181.427 146.739 C 181.307 147.38 177.286 161.122 167.684 175.125 C 159.122 187.448 150.18 199.75 136.577 199.75 C 122.974 199.75 119.474 191.849 103.771 191.849 C 88.467 191.849 83.027 200.011 70.583 200.011 C 58.141 200.011 49.46 188.608 39.478 174.605 C 27.915 158.162 18.573 132.617 18.573 108.372 C 18.573 69.484 43.858 48.859 68.744 48.859 C 81.966 48.859 92.988 57.541 101.29 57.541 C 109.191 57.541 121.514 48.339 136.557 48.339 C 142.259 48.339 162.743 48.859 176.226 68.183 Z M 129.416 31.876 C 135.637 24.494 140.038 14.252 140.038 4.01 C 140.038 2.59 139.918 1.149 139.658 -0.011 C 129.536 0.369 117.493 6.73 110.232 15.152 C 104.531 21.634 99.209 31.876 99.209 42.257 C 99.209 43.818 99.47 45.379 99.59 45.879 C 100.23 45.998 101.27 46.139 102.31 46.139 C 111.393 46.139 122.815 40.057 129.416 31.876 Z" style="fill: rgb(255, 255, 255);" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="0" y="0" width="95" height="95" fill="#ffffff"/>
|
||||||
|
<rect x="105" y="0" width="95" height="95" fill="#ffffff"/>
|
||||||
|
<rect x="0" y="105" width="95" height="95" fill="#ffffff"/>
|
||||||
|
<rect x="105" y="105" width="95" height="95" fill="#ffffff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 318 B |
@@ -12,6 +12,7 @@ import io.ktor.client.plugins.logging.LogLevel
|
|||||||
import io.ktor.client.plugins.logging.Logger
|
import io.ktor.client.plugins.logging.Logger
|
||||||
import io.ktor.client.plugins.logging.Logging
|
import io.ktor.client.plugins.logging.Logging
|
||||||
import io.ktor.client.plugins.logging.SIMPLE
|
import io.ktor.client.plugins.logging.SIMPLE
|
||||||
|
import io.ktor.client.request.header
|
||||||
import io.ktor.client.plugins.websocket.WebSockets
|
import io.ktor.client.plugins.websocket.WebSockets
|
||||||
import io.ktor.client.plugins.websocket.pingInterval
|
import io.ktor.client.plugins.websocket.pingInterval
|
||||||
import io.ktor.client.request.bearerAuth
|
import io.ktor.client.request.bearerAuth
|
||||||
@@ -39,6 +40,7 @@ import com.pr0gramm3r101.utils.crypto.Base64
|
|||||||
import ru.fromchat.crypto.IdentityKeyManager
|
import ru.fromchat.crypto.IdentityKeyManager
|
||||||
import ru.fromchat.crypto.transport.TransportCiphertext
|
import ru.fromchat.crypto.transport.TransportCiphertext
|
||||||
import ru.fromchat.crypto.transport.TransportCrypto
|
import ru.fromchat.crypto.transport.TransportCrypto
|
||||||
|
import ru.fromchat.platform.currentDeviceInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a platform-specific HTTP client that supports WebSockets
|
* Creates a platform-specific HTTP client that supports WebSockets
|
||||||
@@ -69,10 +71,23 @@ object ApiClient {
|
|||||||
pingInterval = 5000.milliseconds
|
pingInterval = 5000.milliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val currentDevice = currentDeviceInfo()
|
||||||
|
val osName = currentDevice.osName?.takeIfNotBlank()
|
||||||
|
val osVersion = currentDevice.osVersion?.takeIfNotBlank()
|
||||||
|
val brand = currentDevice.brand?.takeIfNotBlank()
|
||||||
|
val model = currentDevice.model?.takeIfNotBlank()
|
||||||
|
val userAgent = buildLoginUserAgent(
|
||||||
|
osName = osName,
|
||||||
|
osVersion = osVersion,
|
||||||
|
model = model,
|
||||||
|
brand = brand
|
||||||
|
)
|
||||||
|
|
||||||
defaultRequest {
|
defaultRequest {
|
||||||
token?.let { authToken ->
|
token?.let { authToken ->
|
||||||
bearerAuth(authToken)
|
bearerAuth(authToken)
|
||||||
}
|
}
|
||||||
|
header("User-Agent", userAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle HTTP errors and auth errors globally
|
// Handle HTTP errors and auth errors globally
|
||||||
@@ -98,6 +113,36 @@ object ApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String?.takeIfNotBlank(): String? =
|
||||||
|
this?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
|
||||||
|
private fun formatIosVersion(version: String): String =
|
||||||
|
version
|
||||||
|
.trim()
|
||||||
|
.replace("-", "_")
|
||||||
|
.replace(".", "_")
|
||||||
|
.trim('_')
|
||||||
|
|
||||||
|
private fun buildLoginUserAgent(
|
||||||
|
osName: String?,
|
||||||
|
osVersion: String?,
|
||||||
|
model: String?,
|
||||||
|
brand: String?
|
||||||
|
): String {
|
||||||
|
val safeModel = model?.ifBlank { null } ?: brand?.ifBlank { null } ?: "device"
|
||||||
|
return when {
|
||||||
|
osName?.contains("ios", ignoreCase = true) == true -> {
|
||||||
|
val version = osVersion?.let(::formatIosVersion)?.ifBlank { "17_0" } ?: "17_0"
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS $version like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
|
||||||
|
}
|
||||||
|
osName?.contains("android", ignoreCase = true) == true -> {
|
||||||
|
val version = osVersion?.ifBlank { "10" } ?: "10"
|
||||||
|
"FromChat/1.0 (Linux; Android $version; $safeModel)"
|
||||||
|
}
|
||||||
|
else -> "FromChat/1.0 ($safeModel)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
var token: String? = null
|
var token: String? = null
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.fromchat.platform
|
||||||
|
|
||||||
|
data class CurrentDeviceInfo(
|
||||||
|
val osName: String? = null,
|
||||||
|
val osVersion: String? = null,
|
||||||
|
val deviceType: String? = null,
|
||||||
|
val deviceName: String? = null,
|
||||||
|
val brand: String? = null,
|
||||||
|
val model: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
expect fun currentDeviceInfo(): CurrentDeviceInfo
|
||||||
@@ -106,11 +106,13 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
@@ -148,6 +150,7 @@ import ru.fromchat.materialYou_d
|
|||||||
import ru.fromchat.password_length_error
|
import ru.fromchat.password_length_error
|
||||||
import ru.fromchat.passwords_dont_match
|
import ru.fromchat.passwords_dont_match
|
||||||
import ru.fromchat.platform.openAppNotificationSettings
|
import ru.fromchat.platform.openAppNotificationSettings
|
||||||
|
import ru.fromchat.platform.currentDeviceInfo
|
||||||
import ru.fromchat.settings_account_delete
|
import ru.fromchat.settings_account_delete
|
||||||
import ru.fromchat.settings_account_delete_confirm_body
|
import ru.fromchat.settings_account_delete_confirm_body
|
||||||
import ru.fromchat.settings_account_delete_confirm_title
|
import ru.fromchat.settings_account_delete_confirm_title
|
||||||
@@ -212,6 +215,7 @@ import ru.fromchat.theme
|
|||||||
import ru.fromchat.ui.Theme
|
import ru.fromchat.ui.Theme
|
||||||
import ru.fromchat.ui.dynamicThemeEnabled
|
import ru.fromchat.ui.dynamicThemeEnabled
|
||||||
import ru.fromchat.ui.theme
|
import ru.fromchat.ui.theme
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SettingsListLeadingIcon(imageVector: ImageVector) {
|
private fun SettingsListLeadingIcon(imageVector: ImageVector) {
|
||||||
@@ -585,15 +589,99 @@ fun SettingsNotificationsScreen(onBack: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun formatDeviceLine(d: DeviceSessionInfo, fallbackLabel: String): String {
|
private fun formatDeviceLine(d: DeviceSessionInfo, fallbackLabel: String): String {
|
||||||
val parts = listOfNotNull(d.deviceName, d.osName, d.browserName).filter { it.isNotBlank() }
|
val parts = listOfNotNull(
|
||||||
|
d.deviceName,
|
||||||
|
d.brand,
|
||||||
|
d.model,
|
||||||
|
deviceSessionOsLine(d),
|
||||||
|
d.browserName,
|
||||||
|
d.deviceType
|
||||||
|
).mapNotNull { it.trim().takeIf { it.isNotBlank() } }
|
||||||
|
.distinct()
|
||||||
return if (parts.isNotEmpty()) parts.joinToString(" • ") else (d.deviceType?.takeIf { it.isNotBlank() } ?: fallbackLabel)
|
return if (parts.isNotEmpty()) parts.joinToString(" • ") else (d.deviceType?.takeIf { it.isNotBlank() } ?: fallbackLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun deviceSessionForCurrentDevice(d: DeviceSessionInfo): DeviceSessionInfo {
|
||||||
|
if (!d.current) return d
|
||||||
|
val current = currentDeviceInfo()
|
||||||
|
|
||||||
|
val localOsName = current.osName?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val localOsVersion = current.osVersion?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val localDeviceType = current.deviceType?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val localDeviceName = current.deviceName?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val localBrand = current.brand?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val localModel = current.model?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val remoteDeviceName = d.deviceName?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val remoteBrand = d.brand?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val remoteModel = d.model?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
|
||||||
|
return d.copy(
|
||||||
|
osName = localOsName ?: d.osName,
|
||||||
|
osVersion = localOsVersion ?: d.osVersion,
|
||||||
|
deviceType = localDeviceType ?: d.deviceType,
|
||||||
|
deviceName = remoteDeviceName ?: localDeviceName ?: d.deviceName,
|
||||||
|
brand = remoteBrand ?: localBrand ?: d.brand,
|
||||||
|
model = remoteModel ?: localModel ?: d.model
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun formatDeviceLastSeen(iso: String?): String {
|
private fun formatDeviceLastSeen(iso: String?): String {
|
||||||
if (iso.isNullOrBlank()) return "—"
|
if (iso.isNullOrBlank()) return "—"
|
||||||
return iso.replace("T", " ").take(19)
|
return iso.replace("T", " ").take(19)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resolveDeviceOsName(d: DeviceSessionInfo): String? {
|
||||||
|
val direct = normalizeDeviceOsName(d.osName)
|
||||||
|
if (direct != null) return direct
|
||||||
|
return normalizeDeviceOsName(inferDeviceOsFromSession(d))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizeDeviceOsName(raw: String?): String? {
|
||||||
|
val trimmed = raw?.trim() ?: return null
|
||||||
|
if (trimmed.isBlank()) return null
|
||||||
|
val lower = trimmed.lowercase()
|
||||||
|
return when {
|
||||||
|
"windows" in lower || "win" in lower -> "Windows"
|
||||||
|
"mac" in lower || "os x" in lower || "darwin" in lower -> "macOS"
|
||||||
|
"linux" in lower -> "Linux"
|
||||||
|
"android" in lower -> "Android"
|
||||||
|
"ios" in lower || "iphone" in lower || "ipad" in lower -> "iOS"
|
||||||
|
else -> trimmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inferDeviceOsFromSession(d: DeviceSessionInfo): String? {
|
||||||
|
val type = d.deviceType?.lowercase().orEmpty()
|
||||||
|
val name = d.deviceName?.lowercase().orEmpty()
|
||||||
|
val brand = d.brand?.lowercase().orEmpty()
|
||||||
|
val model = d.model?.lowercase().orEmpty()
|
||||||
|
val browser = d.browserName?.lowercase().orEmpty()
|
||||||
|
val isMobile = "mobile" in type || "tablet" in type || "phone" in type
|
||||||
|
return when {
|
||||||
|
isMobile && (brand.contains("apple") || name.contains("iphone") || name.contains("ipad") || model.contains("iphone") || model.contains("ipad") || browser.contains("ios")) -> "iOS"
|
||||||
|
isMobile && (brand.contains("android") || name.contains("android") || model.contains("android") || browser.contains("android")) -> "Android"
|
||||||
|
isMobile && (brand.contains("samsung") || brand.contains("xiaomi") || brand.contains("huawei") || brand.contains("oppo") || brand.contains("vivo") || brand.contains("pixel") || brand.contains("oneplus") || brand.contains("google") || brand.contains("lg") || brand.contains("motorola") || brand.contains("honor") || brand.contains("realme")) -> "Android"
|
||||||
|
"android" in browser || "android" in brand || "android" in model -> "Android"
|
||||||
|
brand.contains("apple") || model.contains("iphone") || model.contains("ipad") || browser.contains("ios") || browser.contains("iphone") || browser.contains("ipad") -> "iOS"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deviceSessionOsLine(d: DeviceSessionInfo): String? {
|
||||||
|
val os = resolveDeviceOsName(d) ?: return null
|
||||||
|
val version = d.osVersion?.trim().orEmpty()
|
||||||
|
return if (version.isBlank()) os else "$os $version"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deviceSessionLogoResource(d: DeviceSessionInfo): String? {
|
||||||
|
return when (resolveDeviceOsName(d)?.lowercase()) {
|
||||||
|
"windows", "windows nt" -> "drawable/os_windows.svg"
|
||||||
|
"mac", "mac os", "macos" -> "drawable/os_macos.svg"
|
||||||
|
"linux" -> "drawable/os_linux.svg"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun deviceHeadline(d: DeviceSessionInfo, fallback: String): String {
|
private fun deviceHeadline(d: DeviceSessionInfo, fallback: String): String {
|
||||||
d.deviceName?.trim()?.takeIf { it.isNotEmpty() }?.let { return it }
|
d.deviceName?.trim()?.takeIf { it.isNotEmpty() }?.let { return it }
|
||||||
val brandModel = listOfNotNull(d.brand?.trim(), d.model?.trim())
|
val brandModel = listOfNotNull(d.brand?.trim(), d.model?.trim())
|
||||||
@@ -604,16 +692,25 @@ private fun deviceHeadline(d: DeviceSessionInfo, fallback: String): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun deviceDetailLine(d: DeviceSessionInfo): String? {
|
private fun deviceDetailLine(d: DeviceSessionInfo): String? {
|
||||||
|
val deviceType = d.deviceType?.trim()?.takeIf { it.isNotBlank() && !it.equals("other", ignoreCase = true) }
|
||||||
|
val brand = d.brand?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val model = d.model?.trim()?.takeIf { it.isNotBlank() }
|
||||||
|
val browser = d.browserName?.trim()
|
||||||
|
?.takeIf { it.isNotBlank() && !it.equals("other", ignoreCase = true) && !it.equals("desktop", ignoreCase = true) }
|
||||||
|
|
||||||
val parts = buildList {
|
val parts = buildList {
|
||||||
d.osName?.takeIf { it.isNotBlank() }?.let { add(it.trim()) }
|
deviceSessionOsLine(d)?.let { add(it.trim()) }
|
||||||
d.browserName?.takeIf { it.isNotBlank() }?.let { add(it.trim()) }
|
brand?.let { add(it) }
|
||||||
|
model?.let { add(it) }
|
||||||
|
deviceType?.let { add(it) }
|
||||||
|
browser?.let { add(it) }
|
||||||
}
|
}
|
||||||
return if (parts.isNotEmpty()) parts.joinToString(" · ") else null
|
return if (parts.isNotEmpty()) parts.joinToString(" • ") else null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deviceSessionIcon(d: DeviceSessionInfo): ImageVector {
|
private fun deviceSessionIcon(d: DeviceSessionInfo): ImageVector {
|
||||||
val type = d.deviceType?.lowercase().orEmpty()
|
val type = d.deviceType?.lowercase().orEmpty()
|
||||||
val os = d.osName?.lowercase().orEmpty()
|
val os = resolveDeviceOsName(d)?.lowercase().orEmpty()
|
||||||
val hasBrowser = d.browserName?.isNotBlank() == true
|
val hasBrowser = d.browserName?.isNotBlank() == true
|
||||||
return when {
|
return when {
|
||||||
"tablet" in type -> Icons.Filled.TabletAndroid
|
"tablet" in type -> Icons.Filled.TabletAndroid
|
||||||
@@ -692,7 +789,7 @@ private fun DeviceSessionDetailBottomSheet(
|
|||||||
)
|
)
|
||||||
DeviceSessionDetailParam(
|
DeviceSessionDetailParam(
|
||||||
stringResource(Res.string.settings_devices_field_os),
|
stringResource(Res.string.settings_devices_field_os),
|
||||||
d.osName
|
resolveDeviceOsName(d)
|
||||||
)
|
)
|
||||||
DeviceSessionDetailParam(
|
DeviceSessionDetailParam(
|
||||||
stringResource(Res.string.settings_devices_field_os_version),
|
stringResource(Res.string.settings_devices_field_os_version),
|
||||||
@@ -789,6 +886,8 @@ private fun DeviceSessionRow(
|
|||||||
) {
|
) {
|
||||||
val headline = remember(d) { deviceHeadline(d, unknownDeviceLabel) }
|
val headline = remember(d) { deviceHeadline(d, unknownDeviceLabel) }
|
||||||
val detail = remember(d) { deviceDetailLine(d) }
|
val detail = remember(d) { deviceDetailLine(d) }
|
||||||
|
val osLogo = remember(d) { deviceSessionLogoResource(d) }
|
||||||
|
val fallbackIcon = remember(d) { deviceSessionIcon(d) }
|
||||||
val interaction = remember(d.sessionId) { MutableInteractionSource() }
|
val interaction = remember(d.sessionId) { MutableInteractionSource() }
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -808,12 +907,22 @@ private fun DeviceSessionRow(
|
|||||||
.background(MaterialTheme.colorScheme.surfaceContainerHigh),
|
.background(MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Icon(
|
if (osLogo != null) {
|
||||||
imageVector = deviceSessionIcon(d),
|
AsyncImage(
|
||||||
contentDescription = null,
|
model = Res.getUri(osLogo),
|
||||||
modifier = Modifier.size(28.dp),
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
modifier = Modifier.size(28.dp),
|
||||||
)
|
contentScale = ContentScale.Fit,
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = fallbackIcon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -998,10 +1107,11 @@ fun SettingsDevicesScreen(onBack: () -> Unit) {
|
|||||||
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
) {
|
) {
|
||||||
sessionList.forEachIndexed { index, d ->
|
sessionList.forEachIndexed { index, d ->
|
||||||
|
val displaySession = deviceSessionForCurrentDevice(d)
|
||||||
DeviceSessionRow(
|
DeviceSessionRow(
|
||||||
d = d,
|
d = displaySession,
|
||||||
unknownDeviceLabel = unknownDeviceLabel,
|
unknownDeviceLabel = unknownDeviceLabel,
|
||||||
onOpen = { sheetDevice = d }
|
onOpen = { sheetDevice = displaySession }
|
||||||
)
|
)
|
||||||
if (index < sessionList.lastIndex) {
|
if (index < sessionList.lastIndex) {
|
||||||
SettingsSurfaceCutDivider()
|
SettingsSurfaceCutDivider()
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package ru.fromchat.platform
|
||||||
|
|
||||||
|
import platform.UIKit.UIDevice
|
||||||
|
|
||||||
|
private fun String?.trimIfNotBlank(): String? =
|
||||||
|
this?.trim()?.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
|
actual fun currentDeviceInfo(): CurrentDeviceInfo {
|
||||||
|
val current = UIDevice.currentDevice
|
||||||
|
|
||||||
|
return CurrentDeviceInfo(
|
||||||
|
osName = current.systemName.trimIfNotBlank(),
|
||||||
|
osVersion = current.systemVersion.trimIfNotBlank(),
|
||||||
|
deviceType = "mobile",
|
||||||
|
deviceName = current.name.trimIfNotBlank(),
|
||||||
|
brand = "Apple",
|
||||||
|
model = current.model.trimIfNotBlank()
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user