Merge branch 'rustdesk:master' into feature/devcontainer
@@ -16,6 +16,8 @@
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="RustDesk"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
|
||||
<receiver
|
||||
|
||||
@@ -594,7 +594,7 @@ class MainService : Service() {
|
||||
}
|
||||
val notification = notificationBuilder
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setSmallIcon(R.mipmap.ic_stat_logo)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.setAutoCancel(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
BIN
flutter/android/app/src/main/res/mipmap-hdpi/ic_stat_logo.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
flutter/android/app/src/main/res/mipmap-ldpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
BIN
flutter/android/app/src/main/res/mipmap-mdpi/ic_stat_logo.png
Normal file
|
After Width: | Height: | Size: 715 B |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
BIN
flutter/android/app/src/main/res/mipmap-xhdpi/ic_stat_logo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 14 KiB |
BIN
flutter/android/app/src/main/res/mipmap-xxhdpi/ic_stat_logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 16 KiB |
BIN
flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_stat_logo.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#ffffff</color>
|
||||
</resources>
|
||||
1
flutter/assets/GitHub.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="#fff" d="M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0 1 12 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/></svg>
|
||||
|
After Width: | Height: | Size: 792 B |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 18 KiB |
@@ -1,30 +1 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="220.000000pt" height="74.000000pt" viewBox="0 0 220.000000 74.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,74.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M627 734 c-4 -4 -7 -165 -7 -359 0 -260 3 -354 12 -363 13 -13 85
|
||||
-16 112 -6 13 5 16 24 16 100 0 133 7 133 138 1 l105 -107 67 0 c115 0 113 10
|
||||
-28 154 -67 67 -122 130 -122 139 0 10 37 58 83 108 133 147 133 149 24 149
|
||||
l-72 0 -79 -86 c-48 -52 -85 -84 -95 -82 -14 3 -17 27 -21 178 l-5 175 -60 3
|
||||
c-34 2 -64 0 -68 -4z"/>
|
||||
<path d="M1184 727 c-3 -8 -4 -127 -2 -264 l3 -250 30 -58 c50 -98 163 -165
|
||||
260 -153 28 3 30 6 33 47 5 75 -3 91 -46 91 -50 0 -102 29 -124 71 -21 40 -26
|
||||
177 -6 197 7 7 42 12 85 12 l73 0 0 65 0 65 -72 0 c-40 0 -78 4 -85 8 -7 5
|
||||
-13 40 -15 92 l-3 85 -63 3 c-47 2 -64 -1 -68 -11z"/>
|
||||
<path d="M185 537 c-119 -48 -179 -133 -179 -257 -1 -62 4 -84 26 -125 52 -99
|
||||
134 -149 243 -148 82 0 128 18 186 70 54 49 81 104 87 179 8 110 -41 205 -135
|
||||
261 -37 21 -61 27 -122 30 -45 1 -89 -2 -106 -10z m152 -136 c70 -32 96 -117
|
||||
59 -189 -34 -66 -112 -89 -183 -55 -100 47 -95 194 7 245 44 23 65 22 117 -1z"/>
|
||||
<path d="M1713 533 c-51 -18 -122 -83 -147 -137 -45 -96 -27 -218 45 -299 56
|
||||
-64 117 -91 204 -91 59 0 79 4 128 31 31 17 57 27 57 23 0 -5 15 -18 34 -29
|
||||
42 -27 116 -37 145 -22 19 11 22 19 19 69 -3 55 -4 56 -38 65 -63 16 -64 19
|
||||
-70 220 l-5 182 -63 3 c-46 2 -64 -1 -68 -11 -4 -12 -11 -12 -42 -1 -49 17
|
||||
-147 16 -199 -3z m150 -127 c54 -22 87 -70 87 -124 0 -109 -93 -170 -195 -128
|
||||
-75 32 -101 144 -48 207 27 32 74 58 106 59 10 0 32 -6 50 -14z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="293.333" height="98.667" version="1.0" viewBox="0 0 220 74"><path fill="#fff" d="M62.7.6c-.4.4-.7 16.5-.7 35.9 0 26 .3 35.4 1.2 36.3 1.3 1.3 8.5 1.6 11.2.6 1.3-.5 1.6-2.4 1.6-10 0-13.3.7-13.3 13.8-.1L100.3 74h6.7c11.5 0 11.3-1-2.8-15.4-6.7-6.7-12.2-13-12.2-13.9 0-1 3.7-5.8 8.3-10.8 13.3-14.7 13.3-14.9 2.4-14.9h-7.2l-7.9 8.6c-4.8 5.2-8.5 8.4-9.5 8.2-1.4-.3-1.7-2.7-2.1-17.8L75.5.5l-6-.3c-3.4-.2-6.4 0-6.8.4zm55.7.7c-.3.8-.4 12.7-.2 26.4l.3 25 3 5.8c5 9.8 16.3 16.5 26 15.3 2.8-.3 3-.6 3.3-4.7.5-7.5-.3-9.1-4.6-9.1-5 0-10.2-2.9-12.4-7.1-2.1-4-2.6-17.7-.6-19.7.7-.7 4.2-1.2 8.5-1.2h7.3V19h-7.2c-4 0-7.8-.4-8.5-.8-.7-.5-1.3-4-1.5-9.2l-.3-8.5-6.3-.3c-4.7-.2-6.4.1-6.8 1.1zm-99.9 19C6.6 25.1.6 33.6.6 46c-.1 6.2.4 8.4 2.6 12.5 5.2 9.9 13.4 14.9 24.3 14.8 8.2 0 12.8-1.8 18.6-7 5.4-4.9 8.1-10.4 8.7-17.9.8-11-4.1-20.5-13.5-26.1-3.7-2.1-6.1-2.7-12.2-3-4.5-.1-8.9.2-10.6 1zm15.2 13.6c7 3.2 9.6 11.7 5.9 18.9-3.4 6.6-11.2 8.9-18.3 5.5-10-4.7-9.5-19.4.7-24.5 4.4-2.3 6.5-2.2 11.7.1zm137.6-13.2c-5.1 1.8-12.2 8.3-14.7 13.7-4.5 9.6-2.7 21.8 4.5 29.9 5.6 6.4 11.7 9.1 20.4 9.1 5.9 0 7.9-.4 12.8-3.1 3.1-1.7 5.7-2.7 5.7-2.3 0 .5 1.5 1.8 3.4 2.9 4.2 2.7 11.6 3.7 14.5 2.2 1.9-1.1 2.2-1.9 1.9-6.9-.3-5.5-.4-5.6-3.8-6.5-6.3-1.6-6.4-1.9-7-22l-.5-18.2-6.3-.3c-4.6-.2-6.4.1-6.8 1.1-.4 1.2-1.1 1.2-4.2.1-4.9-1.7-14.7-1.6-19.9.3zm15 12.7c5.4 2.2 8.7 7 8.7 12.4 0 10.9-9.3 17-19.5 12.8-7.5-3.2-10.1-14.4-4.8-20.7 2.7-3.2 7.4-5.8 10.6-5.9 1 0 3.2.6 5 1.4z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.5 KiB |
1
flutter/assets/actions.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="496.063 521.772 32 32"><path fill="none" d="M496.063 521.772h32v32h-32v-32Z"/><path d="m513.817 535.858.87-8.052c.136-1.26-.279-1.399-.927-.31l-6.856 11.532c-.216.363-.049.658.374.658h3.031l-.87 8.052c-.136 1.26.279 1.399.927.309l6.856-11.531c.216-.363.048-.658-.374-.658h-3.031Z"/></svg>
|
||||
|
After Width: | Height: | Size: 390 B |
1
flutter/assets/actions_mobile.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="91.748 41.508 32 32"><path fill="none" d="M91.748 41.508h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M104.884 48.3h5.728c1.363 0 2.47.94 2.47 2.098v11.448c0 1.158-1.107 2.098-2.47 2.098h-5.728c-1.363 0-2.469-.94-2.469-2.098V50.398c0-1.158 1.106-2.098 2.469-2.098Zm-.004-2.692h5.729a5.162 5.162 0 0 1 5.164 5.164v13.472a5.164 5.164 0 0 1-1.514 3.649 5.143 5.143 0 0 1-3.65 1.515h-5.729a5.164 5.164 0 0 1-5.157-5.164V50.772c0-1.374.538-2.687 1.508-3.656a5.182 5.182 0 0 1 3.649-1.508Zm1.407 21.076a1.462 1.462 0 0 1 2.922 0 1.461 1.461 0 0 1-2.922 0Z"/></svg>
|
||||
|
After Width: | Height: | Size: 661 B |
@@ -1 +1 @@
|
||||
<svg width="553" height="553"><path d="M77 179a33 33 0 0 0-25 10 33 33 0 0 0-9 24v143a33 33 0 0 0 10 24 33 33 0 0 0 24 10c9 0 17-3 24-10a33 33 0 0 0 10-24V213c0-9-4-17-10-24a33 33 0 0 0-24-10zM352 51l24-44c1-3 1-5-2-6-3-2-5-1-7 2l-24 43a163 163 0 0 0-133 0L186 3c-2-3-4-4-7-2-2 1-3 3-1 6l23 44c-24 12-43 29-57 51a129 129 0 0 0-21 72h307c0-26-7-50-21-72a146 146 0 0 0-57-51zm-136 63a13 13 0 0 1-10 4 13 13 0 0 1-12-13c0-4 1-7 3-9 3-3 6-4 9-4s7 1 10 4c2 2 3 5 3 9s-1 7-3 9zm140 0a12 12 0 0 1-9 4c-4 0-7-1-9-4a12 12 0 0 1-4-9c0-4 1-7 4-9 2-3 5-4 9-4a12 12 0 0 1 9 4c2 2 3 5 3 9s-1 7-3 9zM124 407c0 10 4 19 11 26s15 10 26 10h24v76c0 9 4 17 10 24s15 10 24 10c10 0 18-3 25-10s10-15 10-24v-76h45v76c0 9 4 17 10 24s15 10 25 10c9 0 17-3 24-10s10-15 10-24v-76h25a35 35 0 0 0 25-10c7-7 11-16 11-26V185H124v222zm352-228a33 33 0 0 0-24 10 33 33 0 0 0-10 24v143a34 34 0 0 0 34 34c10 0 18-3 25-10s10-15 10-24V213c0-9-4-17-10-24a33 33 0 0 0-25-10z" fill="white" /></svg>
|
||||
<svg width="553" height="553"><path fill="#fff" d="M77 179a33 33 0 0 0-25 10 33 33 0 0 0-9 24v143a33 33 0 0 0 10 24 33 33 0 0 0 24 10c9 0 17-3 24-10a33 33 0 0 0 10-24V213c0-9-4-17-10-24a33 33 0 0 0-24-10zM352 51l24-44c1-3 1-5-2-6-3-2-5-1-7 2l-24 43a163 163 0 0 0-133 0L186 3c-2-3-4-4-7-2-2 1-3 3-1 6l23 44c-24 12-43 29-57 51a129 129 0 0 0-21 72h307c0-26-7-50-21-72a146 146 0 0 0-57-51zm-136 63a13 13 0 0 1-10 4 13 13 0 0 1-12-13c0-4 1-7 3-9 3-3 6-4 9-4s7 1 10 4c2 2 3 5 3 9s-1 7-3 9zm140 0a12 12 0 0 1-9 4c-4 0-7-1-9-4a12 12 0 0 1-4-9c0-4 1-7 4-9 2-3 5-4 9-4a12 12 0 0 1 9 4c2 2 3 5 3 9s-1 7-3 9zM124 407c0 10 4 19 11 26s15 10 26 10h24v76c0 9 4 17 10 24s15 10 24 10c10 0 18-3 25-10s10-15 10-24v-76h45v76c0 9 4 17 10 24s15 10 25 10c9 0 17-3 24-10s10-15 10-24v-76h25a35 35 0 0 0 25-10c7-7 11-16 11-26V185H124v222zm352-228a33 33 0 0 0-24 10 33 33 0 0 0-10 24v143a34 34 0 0 0 34 34c10 0 18-3 25-10s10-15 10-24V213c0-9-4-17-10-24a33 33 0 0 0-25-10z"/></svg>
|
||||
|
Before Width: | Height: | Size: 955 B After Width: | Height: | Size: 952 B |
1
flutter/assets/call_end.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="587.811 560.459 32 32"><path fill="none" d="M587.811 560.459h32v32h-32v-32Z"/><path fill-rule="evenodd" d="m601.469 580.246-3.859 3.858a1.02 1.02 0 1 1-1.444-1.444l3.858-3.859 2.554-2.553 3.85-3.851a1.022 1.022 0 0 1 1.445 1.445l-3.851 3.85 2.299 2.299a1.23 1.23 0 0 0 1.717.014q3.089-2.988 6.218-.117.03.026.089.083c.34.34.527.8.527 1.276a1.78 1.78 0 0 1-.528 1.277l-1.366 1.366a5.557 5.557 0 0 1-7.861.004l-3.648-3.648Zm-.923-6.031-.267-.266a1.23 1.23 0 0 1-.014-1.717q2.988-3.09.117-6.218-.026-.031-.083-.089c-.341-.34-.8-.527-1.276-.527a1.78 1.78 0 0 0-1.277.528l-1.366 1.366a5.555 5.555 0 0 0-.004 7.861l1.616 1.616a.832.832 0 0 0 1.175 0l1.379-1.378a.833.833 0 0 0 0-1.176Z"/></svg>
|
||||
|
After Width: | Height: | Size: 790 B |
1
flutter/assets/call_wait.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="587.811 592.459 32 32"><path fill="none" d="M587.811 592.459h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M595.385 608.153a5.553 5.553 0 0 1 .004-7.852l1.365-1.365c.34-.34.795-.531 1.275-.527.476 0 .935.187 1.275.526l.083.089q2.868 3.125-.117 6.212a1.226 1.226 0 0 0 .014 1.714l6.036 6.036a1.226 1.226 0 0 0 1.714.014q3.086-2.985 6.212-.117l.089.083c.339.34.526.799.526 1.275.004.48-.187.935-.527 1.275l-1.365 1.365a5.553 5.553 0 0 1-7.852.004l-8.732-8.732Zm7.426-1.824a1.215 1.215 0 1 1 2.43 0 1.215 1.215 0 0 1-2.43 0Zm3.811 0a1.215 1.215 0 1 1 2.43 0 1.215 1.215 0 0 1-2.43 0Zm3.811 0a1.215 1.215 0 1 1 2.43 0 1.215 1.215 0 0 1-2.43 0Z"/></svg>
|
||||
|
After Width: | Height: | Size: 750 B |
1
flutter/assets/chat.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="587.811 521.772 32 32"><path fill="none" d="M587.811 521.772h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M601.822 543.465h7.624a3.94 3.94 0 0 0 3.942-3.938v-7.213a3.944 3.944 0 0 0-3.942-3.942h-11.27a3.944 3.944 0 0 0-3.942 3.942v7.213a3.944 3.944 0 0 0 2.901 3.798v2.543c0 1.413.787 1.726 1.757.699l2.93-3.102Zm-1.593-7.668a2.203 2.203 0 0 1 .001-3.112l.541-.541a.714.714 0 0 1 1.011 0l.033.035q1.136 1.239-.047 2.462a.486.486 0 0 0 .006.679l2.392 2.392c.186.186.49.189.679.006q1.224-1.183 2.462-.046l.035.033a.71.71 0 0 1 0 1.01l-.541.541a2.199 2.199 0 0 1-3.112.002l-3.46-3.461Z"/></svg>
|
||||
|
After Width: | Height: | Size: 694 B |
1
flutter/assets/close.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="679.559 521.772 32 32"><path fill="none" d="M679.559 521.772h32v32h-32v-32Z"/><path d="m695.559 536.034-5.305-5.305a1.229 1.229 0 1 0-1.738 1.738l5.305 5.305-5.305 5.305a1.229 1.229 0 0 0 0 1.738 1.23 1.23 0 0 0 1.738 0l5.305-5.305 5.305 5.305a1.229 1.229 0 1 0 1.738-1.738l-5.305-5.305 5.305-5.305a1.229 1.229 0 1 0-1.738-1.738l-5.305 5.305Z"/></svg>
|
||||
|
After Width: | Height: | Size: 453 B |
1
flutter/assets/display.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="450.189 521.772 32 32"><path fill="none" d="M450.189 521.772h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M470.477 545.236h2.795a5.13 5.13 0 0 0 3.639-1.515 5.126 5.126 0 0 0 1.515-3.639v-9.07a5.126 5.126 0 0 0-1.515-3.639 5.13 5.13 0 0 0-3.639-1.515h-14.166a5.13 5.13 0 0 0-3.639 1.515 5.127 5.127 0 0 0-1.516 3.639v9.07c0 1.363.544 2.675 1.516 3.639a5.13 5.13 0 0 0 3.639 1.515h2.795v1.234a3.217 3.217 0 0 0 3.216 3.216h2.144a3.217 3.217 0 0 0 3.216-3.216v-1.234Zm-11.371-15.753h14.166c.406 0 .79.166 1.08.449.283.29.449.674.449 1.08v9.07c0 .406-.166.79-.449 1.08-.29.283-.674.449-1.08.449h-14.166c-.406 0-.79-.166-1.08-.449a1.55 1.55 0 0 1-.45-1.08v-9.07c0-.406.167-.79.45-1.08.29-.283.674-.449 1.08-.449Z"/></svg>
|
||||
|
After Width: | Height: | Size: 820 B |
1
flutter/assets/fullscreen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="404.315 521.772 32 32"><path fill-rule="evenodd" d="M411.985 533.763a1.815 1.815 0 0 1-1.812 1.812c-.479 0-.943-.196-1.284-.536a1.834 1.834 0 0 1-.529-1.276v-2.806c0-1.312.522-2.566 1.45-3.494a4.938 4.938 0 0 1 3.494-1.45h2.799c.486 0 .942.196 1.283.536.341.334.529.798.529 1.276a1.8 1.8 0 0 1-.529 1.283c-.341.341-.797.53-1.283.53h-2.799c-.348 0-.688.137-.935.391-.246.247-.384.58-.384.928v2.806Zm4.118 12.143a1.802 1.802 0 0 1 1.812 1.812c0 .479-.188.943-.529 1.276a1.81 1.81 0 0 1-1.283.537h-2.799a4.938 4.938 0 0 1-3.494-1.45 4.939 4.939 0 0 1-1.45-3.495v-2.805c0-.479.196-.935.529-1.276a1.824 1.824 0 0 1 1.284-.537c.485 0 .942.196 1.283.537.341.341.529.797.529 1.276v2.805c0 .348.138.682.384.928.247.254.587.392.935.392h2.799Zm8.424-16.268c-.486 0-.943-.189-1.283-.53a1.8 1.8 0 0 1-.529-1.283c0-.478.188-.942.529-1.276.34-.34.797-.536 1.283-.536h2.798a4.94 4.94 0 0 1 3.495 1.45 4.938 4.938 0 0 1 1.45 3.494v2.806a1.823 1.823 0 0 1-1.813 1.812c-.486 0-.942-.196-1.283-.536a1.8 1.8 0 0 1-.529-1.276v-2.806a1.31 1.31 0 0 0-.385-.928 1.302 1.302 0 0 0-.935-.391h-2.798Zm4.118 12.143c0-.479.188-.935.529-1.276a1.81 1.81 0 0 1 1.283-.537 1.82 1.82 0 0 1 1.813 1.813v2.805a4.939 4.939 0 0 1-1.45 3.495 4.94 4.94 0 0 1-3.495 1.45h-2.798c-.486 0-.943-.196-1.283-.537a1.784 1.784 0 0 1-.529-1.276 1.804 1.804 0 0 1 1.812-1.812h2.798c.348 0 .689-.138.935-.392a1.31 1.31 0 0 0 .385-.928v-2.805Z"/><path fill="none" d="M404.315 521.772h32v32h-32v-32Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
flutter/assets/fullscreen_exit.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="404.315 560.459 32 32"><path fill="none" d="M404.315 560.459h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M414.29 566.512c0-.478.189-.935.53-1.275.34-.341.797-.537 1.283-.537a1.824 1.824 0 0 1 1.812 1.812v2.806a4.938 4.938 0 0 1-1.45 3.494 4.938 4.938 0 0 1-3.494 1.45h-2.798c-.486 0-.943-.195-1.284-.536a1.792 1.792 0 0 1-.529-1.276 1.805 1.805 0 0 1 1.813-1.813h2.798c.348 0 .689-.137.935-.391.247-.246.384-.58.384-.928v-2.806Zm-4.117 15.768a1.804 1.804 0 0 1-1.813-1.812c0-.478.189-.942.529-1.276a1.811 1.811 0 0 1 1.284-.536h2.798c1.312 0 2.566.522 3.494 1.449a4.94 4.94 0 0 1 1.45 3.495v2.805a1.824 1.824 0 0 1-1.812 1.813c-.486 0-.943-.196-1.283-.537a1.797 1.797 0 0 1-.53-1.276V583.6c0-.348-.137-.682-.384-.928a1.303 1.303 0 0 0-.935-.392h-2.798Zm20.284-11.643c.486 0 .943.189 1.283.53.341.34.53.797.53 1.283 0 .478-.189.942-.53 1.276-.34.341-.797.536-1.283.536h-2.798a4.94 4.94 0 0 1-3.495-1.45 4.937 4.937 0 0 1-1.449-3.494v-2.806a1.82 1.82 0 0 1 1.812-1.812c.486 0 .942.196 1.283.537.341.34.529.797.529 1.275v2.806c0 .348.138.682.385.928.246.254.587.391.935.391h2.798Zm-4.118 15.768c0 .479-.188.936-.529 1.276a1.81 1.81 0 0 1-1.283.537 1.82 1.82 0 0 1-1.812-1.813V583.6a4.944 4.944 0 0 1 4.944-4.944h2.798c.486 0 .943.195 1.283.536.341.334.53.798.53 1.276 0 .486-.189.942-.53 1.283a1.8 1.8 0 0 1-1.283.529h-2.798c-.348 0-.689.138-.935.392a1.31 1.31 0 0 0-.385.928v2.805Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1 +1 @@
|
||||
<svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M238.802 115.023l-111.573 114.68-8.6-8.367L230.2 106.656z"/><path d="M125.559 108.093l114.68 111.572-8.368 8.601-114.68-111.572z"/></g></svg>
|
||||
<svg viewBox="0 0 347.97 347.97"><path fill="none" stroke="red" stroke-width="14.827" d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z"/><g fill="red"><path d="m238.802 115.023-111.573 114.68-8.6-8.367L230.2 106.656z"/><path d="m125.559 108.093 114.68 111.572-8.368 8.601-114.68-111.572z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 440 B |
@@ -1 +1 @@
|
||||
<svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z"/></g></svg>
|
||||
<svg viewBox="0 0 347.97 347.97"><path fill="none" stroke="red" stroke-width="14.827" d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z"/><path fill="red" d="m231.442 247.498-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z"/></svg>
|
||||
|
Before Width: | Height: | Size: 755 B After Width: | Height: | Size: 746 B |
@@ -1 +1 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261 253" width="261" height="253"><path fill="#0ff" d="m1 217c0-5.5 4.5-10 10-10h60c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-60c-5.5 0-10-4.5-10-10z"/><path fill="#487997" d="m89 216c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#c0ff00" d="m3 166c0-5.5 4.5-10 10-10h34c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-34c-5.5 0-10-4.5-10-10z"/><path fill="#00f" d="m63 166c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="m140 215c0-5.5 4.5-10 10-10h26c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-26c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="m190 215c0-5.5 4.5-10 10-10h26c5.5 0 10 4.5 10 10v28c0 5.5-4.5 10-10 10h-26c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m112 166c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m165 165c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m216 164c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="m0 115c0-5.5 4.5-10 10-10h61c5.5 0 10 4.5 10 10v23c0 5.5-4.5 10-10 10h-61c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m90 116c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m139 116c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m191 115c0-5.5 4.5-10 10-10h23c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-23c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="m2 62c0-5.5 4.5-10 10-10h50c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-50c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m79 62c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m131 64c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m182 63c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m0 10c0-5.5 4.5-10 10-10h28c5.5 0 10 4.5 10 10v27c0 5.5-4.5 10-10 10h-28c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m54 11c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m105 12c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m156 13c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10z"/><path d="m16.7 171.9v1.9q-1.1-0.5-2.1-0.8-1-0.2-1.9-0.2-1.7 0-2.5 0.6-0.9 0.6-0.9 1.8 0 0.9 0.6 1.4 0.6 0.5 2.2 0.8l1.2 0.3q2.2 0.4 3.2 1.4 1.1 1.1 1.1 2.9 0 2.1-1.4 3.2-1.5 1.1-4.2 1.1-1 0-2.2-0.3-1.2-0.2-2.4-0.6v-2.1q1.2 0.7 2.3 1 1.2 0.4 2.3 0.4 1.7 0 2.6-0.7 0.9-0.6 0.9-1.9 0-1.1-0.6-1.7-0.7-0.6-2.2-0.9l-1.2-0.2q-2.2-0.4-3.2-1.4-1-0.9-1-2.6 0-1.9 1.4-3 1.3-1.1 3.7-1.1 1.1 0 2.1 0.1 1.1 0.2 2.2 0.6zm13 7.5v6.6h-1.8v-6.5q0-1.6-0.6-2.4-0.6-0.7-1.8-0.7-1.5 0-2.3 0.9-0.9 0.9-0.9 2.5v6.2h-1.8v-15.2h1.8v6q0.7-1 1.5-1.5 0.9-0.5 2.1-0.5 1.8 0 2.8 1.2 1 1.1 1 3.4zm3.6 6.6v-10.9h1.8v10.9zm0-12.9v-2.3h1.7v2.3zm9.4-2.3h1.7v1.5h-1.7q-0.9 0-1.3 0.4-0.4 0.4-0.4 1.4v1h3v1.4h-3v9.5h-1.8v-9.5h-1.7v-1.4h1.7v-0.8q0-1.8 0.8-2.7 0.9-0.8 2.7-0.8zm2.9 1.2h1.8v3.1h3.7v1.4h-3.7v5.9q0 1.3 0.3 1.7 0.4 0.4 1.5 0.4h1.9v1.5h-1.9q-2.1 0-2.8-0.8-0.8-0.8-0.8-2.8v-5.9h-1.4v-1.4h1.4z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="261" height="253" version="1.2"><path fill="#0ff" d="M1 217c0-5.5 4.5-10 10-10h60c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10H11c-5.5 0-10-4.5-10-10z"/><path fill="#487997" d="M89 216c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10H99c-5.5 0-10-4.5-10-10z"/><path fill="#c0ff00" d="M3 166c0-5.5 4.5-10 10-10h34c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10H13c-5.5 0-10-4.5-10-10z"/><path fill="#00f" d="M63 166c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10H73c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="M140 215c0-5.5 4.5-10 10-10h26c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-26c-5.5 0-10-4.5-10-10zM190 215c0-5.5 4.5-10 10-10h26c5.5 0 10 4.5 10 10v28c0 5.5-4.5 10-10 10h-26c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="M112 166c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10zM165 165c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10zM216 164c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="M0 115c0-5.5 4.5-10 10-10h61c5.5 0 10 4.5 10 10v23c0 5.5-4.5 10-10 10H10c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="M90 116c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10zM139 116c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10zM191 115c0-5.5 4.5-10 10-10h23c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-23c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="M2 62c0-5.5 4.5-10 10-10h50c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10H12C6.5 98 2 93.5 2 88z"/><path fill="#00ffa8" d="M79 62c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10H89c-5.5 0-10-4.5-10-10zM131 64c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10zM182 63c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10zM0 10C0 4.5 4.5 0 10 0h28c5.5 0 10 4.5 10 10v27c0 5.5-4.5 10-10 10H10C4.5 47 0 42.5 0 37zM54 11c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10H64c-5.5 0-10-4.5-10-10zM105 12c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10zM156 13c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10z"/><path d="M16.7 171.9v1.9q-1.1-.5-2.1-.8-1-.2-1.9-.2-1.7 0-2.5.6-.9.6-.9 1.8 0 .9.6 1.4.6.5 2.2.8l1.2.3q2.2.4 3.2 1.4 1.1 1.1 1.1 2.9 0 2.1-1.4 3.2-1.5 1.1-4.2 1.1-1 0-2.2-.3-1.2-.2-2.4-.6v-2.1q1.2.7 2.3 1 1.2.4 2.3.4 1.7 0 2.6-.7.9-.6.9-1.9 0-1.1-.6-1.7-.7-.6-2.2-.9l-1.2-.2q-2.2-.4-3.2-1.4-1-.9-1-2.6 0-1.9 1.4-3 1.3-1.1 3.7-1.1 1.1 0 2.1.1 1.1.2 2.2.6zm13 7.5v6.6h-1.8v-6.5q0-1.6-.6-2.4-.6-.7-1.8-.7-1.5 0-2.3.9-.9.9-.9 2.5v6.2h-1.8v-15.2h1.8v6q.7-1 1.5-1.5.9-.5 2.1-.5 1.8 0 2.8 1.2 1 1.1 1 3.4zm3.6 6.6v-10.9h1.8V186zm0-12.9v-2.3H35v2.3zm9.4-2.3h1.7v1.5h-1.7q-.9 0-1.3.4t-.4 1.4v1h3v1.4h-3v9.5h-1.8v-9.5h-1.7v-1.4h1.7v-.8q0-1.8.8-2.7.9-.8 2.7-.8zm2.9 1.2h1.8v3.1h3.7v1.4h-3.7v5.9q0 1.3.3 1.7.4.4 1.5.4h1.9v1.5h-1.9q-2.1 0-2.8-.8-.8-.8-.8-2.8v-5.9h-1.4v-1.4h1.4z"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -1 +1 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 253" width="260" height="253"><path fill="#c0ff00" d="m0 167c0-5.5 4.5-10 10-10h90c5.5 0 10 4.5 10 10v23c0 5.5-4.5 10-10 10h-90c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="m3 63c0-5.5 4.5-10 10-10h46c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-46c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="m1 114c0-5.5 4.5-10 10-10h62c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-62c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m114 166c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m90 117c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v22c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m81 64c0-5.5 4.5-10 10-10h22c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-22c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m54 10c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v27c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m2 10c0-5.5 4.5-10 10-10h26c5.5 0 10 4.5 10 10v27c0 5.5-4.5 10-10 10h-26c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="m2 216c0-5.5 4.5-10 10-10h59c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-59c-5.5 0-10-4.5-10-10z"/><path fill="#487997" d="m89 217c0-5.5 4.5-10 10-10h23c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-23c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="m141 217c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="m191 216c0-5.5 4.5-10 10-10h23c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-23c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m166 164c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v27c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m215 165c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m138 111c0-5.5 4.5-10 10-10h28c5.5 0 10 4.5 10 10v29c0 5.5-4.5 10-10 10h-28c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m192 112c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v29c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m129 64c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m182 62c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m105 11c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="m154 12c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10z"/><path d="m42.7 172.9v1.9q-1.1-0.5-2.1-0.8-1-0.2-1.9-0.2-1.7 0-2.5 0.6-0.9 0.6-0.9 1.8 0 0.9 0.6 1.4 0.6 0.5 2.2 0.8l1.2 0.3q2.2 0.4 3.2 1.4 1.1 1.1 1.1 2.9 0 2.1-1.4 3.2-1.5 1.1-4.2 1.1-1 0-2.2-0.3-1.2-0.2-2.4-0.6v-2.1q1.2 0.7 2.3 1 1.2 0.4 2.3 0.4 1.7 0 2.6-0.7 0.9-0.6 0.9-1.9 0-1.1-0.6-1.7-0.7-0.6-2.2-0.9l-1.2-0.2q-2.2-0.4-3.2-1.4-1-0.9-1-2.6 0-1.9 1.4-3 1.3-1.1 3.7-1.1 1.1 0 2.1 0.1 1.1 0.2 2.2 0.6zm13 7.5v6.6h-1.8v-6.5q0-1.6-0.6-2.4-0.6-0.7-1.8-0.7-1.5 0-2.3 0.9-0.9 0.9-0.9 2.5v6.2h-1.8v-15.2h1.8v6q0.7-1 1.5-1.5 0.9-0.5 2.1-0.5 1.8 0 2.8 1.2 1 1.1 1 3.4zm3.6 6.6v-10.9h1.8v10.9zm0-12.9v-2.3h1.7v2.3zm9.4-2.3h1.7v1.5h-1.7q-0.9 0-1.3 0.4-0.4 0.4-0.4 1.4v1h3v1.4h-3v9.5h-1.8v-9.5h-1.7v-1.4h1.7v-0.8q0-1.8 0.8-2.7 0.9-0.8 2.7-0.8zm2.9 1.2h1.8v3.1h3.7v1.4h-3.7v5.9q0 1.3 0.3 1.7 0.4 0.4 1.5 0.4h1.9v1.5h-1.9q-2.1 0-2.8-0.8-0.8-0.8-0.8-2.8v-5.9h-1.4v-1.4h1.4z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="260" height="253" version="1.2"><path fill="#c0ff00" d="M0 167c0-5.5 4.5-10 10-10h90c5.5 0 10 4.5 10 10v23c0 5.5-4.5 10-10 10H10c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="M3 63c0-5.5 4.5-10 10-10h46c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10H13C7.5 97 3 92.5 3 87zM1 114c0-5.5 4.5-10 10-10h62c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10H11c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="M114 166c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v24c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10zM90 117c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v22c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10zM81 64c0-5.5 4.5-10 10-10h22c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10H91c-5.5 0-10-4.5-10-10zM54 10c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v27c0 5.5-4.5 10-10 10H64c-5.5 0-10-4.5-10-10zM2 10C2 4.5 6.5 0 12 0h26c5.5 0 10 4.5 10 10v27c0 5.5-4.5 10-10 10H12C6.5 47 2 42.5 2 37z"/><path fill="#0ff" d="M2 216c0-5.5 4.5-10 10-10h59c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10H12c-5.5 0-10-4.5-10-10z"/><path fill="#487997" d="M89 217c0-5.5 4.5-10 10-10h23c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10H99c-5.5 0-10-4.5-10-10z"/><path fill="#0ff" d="M141 217c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10zM191 216c0-5.5 4.5-10 10-10h23c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-23c-5.5 0-10-4.5-10-10z"/><path fill="#00ffa8" d="M166 164c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v27c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10zM215 165c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10zM138 111c0-5.5 4.5-10 10-10h28c5.5 0 10 4.5 10 10v29c0 5.5-4.5 10-10 10h-28c-5.5 0-10-4.5-10-10zM192 112c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v29c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10zM129 64c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10zM182 62c0-5.5 4.5-10 10-10h25c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-25c-5.5 0-10-4.5-10-10zM105 11c0-5.5 4.5-10 10-10h24c5.5 0 10 4.5 10 10v26c0 5.5-4.5 10-10 10h-24c-5.5 0-10-4.5-10-10zM154 12c0-5.5 4.5-10 10-10h27c5.5 0 10 4.5 10 10v25c0 5.5-4.5 10-10 10h-27c-5.5 0-10-4.5-10-10z"/><path d="M42.7 172.9v1.9q-1.1-.5-2.1-.8-1-.2-1.9-.2-1.7 0-2.5.6-.9.6-.9 1.8 0 .9.6 1.4.6.5 2.2.8l1.2.3q2.2.4 3.2 1.4 1.1 1.1 1.1 2.9 0 2.1-1.4 3.2-1.5 1.1-4.2 1.1-1 0-2.2-.3-1.2-.2-2.4-.6v-2.1q1.2.7 2.3 1 1.2.4 2.3.4 1.7 0 2.6-.7.9-.6.9-1.9 0-1.1-.6-1.7-.7-.6-2.2-.9l-1.2-.2q-2.2-.4-3.2-1.4-1-.9-1-2.6 0-1.9 1.4-3 1.3-1.1 3.7-1.1 1.1 0 2.1.1 1.1.2 2.2.6zm13 7.5v6.6h-1.8v-6.5q0-1.6-.6-2.4-.6-.7-1.8-.7-1.5 0-2.3.9-.9.9-.9 2.5v6.2h-1.8v-15.2h1.8v6q.7-1 1.5-1.5.9-.5 2.1-.5 1.8 0 2.8 1.2 1 1.1 1 3.4zm3.6 6.6v-10.9h1.8V187zm0-12.9v-2.3H61v2.3zm9.4-2.3h1.7v1.5h-1.7q-.9 0-1.3.4t-.4 1.4v1h3v1.4h-3v9.5h-1.8v-9.5h-1.7v-1.4h1.7v-.8q0-1.8.8-2.7.9-.8 2.7-.8zm2.9 1.2h1.8v3.1h3.7v1.4h-3.7v5.9q0 1.3.3 1.7.4.4 1.5.4h1.9v1.5h-1.9q-2.1 0-2.8-.8-.8-.8-.8-2.8v-5.9h-1.4v-1.4h1.4z"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.9 KiB |
1
flutter/assets/keyboard.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="541.937 521.772 32 32"><path fill="none" d="M541.937 521.772h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M552.145 539.981h11.584c.446 0 .808.362.808.808v.536c0 .786-.639 1.425-1.425 1.425h-10.35a1.426 1.426 0 0 1-1.425-1.425v-.536c0-.446.362-.808.808-.808Zm-1.761-3.511h.899c.536 0 .971.435.971.971v.899a.971.971 0 0 1-.971.971h-.899a.971.971 0 0 1-.971-.971v-.899c0-.536.435-.971.971-.971Zm3.552 0h.899c.536 0 .971.435.971.971v.899a.971.971 0 0 1-.971.971h-.899a.972.972 0 0 1-.972-.971v-.899c0-.536.436-.971.972-.971Zm3.551 0h.9c.536 0 .971.435.971.971v.899a.971.971 0 0 1-.971.971h-.9a.971.971 0 0 1-.971-.971v-.899c0-.536.435-.971.971-.971Zm3.552 0h.899c.536 0 .972.435.972.971v.899a.972.972 0 0 1-.972.971h-.899a.971.971 0 0 1-.971-.971v-.899c0-.536.435-.971.971-.971Zm3.552 0h.899c.536 0 .971.435.971.971v.899a.971.971 0 0 1-.971.971h-.899a.971.971 0 0 1-.971-.971v-.899c0-.536.435-.971.971-.971Zm-14.383-3.512h1.25c.44 0 .796.357.796.796v1.25a.796.796 0 0 1-.796.796h-1.25a.796.796 0 0 1-.795-.796v-1.25c0-.439.356-.796.795-.796Zm3.552 0h1.25c.439 0 .796.357.796.796v1.25a.797.797 0 0 1-.796.796h-1.25a.797.797 0 0 1-.796-.796v-1.25c0-.439.357-.796.796-.796Zm3.552 0h1.25c.439 0 .796.357.796.796v1.25a.797.797 0 0 1-.796.796h-1.25a.797.797 0 0 1-.796-.796v-1.25c0-.439.357-.796.796-.796Zm3.552 0h1.25c.439 0 .796.357.796.796v1.25a.797.797 0 0 1-.796.796h-1.25a.797.797 0 0 1-.796-.796v-1.25c0-.439.357-.796.796-.796Zm-9.553-3.85h13.252c1.407 0 2.755.507 3.748 1.409.993.902 1.552 2.127 1.552 3.404v7.702c0 1.277-.559 2.501-1.552 3.403-.993.902-2.341 1.409-3.748 1.409h-13.252c-1.407 0-2.755-.507-3.748-1.409-.993-.902-1.552-2.126-1.552-3.403v-7.702c0-1.277.559-2.502 1.552-3.404.993-.902 2.341-1.409 3.748-1.409Zm13.105 3.85h1.25c.439 0 .795.357.795.796v1.25a.796.796 0 0 1-.795.796h-1.25a.796.796 0 0 1-.796-.796v-1.25c0-.439.356-.796.796-.796Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1,6 +1 @@
|
||||
<svg viewBox="0 0 256 256">
|
||||
<g transform="translate(0 256) scale(.1 -.1)" fill="white">
|
||||
<path d="m1215 2537c-140-37-242-135-286-278-23-75-23-131 1-383l18-200-54-60c-203-224-383-615-384-831v-51l-66-43c-113-75-194-199-194-300 0-110 99-234 244-305 103-50 185-69 296-69 100 0 156 14 211 54 26 18 35 19 78 10 86-18 233-24 335-12 85 10 222 38 269 56 9 4 19-7 29-35 20-50 52-64 136-57 98 8 180 52 282 156 124 125 180 244 180 380 0 80-28 142-79 179l-36 26 4 119c5 175-22 292-105 460-74 149-142 246-286 409-43 49-78 92-78 97 0 4-7 52-15 107-8 54-19 140-24 189-13 121-41 192-103 260-95 104-248 154-373 122zm172-112c62-19 134-80 163-140 15-31 28-92 41-193 27-214 38-276 57-304 9-14 59-74 111-134 92-106 191-246 236-334 69-137 115-339 101-451l-7-55-71 10c-100 13-234-5-265-36-54-55-85-207-82-412l1-141-51-17c-104-34-245-51-380-45-69 3-142 10-162 16-32 10-37 17-53 68-23 72-87 201-136 273-80 117-158 188-237 215-37 13-37 13-34 61 13 211 182 555 373 759 57 62 58 63 58 121 0 33-9 149-19 259-21 224-18 266 26 347 67 122 193 174 330 133zm687-1720c32-9 71-25 87-36 60-42 59-151-4-274-59-119-221-250-317-257-34-3-35-2-48 47-18 65-20 329-3 413 16 83 29 110 55 115 51 10 177 6 230-8zm-1418-80c79-46 187-195 247-340 41-99 43-121 12-141-39-25-148-30-238-10-142 32-264 112-307 202-20 41-21 50-10 87 24 83 102 166 192 207 54 25 53 25 104-5z"/>
|
||||
<path d="m1395 1945c-92-16-220-52-256-70-28-15-29-18-29-89 0-247 165-397 345-312 60 28 77 46 106 111 54 123 0 378-80 374-9 0-47-7-86-14zm74-156c15-69 14-112-5-159s-55-70-111-70c-48 0-78 20-102 68-15 29-41 131-41 159 0 9 230 63 242 57 3-2 11-27 17-55z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="441" height="512" style="isolation:isolate"><path fill="#FFF" fill-rule="evenodd" d="M220.499 479.926c-74.052 0-136.663-53.729-147.853-118.13-.591-3.404-3.789-5.476-7.137-4.626l-50.683 12.879C3.107 373.027-2.913 366.27 1.39 354.971q26.627-69.911 87.656-131.908c30.862-31.352 41.876-59.308 43.591-106.328 1.715-47.02 40.427-84.661 87.862-84.661 47.435 0 86.149 37.641 87.864 84.661 1.715 47.02 12.729 74.976 43.591 106.328q61.027 61.997 87.656 131.908c4.303 11.299-1.717 18.056-13.436 15.078l-50.683-12.879c-3.348-.85-6.546 1.222-7.137 4.626-11.192 64.401-73.801 118.13-147.855 118.13Zm0-251.676c-1.591.049-3.181-.338-4.393-1.113l-50.912-32.578c-6.324-4.004-7.244-11.629-2.051-17.02 5.194-5.388 29.297-12.27 57.356-12.27 28.061 0 52.162 6.882 57.356 12.27 5.193 5.391 4.275 13.016-2.049 17.02l-50.912 32.578c-1.214.775-2.804 1.162-4.395 1.113Zm-44.627-7.73c-38.185 15.381-67.218 55.636-67.218 102.62 0 61.729 50.116 111.847 111.845 111.847s111.845-50.118 111.845-111.847c0-46.986-29.035-87.243-67.225-102.622-1.601-.647-4.082-.416-5.538.516l-27.383 17.52c-.03.021-.062.043-.083.064a20.762 20.762 0 0 1-5.705 2.473c-1.848.49-3.755.73-5.676.73h-.177l-.062-.011-.054.011h-.175c-1.921 0-3.83-.24-5.686-.73a20.7 20.7 0 0 1-5.697-2.473 3.83 3.83 0 0 0-.094-.064l-27.376-17.516c-1.456-.931-3.937-1.165-5.541-.518Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 375" style="width:32px;height:32px;margin:0 4px 4px 0"><rect transform="matrix(.91553 0 0 .91553 -152.92 116.76)" x="167.03" y="-127.54" width="409.6" height="409.6" rx="64" ry="64" fill="#0071ff"></rect><path d="M150.428 322.264c-29.063-6.202-53.897-22.439-73.115-47.804-19.507-25.746-27.838-55.355-25.723-91.414 6.655-62.013 47.667-106.753 99.687-120.411 4.509-.989 8.353-3.462 12.55-1.322 3.22 1.64 6.028 4.467 7.206 7.251 1.25 2.955 1.877 21.54.99 29.331-1.076 9.46-3.877 12.418-14.566 15.388-29.723 10.195-48.105 34.07-53.697 61.017-4.8 29.668 2.951 59.729 21.528 78.727 8.966 8.993 17.92 14.24 30.869 18.086 8.646 2.57 13.393 5.758 15.036 10.102 1.085 2.867 1.63 22.984.779 28.772-1.33 9.046-1.702 9.796-5.792 11.667-5.029 2.3-7.404 2.392-15.752.61zm50.708.29c-3.092-1.402-5.673-4.83-6.73-8.94-.134-9.408-2.366-25.754 1.02-33.373 1.88-4.128 4.65-5.999 12.433-8.396 21.267-6.551 37.593-19.88 46.806-38.213 11.11-22.108 11.877-55.183 1.808-77.975-9.154-20.723-25.7-35.217-48.555-42.534-8.872-2.84-12.004-5.065-12.968-9.21-1.002-4.31-1.435-19.87-.785-28.218.682-8.766 1.249-9.99 6.162-13.318 3.701-2.505 5.482-2.446 17.223.575 36.718 10.077 65.97 33.597 83.026 66.68 18.495 37.034 19.191 86.11 1.742 122.655-17.233 36.09-50.591 62.511-88.622 70.194-8.172 1.65-9.07 1.656-12.56.073z" fill="#fff"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" viewBox="66.993 897.484 26 26"><linearGradient id="a" x1=".148" x2=".845" y1=".851" y2=".154" gradientTransform="matrix(26.301 0 0 26.331 90.674 911.757)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0071ff"/><stop offset="1" stop-color="#00bfe1"/></linearGradient><defs><linearGradient xlink:href="#a" id="b" x1=".148" x2=".845" y1=".851" y2=".154" gradientTransform="matrix(26.00048 0 0 25.99935 66.993 897.485)" gradientUnits="userSpaceOnUse"/></defs><path fill="url(#b)" d="m89.318 903.552-2.135 2.122c-.376.337-.558.879-.347 1.337 1.422 2.976.882 6.524-1.452 8.856-2.335 2.331-5.887 2.87-8.866 1.449-.439-.197-.954-.03-1.292.312l-2.17 2.167a1.154 1.154 0 0 0 .208 1.81 13.005 13.005 0 0 0 15.91-1.912 12.97 12.97 0 0 0 1.956-15.887 1.154 1.154 0 0 0-1.812-.254zm-18.467-2.305a12.969 12.969 0 0 0-2.02 15.885 1.154 1.154 0 0 0 1.812.254l2.124-2.11c.385-.336.572-.884.359-1.348-1.423-2.976-.884-6.524 1.451-8.856 2.334-2.332 5.887-2.871 8.866-1.45.434.194.942.033 1.281-.3l2.182-2.18a1.152 1.152 0 0 0-.208-1.81 13.009 13.009 0 0 0-15.893 1.973z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1 +1 @@
|
||||
<svg viewBox="0 0 384 512"><path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z" fill="white" /></svg>
|
||||
<svg viewBox="0 0 384 512"><path fill="#fff" d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/></svg>
|
||||
|
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 492 B |
1
flutter/assets/pinned.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="358.441 560.459 32 32"><path fill="none" d="M358.441 560.459h32v32h-32v-32Z"/><path d="m371.985 579.94-5.191 5.191a.726.726 0 0 1-1.025-1.026l5.191-5.19-2.333-2.333c-1.132-1.131-.755-2.152.841-2.277l1.449-.114c.399-.032.952-.287 1.235-.57l2.909-2.908a.726.726 0 0 0 0-1.025l-.257-.257a1.089 1.089 0 0 1 0-1.538 1.089 1.089 0 0 1 1.538 0l6.664 6.665a1.087 1.087 0 1 1-1.538 1.537l-.256-.256a.725.725 0 0 0-1.025 0l-2.908 2.908c-.283.283-.538.837-.57 1.236l-.114 1.449c-.125 1.596-1.146 1.972-2.278.841l-2.332-2.333Z"/></svg>
|
||||
|
After Width: | Height: | Size: 625 B |
1
flutter/assets/rec.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="633.685 521.772 32 32"><path fill="none" d="M633.685 521.772h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M638.136 537.772a2.972 2.972 0 0 1 2.97-2.97 2.972 2.972 0 0 1 2.97 2.97 2.972 2.972 0 0 1-2.97 2.97 2.972 2.972 0 0 1-2.97-2.97Zm17.155 3.24a.818.818 0 0 0 .283-.052.693.693 0 0 0 .243-.15.603.603 0 0 0 .161-.243.743.743 0 0 0 0-.566.681.681 0 0 0-.408-.383.799.799 0 0 0-.279-.048h-2.575v-1.103h1.743a.844.844 0 0 0 .284-.052.69.69 0 0 0 .404-.389.728.728 0 0 0 0-.56.689.689 0 0 0-.404-.389.844.844 0 0 0-.284-.052h-1.743v-1.052h2.575a.777.777 0 0 0 .522-.195.687.687 0 0 0 .22-.518.74.74 0 0 0-.055-.283.603.603 0 0 0-.161-.243.696.696 0 0 0-.243-.151.841.841 0 0 0-.283-.051h-3.325a.74.74 0 0 0-.283.055.76.76 0 0 0-.247.154.734.734 0 0 0-.221.541v4.98a.764.764 0 0 0 .217.533.74.74 0 0 0 .534.217h3.325Zm5.921-5.447a.6.6 0 0 0-.037-.118 1.013 1.013 0 0 0-.062-.106.635.635 0 0 0-.077-.096l-.089-.077a3.669 3.669 0 0 0-.474-.327 2.477 2.477 0 0 0-.934-.335 3.834 3.834 0 0 0-.615-.048c-.224 0-.445.022-.665.066a2.863 2.863 0 0 0-.6.195 3.578 3.578 0 0 0-.548.305 3.12 3.12 0 0 0-.471.405 3.15 3.15 0 0 0-.386.485 3.245 3.245 0 0 0-.474 1.177 3.757 3.757 0 0 0 0 1.357c.04.214.099.42.18.618.081.199.18.39.294.57.114.173.243.339.386.486.144.151.302.287.475.405.173.117.357.22.548.305.195.084.397.147.607.187a3.247 3.247 0 0 0 1.732-.117c.169-.059.331-.136.489-.225a3.26 3.26 0 0 0 .478-.323.795.795 0 0 0 .184-.261.673.673 0 0 0 .059-.287.782.782 0 0 0-.048-.273.594.594 0 0 0-.143-.235.712.712 0 0 0-.449-.217.816.816 0 0 0-.511.155 2.906 2.906 0 0 1-.283.183 2.476 2.476 0 0 1-.276.129 1.593 1.593 0 0 1-.578.103 1.598 1.598 0 0 1-.96-.309 1.866 1.866 0 0 1-.639-.831 2.052 2.052 0 0 1-.1-1.133c.022-.117.055-.235.1-.345a1.832 1.832 0 0 1 .937-1.005 1.538 1.538 0 0 1 .662-.143 2.6 2.6 0 0 1 .412.033.786.786 0 0 1 .21.063 1.702 1.702 0 0 1 .474.305c.078.062.166.11.258.14a.65.65 0 0 0 .279.036.8.8 0 0 0 .247-.062.728.728 0 0 0 .375-.368.671.671 0 0 0 .055-.272c0-.033 0-.066-.004-.103a.747.747 0 0 0-.018-.092Zm-12.972 3.233h-.555v1.464a.762.762 0 0 1-.055.283.764.764 0 0 1-.155.246.748.748 0 0 1-.246.166.816.816 0 0 1-.295.055.758.758 0 0 1-.533-.217.737.737 0 0 1-.217-.533v-4.98c0-.099.019-.199.055-.294a.752.752 0 0 1 .166-.247.745.745 0 0 1 .246-.154.746.746 0 0 1 .283-.055h1.324c.21 0 .416.025.622.073.184.044.364.11.533.202.166.092.32.206.46.335a2.013 2.013 0 0 1 .559.956c.052.199.077.405.077.611 0 .195-.025.39-.077.577-.048.177-.121.35-.217.504a2.019 2.019 0 0 1-.504.555l.889 1.539a.758.758 0 0 1 .094.273.766.766 0 0 1-.011.29.74.74 0 0 1-.358.462.775.775 0 0 1-.57.078.73.73 0 0 1-.264-.132.78.78 0 0 1-.191-.221l-1.06-1.836Zm.018-2.825c.081 0 .166.011.247.03a.7.7 0 0 1 .438.32.662.662 0 0 1 .066.165c.018.07.029.147.029.221a.602.602 0 0 1-.088.327.579.579 0 0 1-.107.129.94.94 0 0 1-.154.103.93.93 0 0 1-.431.088h-.573v-1.383h.573Z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
1
flutter/assets/record_screen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="800" height="800" viewBox="0 0 415 415"><path d="M174.848 188.711c2.407-2.068 3.577-5.109 3.577-9.297 0-4.133-1.193-7.307-3.647-9.701-2.431-2.369-6.148-3.571-11.048-3.571h-15.915v25.73h15.576c5.161 0 9.016-1.063 11.457-3.161z"/><path d="M0 65.241v284.518h415V65.241H0zm70.675 173.012c-18.293 0-33.123-14.83-33.123-33.123s14.83-33.123 33.123-33.123 33.123 14.83 33.123 33.123-14.83 33.123-33.123 33.123zm136.093 11.303h-22.422l-.411-.328c-2.099-1.679-3.467-4.433-4.067-8.185-.552-3.451-.832-6.769-.832-9.859v-6.979c0-4.492-1.212-8.003-3.602-10.435-2.417-2.456-5.779-3.65-10.28-3.65h-17.337v39.437H125.03v-101.66h38.701c11.546 0 20.743 2.699 27.335 8.023 6.688 5.403 10.078 13.012 10.078 22.614 0 5.404-1.442 10.124-4.285 14.028-2.217 3.044-5.267 5.647-9.089 7.767 4.433 1.864 7.785 4.556 9.99 8.029 2.696 4.247 4.063 9.533 4.063 15.711v7.25c0 2.622.361 5.407 1.074 8.278.66 2.664 1.774 4.637 3.309 5.864l.563.451v3.644zm80.567 0h-70.558v-101.66h70.422v18.246h-47.636v21.801h40.859v18.246h-40.859v25.121h47.771v18.246zm86.866-66.363-.552 1.65h-21.824v-1.5c0-6.092-1.443-10.781-4.29-13.937-2.805-3.111-7.331-4.688-13.454-4.688-5.458 0-9.671 2.155-12.879 6.589-3.273 4.522-4.933 10.41-4.933 17.501v19.699c0 7.167 1.743 13.091 5.182 17.607 3.39 4.452 7.854 6.617 13.646 6.617 5.714 0 9.953-1.507 12.602-4.479 2.693-3.022 4.059-7.668 4.059-13.808v-1.5h21.756l.552 1.65.004.23c.187 11.009-3.245 19.89-10.199 26.396-6.92 6.473-16.601 9.755-28.772 9.755-12.247 0-22.344-4.006-30.01-11.906-7.655-7.887-11.537-18.155-11.537-30.521v-19.583c0-12.311 3.785-22.573 11.25-30.504 7.488-7.957 17.34-11.991 29.28-11.991 12.524 0 22.485 3.289 29.605 9.776 7.166 6.531 10.705 15.519 10.519 26.714l-.005.233z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1,3 +1 @@
|
||||
<svg viewBox="0 0 347.97 347.97">
|
||||
<path fill="#3F7D46" d="m317.31 54.367c-59.376 0-104.86-16.964-143.33-54.367-38.461 37.403-83.947 54.367-143.32 54.367 0 97.405-20.155 236.94 143.32 293.6 163.48-56.666 143.33-196.2 143.33-293.6zm-155.2 171.41-47.749-47.756 21.379-21.378 26.37 26.376 50.121-50.122 21.378 21.378-71.499 71.502z"/>
|
||||
</svg>
|
||||
<svg viewBox="0 0 347.97 347.97"><path fill="#3F7D46" d="M317.31 54.367c-59.376 0-104.86-16.964-143.33-54.367-38.461 37.403-83.947 54.367-143.32 54.367 0 97.405-20.155 236.94 143.32 293.6 163.48-56.666 143.33-196.2 143.33-293.6zm-155.2 171.41-47.749-47.756 21.379-21.378 26.37 26.376 50.121-50.122 21.378 21.378-71.499 71.502z"/></svg>
|
||||
|
Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 335 B |
@@ -1 +1 @@
|
||||
<svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="#3f7d46" stroke="#3f7d46" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z" fill="#fff"/></g></svg>
|
||||
<svg viewBox="0 0 347.97 347.97"><path fill="#3f7d46" stroke="#3f7d46" stroke-width="14.827" d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z"/><path fill="#fff" d="m231.442 247.498-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z"/></svg>
|
||||
|
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 754 B |
1
flutter/assets/unpinned.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32pt" height="32pt" style="isolation:isolate" viewBox="358.441 521.772 32 32"><path fill="none" d="M358.441 521.772h32v32h-32v-32Z"/><path d="M375.166 539.539v7.34a.726.726 0 0 1-1.45 0v-7.34h-3.299c-1.6 0-2.056-.988-1.016-2.205l.944-1.106c.26-.304.471-.876.471-1.276v-4.113c0-.4-.325-.725-.725-.725h-.362a1.088 1.088 0 0 1 0-2.175h9.424a1.088 1.088 0 0 1 0 2.175h-.362a.725.725 0 0 0-.725.725v4.113c0 .4.211.972.47 1.276l.945 1.106c1.039 1.217.584 2.205-1.017 2.205h-3.298Z"/></svg>
|
||||
|
After Width: | Height: | Size: 530 B |
1
flutter/assets/voice_call.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path d="M608 160c141.16 0 256 114.84 256 256 0 17.67 14.33 32 32 32s32-14.33 32-32c0-85.48-33.29-165.83-93.73-226.27C773.83 129.29 693.47 96 608 96c-17.67 0-32 14.33-32 32s14.33 32 32 32zm-24 168c61.76 0 112 50.24 112 112 0 17.67 14.33 32 32 32s32-14.33 32-32c0-97.05-78.95-176-176-176-17.67 0-32 14.33-32 32s14.33 32 32 32z"/><path d="M808.3 561.21c-12.76-3.83-25.7-6.2-38.46-7.03-60.3-4.5-116.45 18.9-146.55 61.08-22.6 31.67-45.66 50.01-68.52 54.5-17.71 3.48-33.12-1.7-45.49-5.85-2.66-.9-5.18-1.74-7.68-2.49-93.84-28.17-156.49-108.42-155.9-199.7.16-24.14 16.38-45.98 42.34-56.99 43.75-18.56 77.35-54 92.17-97.22 7.02-20.48 9.65-41.57 7.8-62.68-2.66-31.78-15.1-61.85-35.96-86.96-21.1-25.39-49.51-44-82.16-53.8-4.07-1.22-8.22-2.31-12.35-3.23-30.63-6.87-62.7-4.49-92.73 6.88-29.24 11.07-54.56 29.86-73.23 54.33a476.073 476.073 0 0 0-36.42 55.34 477.675 477.675 0 0 0-17.24 33.81C109.84 312.17 95.73 376.76 96 443.15c.26 63.78 13.7 126.26 39.95 185.7 27.55 62.39 69.3 119.84 120.74 166.11 54.14 48.71 117.6 84.85 188.63 107.4C499.02 919.41 554.33 928 610.21 928c10.99 0 22.01-.33 33.03-1 17.64-1.07 31.08-16.23 30.01-33.87-1.07-17.64-16.22-31.08-33.87-30.01-59.19 3.57-117.96-3.75-174.69-21.76C342.78 802.66 244.31 715.78 194.5 603c-46.76-105.9-46.21-221.33 1.55-325.03 4.55-9.87 9.57-19.72 14.92-29.26 9.29-16.54 19.89-32.64 31.5-47.86 23.47-30.77 64.09-45.87 101.07-37.58 2.66.6 5.33 1.3 7.95 2.08 40.93 12.29 69.48 45.6 72.75 84.86 0 .05.01.1.01.15 1.07 12.15-.47 24.39-4.58 36.37-8.94 26.06-29.58 47.59-56.63 59.07-23.58 10.01-43.63 25.72-57.99 45.45-15.12 20.78-23.2 45-23.36 70.05-.37 57.15 19 114.29 54.53 160.91 36.46 47.83 87.28 82.58 146.96 100.49 1.5.45 3.44 1.1 5.69 1.86 29.79 10.01 108.9 36.59 186.49-72.13 16.95-23.75 52.2-37.26 89.81-34.42l.36.03c7.97.51 16.17 2.02 24.34 4.47 22.12 6.64 42.04 25.38 56.11 52.77 16.97 33.04 21.71 72.53 12.1 100.56l-.16.47c-5.54 16.05-17.78 29.48-34.47 37.8-15.82 7.89-22.24 27.1-14.36 42.92s27.1 22.24 42.92 14.36c31.78-15.85 55.36-42.19 66.41-74.2l.18-.53c15.23-44.4 9.22-102.11-15.68-150.61-22.07-43.02-55.68-73.15-94.62-84.84z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
1
flutter/assets/voice_call_waiting.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path d="M561 362.67h-98V463h98V362.67zm200.67 0H661.33V463h100.33V362.67zM911 687c-62.22 0-121.33-9.33-177.33-28-20.22-6.22-37.33-2.34-51.33 11.67L572.67 780.34c-70-35.78-133.39-82.06-190.17-138.84S279.45 521.33 243.67 451.33l109.67-109.67c14-14 17.89-31.11 11.67-51.34-18.67-56-28-115.11-28-177.33 0-14-4.67-25.67-14-35-9.33-9.33-21-14-35-14H113c-14 0-25.67 4.67-35 14-9.33 9.33-14 21-14 35 0 112 21.39 220.11 64.17 324.33 42.78 104.22 103.83 196 183.17 275.34 79.33 79.34 171.1 140.4 275.33 183.17C690.89 938.61 799 960 911 960c14 0 25.67-4.67 35-14 9.33-9.33 14-21 14-35V736c0-14-4.67-25.67-14-35-9.33-9.33-21-14-35-14zm-51.33-224H960V362.67H859.67V463z"/></svg>
|
||||
|
After Width: | Height: | Size: 768 B |
@@ -1 +1 @@
|
||||
<svg viewBox="0 0 448 512"><path d="M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z" fill="white" />
|
||||
<svg viewBox="0 0 448 512"><path fill="#fff" d="m0 93.7 183.6-25.3v177.4H0V93.7zm0 324.6 183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z"/></svg>
|
||||
|
Before Width: | Height: | Size: 189 B After Width: | Height: | Size: 192 B |
@@ -49,6 +49,11 @@ int androidVersion = 0;
|
||||
int windowsBuildNumber = 0;
|
||||
DesktopType? desktopType;
|
||||
|
||||
/// Check if the app is running with single view mode.
|
||||
bool isSingleViewApp() {
|
||||
return desktopType == DesktopType.cm;
|
||||
}
|
||||
|
||||
/// * debug or test only, DO NOT enable in release build
|
||||
bool isTest = false;
|
||||
|
||||
@@ -212,6 +217,9 @@ class MyTheme {
|
||||
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
|
||||
)
|
||||
: null,
|
||||
checkboxTheme: const CheckboxThemeData(
|
||||
checkColor: MaterialStatePropertyAll(dark)
|
||||
),
|
||||
).copyWith(
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
ColorThemeExtension.dark,
|
||||
@@ -331,6 +339,9 @@ closeConnection({String? id}) {
|
||||
}
|
||||
|
||||
void window_on_top(int? id) {
|
||||
if (!isDesktop) {
|
||||
return;
|
||||
}
|
||||
if (id == null) {
|
||||
// main window
|
||||
windowManager.restore();
|
||||
@@ -367,20 +378,25 @@ class Dialog<T> {
|
||||
}
|
||||
}
|
||||
|
||||
class OverlayKeyState {
|
||||
final _overlayKey = GlobalKey<OverlayState>();
|
||||
|
||||
/// use global overlay by default
|
||||
OverlayState? get state =>
|
||||
_overlayKey.currentState ?? globalKey.currentState?.overlay;
|
||||
|
||||
GlobalKey<OverlayState>? get key => _overlayKey;
|
||||
}
|
||||
|
||||
class OverlayDialogManager {
|
||||
OverlayState? _overlayState;
|
||||
final Map<String, Dialog> _dialogs = {};
|
||||
var _overlayKeyState = OverlayKeyState();
|
||||
int _tagCount = 0;
|
||||
|
||||
OverlayEntry? _mobileActionsOverlayEntry;
|
||||
|
||||
/// By default OverlayDialogManager use global overlay
|
||||
OverlayDialogManager() {
|
||||
_overlayState = globalKey.currentState?.overlay;
|
||||
}
|
||||
|
||||
void setOverlayState(OverlayState? overlayState) {
|
||||
_overlayState = overlayState;
|
||||
void setOverlayState(OverlayKeyState overlayKeyState) {
|
||||
_overlayKeyState = overlayKeyState;
|
||||
}
|
||||
|
||||
void dismissAll() {
|
||||
@@ -404,7 +420,7 @@ class OverlayDialogManager {
|
||||
bool useAnimation = true,
|
||||
bool forceGlobal = false}) {
|
||||
final overlayState =
|
||||
forceGlobal ? globalKey.currentState?.overlay : _overlayState;
|
||||
forceGlobal ? globalKey.currentState?.overlay : _overlayKeyState.state;
|
||||
|
||||
if (overlayState == null) {
|
||||
return Future.error(
|
||||
@@ -487,12 +503,14 @@ class OverlayDialogManager {
|
||||
Offstage(
|
||||
offstage: !showCancel,
|
||||
child: Center(
|
||||
child: TextButton(
|
||||
style: flatButtonStyle,
|
||||
onPressed: cancel,
|
||||
child: Text(translate('Cancel'),
|
||||
style:
|
||||
const TextStyle(color: MyTheme.accent)))))
|
||||
child: isDesktop
|
||||
? dialogButton('Cancel', onPressed: cancel)
|
||||
: TextButton(
|
||||
style: flatButtonStyle,
|
||||
onPressed: cancel,
|
||||
child: Text(translate('Cancel'),
|
||||
style: const TextStyle(
|
||||
color: MyTheme.accent)))))
|
||||
])),
|
||||
onCancel: showCancel ? cancel : null,
|
||||
);
|
||||
@@ -508,7 +526,8 @@ class OverlayDialogManager {
|
||||
|
||||
void showMobileActionsOverlay({FFI? ffi}) {
|
||||
if (_mobileActionsOverlayEntry != null) return;
|
||||
if (_overlayState == null) return;
|
||||
final overlayState = _overlayKeyState.state;
|
||||
if (overlayState == null) return;
|
||||
|
||||
// compute overlay position
|
||||
final screenW = MediaQuery.of(globalKey.currentContext!).size.width;
|
||||
@@ -534,7 +553,7 @@ class OverlayDialogManager {
|
||||
onHidePressed: () => hideMobileActionsOverlay(),
|
||||
);
|
||||
});
|
||||
_overlayState!.insert(overlay);
|
||||
overlayState.insert(overlay);
|
||||
_mobileActionsOverlayEntry = overlay;
|
||||
}
|
||||
|
||||
@@ -618,6 +637,7 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
if (!scopeNode.hasFocus) scopeNode.requestFocus();
|
||||
});
|
||||
const double padding = 16;
|
||||
bool tabTapped = false;
|
||||
return FocusScope(
|
||||
node: scopeNode,
|
||||
autofocus: true,
|
||||
@@ -627,13 +647,15 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
onCancel?.call();
|
||||
}
|
||||
return KeyEventResult.handled; // avoid TextField exception on escape
|
||||
} else if (onSubmit != null &&
|
||||
} else if (!tabTapped &&
|
||||
onSubmit != null &&
|
||||
key.logicalKey == LogicalKeyboardKey.enter) {
|
||||
if (key is RawKeyDownEvent) onSubmit?.call();
|
||||
return KeyEventResult.handled;
|
||||
} else if (key.logicalKey == LogicalKeyboardKey.tab) {
|
||||
if (key is RawKeyDownEvent) {
|
||||
scopeNode.nextFocus();
|
||||
tabTapped = true;
|
||||
}
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
@@ -642,8 +664,9 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
child: AlertDialog(
|
||||
scrollable: true,
|
||||
title: title,
|
||||
contentPadding: EdgeInsets.fromLTRB(
|
||||
contentPadding ?? padding, 25, contentPadding ?? padding, 10),
|
||||
titlePadding: EdgeInsets.fromLTRB(padding, 24, padding, 0),
|
||||
contentPadding: EdgeInsets.fromLTRB(contentPadding ?? padding, 25,
|
||||
contentPadding ?? padding, actions is List ? 10 : padding),
|
||||
content: ConstrainedBox(
|
||||
constraints: contentBoxConstraints,
|
||||
child: Theme(
|
||||
@@ -653,7 +676,7 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
child: content),
|
||||
),
|
||||
actions: actions,
|
||||
actionsPadding: EdgeInsets.fromLTRB(0, 0, padding, padding),
|
||||
actionsPadding: EdgeInsets.fromLTRB(padding, 0, padding, padding),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -661,7 +684,7 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
|
||||
void msgBox(String id, String type, String title, String text, String link,
|
||||
OverlayDialogManager dialogManager,
|
||||
{bool? hasCancel}) {
|
||||
{bool? hasCancel, ReconnectHandle? reconnect}) {
|
||||
dialogManager.dismissAll();
|
||||
List<Widget> buttons = [];
|
||||
bool hasOk = false;
|
||||
@@ -701,6 +724,13 @@ void msgBox(String id, String type, String title, String text, String link,
|
||||
dialogManager.dismissAll();
|
||||
}));
|
||||
}
|
||||
if (reconnect != null && title == "Connection Error") {
|
||||
buttons.insert(
|
||||
0,
|
||||
dialogButton('Reconnect', isOutline: true, onPressed: () {
|
||||
reconnect(dialogManager, id, false);
|
||||
}));
|
||||
}
|
||||
if (link.isNotEmpty) {
|
||||
buttons.insert(0, dialogButton('JumpLink', onPressed: jumplink));
|
||||
}
|
||||
@@ -1393,13 +1423,14 @@ bool callUniLinksUriHandler(Uri uri) {
|
||||
connectMainDesktop(String id,
|
||||
{required bool isFileTransfer,
|
||||
required bool isTcpTunneling,
|
||||
required bool isRDP}) async {
|
||||
required bool isRDP,
|
||||
bool? forceRelay}) async {
|
||||
if (isFileTransfer) {
|
||||
await rustDeskWinManager.newFileTransfer(id);
|
||||
await rustDeskWinManager.newFileTransfer(id, forceRelay: forceRelay);
|
||||
} else if (isTcpTunneling || isRDP) {
|
||||
await rustDeskWinManager.newPortForward(id, isRDP);
|
||||
await rustDeskWinManager.newPortForward(id, isRDP, forceRelay: forceRelay);
|
||||
} else {
|
||||
await rustDeskWinManager.newRemoteDesktop(id);
|
||||
await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1410,7 +1441,8 @@ connectMainDesktop(String id,
|
||||
connect(BuildContext context, String id,
|
||||
{bool isFileTransfer = false,
|
||||
bool isTcpTunneling = false,
|
||||
bool isRDP = false}) async {
|
||||
bool isRDP = false,
|
||||
bool forceRelay = false}) async {
|
||||
if (id == '') return;
|
||||
id = id.replaceAll(' ', '');
|
||||
assert(!(isFileTransfer && isTcpTunneling && isRDP),
|
||||
@@ -1418,18 +1450,18 @@ connect(BuildContext context, String id,
|
||||
|
||||
if (isDesktop) {
|
||||
if (desktopType == DesktopType.main) {
|
||||
await connectMainDesktop(
|
||||
id,
|
||||
isFileTransfer: isFileTransfer,
|
||||
isTcpTunneling: isTcpTunneling,
|
||||
isRDP: isRDP,
|
||||
);
|
||||
await connectMainDesktop(id,
|
||||
isFileTransfer: isFileTransfer,
|
||||
isTcpTunneling: isTcpTunneling,
|
||||
isRDP: isRDP,
|
||||
forceRelay: forceRelay);
|
||||
} else {
|
||||
await rustDeskWinManager.call(WindowType.Main, kWindowConnect, {
|
||||
'id': id,
|
||||
'isFileTransfer': isFileTransfer,
|
||||
'isTcpTunneling': isTcpTunneling,
|
||||
'isRDP': isRDP,
|
||||
"forceRelay": forceRelay,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -1723,3 +1755,55 @@ Future<void> updateSystemWindowTheme() async {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// macOS only
|
||||
///
|
||||
/// Note: not found a general solution for rust based AVFoundation bingding.
|
||||
/// [AVFoundation] crate has compile error.
|
||||
const kMacOSPermChannel = MethodChannel("org.rustdesk.rustdesk/macos");
|
||||
|
||||
enum PermissionAuthorizeType {
|
||||
undetermined,
|
||||
authorized,
|
||||
denied, // and restricted
|
||||
}
|
||||
|
||||
Future<PermissionAuthorizeType> osxCanRecordAudio() async {
|
||||
int res = await kMacOSPermChannel.invokeMethod("canRecordAudio");
|
||||
print(res);
|
||||
if (res > 0) {
|
||||
return PermissionAuthorizeType.authorized;
|
||||
} else if (res == 0) {
|
||||
return PermissionAuthorizeType.undetermined;
|
||||
} else {
|
||||
return PermissionAuthorizeType.denied;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> osxRequestAudio() async {
|
||||
return await kMacOSPermChannel.invokeMethod("requestRecordAudio");
|
||||
}
|
||||
|
||||
class DraggableNeverScrollableScrollPhysics extends ScrollPhysics {
|
||||
/// Creates scroll physics that does not let the user scroll.
|
||||
const DraggableNeverScrollableScrollPhysics({super.parent});
|
||||
|
||||
@override
|
||||
DraggableNeverScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return DraggableNeverScrollableScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldAcceptUserOffset(ScrollMetrics position) {
|
||||
// TODO: find a better solution to check if the offset change is caused by the scrollbar.
|
||||
// Workaround: when dragging with the scrollbar, it always triggers an [IdleScrollActivity].
|
||||
if (position is ScrollPositionWithSingleContext) {
|
||||
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
|
||||
return position.activity is IdleScrollActivity;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get allowImplicitScrolling => false;
|
||||
}
|
||||
|
||||
@@ -43,13 +43,10 @@ class _AddressBookState extends State<AddressBook> {
|
||||
return Obx(() {
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
return Center(
|
||||
child: InkWell(
|
||||
onTap: loginDialog,
|
||||
child: Text(
|
||||
translate("Login"),
|
||||
style: const TextStyle(decoration: TextDecoration.underline),
|
||||
),
|
||||
),
|
||||
child: ElevatedButton(
|
||||
onPressed: loginDialog,
|
||||
child: Text(translate("Login"))
|
||||
)
|
||||
);
|
||||
} else {
|
||||
if (gFFI.abModel.abLoading.value) {
|
||||
@@ -389,7 +386,7 @@ class _AddressBookState extends State<AddressBook> {
|
||||
errorText: msg.isEmpty ? null : translate(msg),
|
||||
),
|
||||
controller: controller,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
autofocus: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -95,10 +95,31 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
color: Theme.of(context).colorScheme.primary)),
|
||||
messageOptions: MessageOptions(
|
||||
showOtherUsersAvatar: false,
|
||||
showTime: true,
|
||||
currentUserTextColor: Colors.white,
|
||||
textColor: Colors.white,
|
||||
maxWidth: constraints.maxWidth * 0.7,
|
||||
messageTextBuilder: (message, _, __) {
|
||||
final isOwnMessage =
|
||||
message.user.id == currentUser.id;
|
||||
return Column(
|
||||
crossAxisAlignment: isOwnMessage
|
||||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(message.text,
|
||||
style: TextStyle(color: Colors.white)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
"${message.createdAt.hour}:${message.createdAt.minute}",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
messageDecorationBuilder: (_, __, ___) =>
|
||||
defaultMessageDecoration(
|
||||
color: MyTheme.accent80,
|
||||
|
||||
@@ -1,18 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
|
||||
abstract class ValidationRule {
|
||||
String get name;
|
||||
bool validate(String value);
|
||||
}
|
||||
|
||||
class LengthRangeValidationRule extends ValidationRule {
|
||||
final int _min;
|
||||
final int _max;
|
||||
|
||||
LengthRangeValidationRule(this._min, this._max);
|
||||
|
||||
@override
|
||||
String get name => translate('length %min% to %max%')
|
||||
.replaceAll('%min%', _min.toString())
|
||||
.replaceAll('%max%', _max.toString());
|
||||
|
||||
@override
|
||||
bool validate(String value) {
|
||||
return value.length >= _min && value.length <= _max;
|
||||
}
|
||||
}
|
||||
|
||||
class RegexValidationRule extends ValidationRule {
|
||||
final String _name;
|
||||
final RegExp _regex;
|
||||
|
||||
RegexValidationRule(this._name, this._regex);
|
||||
|
||||
@override
|
||||
String get name => translate(_name);
|
||||
|
||||
@override
|
||||
bool validate(String value) {
|
||||
return value.isNotEmpty ? value.contains(_regex) : false;
|
||||
}
|
||||
}
|
||||
|
||||
void changeIdDialog() {
|
||||
var newId = "";
|
||||
var msg = "";
|
||||
var isInProgress = false;
|
||||
TextEditingController controller = TextEditingController();
|
||||
final RxString rxId = controller.text.trim().obs;
|
||||
|
||||
final rules = [
|
||||
RegexValidationRule('starts with a letter', RegExp(r'^[a-zA-Z]')),
|
||||
LengthRangeValidationRule(6, 16),
|
||||
RegexValidationRule('allowed characters', RegExp(r'^\w*$'))
|
||||
];
|
||||
|
||||
gFFI.dialogManager.show((setState, close) {
|
||||
submit() async {
|
||||
debugPrint("onSubmit");
|
||||
newId = controller.text.trim();
|
||||
|
||||
final Iterable violations = rules.where((r) => !r.validate(newId));
|
||||
if (violations.isNotEmpty) {
|
||||
setState(() {
|
||||
msg =
|
||||
'${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
msg = "";
|
||||
isInProgress = true;
|
||||
@@ -31,7 +87,7 @@ void changeIdDialog() {
|
||||
}
|
||||
setState(() {
|
||||
isInProgress = false;
|
||||
msg = translate(status);
|
||||
msg = '${translate('Prompt')}: ${translate(status)}';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,18 +102,47 @@ void changeIdDialog() {
|
||||
),
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('Your new ID'),
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: msg.isEmpty ? null : translate(msg)),
|
||||
errorText: msg.isEmpty ? null : translate(msg),
|
||||
suffixText: '${rxId.value.length}/16',
|
||||
suffixStyle: const TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
inputFormatters: [
|
||||
LengthLimitingTextInputFormatter(16),
|
||||
// FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
|
||||
],
|
||||
maxLength: 16,
|
||||
controller: controller,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
autofocus: true,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
rxId.value = value.trim();
|
||||
msg = '';
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4.0,
|
||||
height: 8.0,
|
||||
),
|
||||
Obx(() => Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 4,
|
||||
children: rules.map((e) {
|
||||
var checked = e.validate(rxId.value);
|
||||
return Chip(
|
||||
label: Text(
|
||||
e.name,
|
||||
style: TextStyle(
|
||||
color: checked
|
||||
? const Color(0xFF0A9471)
|
||||
: Color.fromARGB(255, 198, 86, 157)),
|
||||
),
|
||||
backgroundColor: checked
|
||||
? const Color(0xFFD0F7ED)
|
||||
: Color.fromARGB(255, 247, 205, 232));
|
||||
}).toList(),
|
||||
)),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Offstage(
|
||||
offstage: !isInProgress, child: const LinearProgressIndicator())
|
||||
@@ -99,7 +184,7 @@ void changeWhiteList({Function()? callback}) async {
|
||||
errorText: msg.isEmpty ? null : translate(msg),
|
||||
),
|
||||
controller: controller,
|
||||
focusNode: FocusNode()..requestFocus()),
|
||||
autofocus: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -186,7 +271,7 @@ Future<String> changeDirectAccessPort(
|
||||
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
||||
],
|
||||
controller: controller,
|
||||
focusNode: FocusNode()..requestFocus()),
|
||||
autofocus: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -197,24 +197,25 @@ class _WidgetOPState extends State<WidgetOP> {
|
||||
_failedMsg = '';
|
||||
}
|
||||
return Offstage(
|
||||
offstage:
|
||||
_failedMsg.isEmpty && widget.curOP.value != widget.config.op,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
_stateMsg,
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
_failedMsg,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.red,
|
||||
),
|
||||
offstage:
|
||||
_failedMsg.isEmpty && widget.curOP.value != widget.config.op,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: '$_stateMsg ',
|
||||
style:
|
||||
DefaultTextStyle.of(context).style.copyWith(fontSize: 12),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: _failedMsg,
|
||||
style: DefaultTextStyle.of(context).style.copyWith(
|
||||
fontSize: 14,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
@@ -323,13 +324,13 @@ class LoginWidgetUserPass extends StatelessWidget {
|
||||
children: [
|
||||
const SizedBox(height: 8.0),
|
||||
DialogTextField(
|
||||
title: '${translate("Username")}:',
|
||||
title: translate("Username"),
|
||||
controller: username,
|
||||
focusNode: userFocusNode,
|
||||
prefixIcon: Icon(Icons.account_circle_outlined),
|
||||
errorText: usernameMsg),
|
||||
DialogTextField(
|
||||
title: '${translate("Password")}:',
|
||||
title: translate("Password"),
|
||||
obscureText: true,
|
||||
controller: pass,
|
||||
prefixIcon: Icon(Icons.lock_outline),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../consts.dart';
|
||||
@@ -96,12 +97,14 @@ class DraggableChatWindow extends StatelessWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
|
||||
child: Row(children: [
|
||||
Icon(Icons.chat_bubble_outline,
|
||||
size: 20, color: Theme.of(context).colorScheme.primary),
|
||||
SizedBox(width: 6),
|
||||
Text(translate("Chat"))
|
||||
])),
|
||||
child: Obx(() => Opacity(
|
||||
opacity: chatModel.isWindowFocus.value ? 1.0 : 0.4,
|
||||
child: Row(children: [
|
||||
Icon(Icons.chat_bubble_outline,
|
||||
size: 20, color: Theme.of(context).colorScheme.primary),
|
||||
SizedBox(width: 6),
|
||||
Text(translate("Chat"))
|
||||
])))),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(2),
|
||||
child: ActionIcon(
|
||||
@@ -304,15 +307,17 @@ class _DraggableState extends State<Draggable> {
|
||||
if (widget.checkKeyboard) {
|
||||
checkKeyboard();
|
||||
}
|
||||
if (widget.checkKeyboard) {
|
||||
if (widget.checkScreenSize) {
|
||||
checkScreenSize();
|
||||
}
|
||||
return Positioned(
|
||||
top: _position.dy,
|
||||
left: _position.dx,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: widget.builder(context, onPanUpdate));
|
||||
return Stack(children: [
|
||||
Positioned(
|
||||
top: _position.dy,
|
||||
left: _position.dx,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: widget.builder(context, onPanUpdate))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,3 +371,55 @@ class QualityMonitor extends StatelessWidget {
|
||||
)
|
||||
: const SizedBox.shrink()));
|
||||
}
|
||||
|
||||
class BlockableOverlayState extends OverlayKeyState {
|
||||
final _middleBlocked = false.obs;
|
||||
|
||||
VoidCallback? onMiddleBlockedClick; // to-do use listener
|
||||
|
||||
RxBool get middleBlocked => _middleBlocked;
|
||||
|
||||
void addMiddleBlockedListener(void Function(bool) cb) {
|
||||
_middleBlocked.listen(cb);
|
||||
}
|
||||
|
||||
void setMiddleBlocked(bool blocked) {
|
||||
if (blocked != _middleBlocked.value) {
|
||||
_middleBlocked.value = blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BlockableOverlay extends StatelessWidget {
|
||||
final Widget underlying;
|
||||
final List<OverlayEntry>? upperLayer;
|
||||
|
||||
final BlockableOverlayState state;
|
||||
|
||||
BlockableOverlay(
|
||||
{required this.underlying, required this.state, this.upperLayer});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final initialEntries = [
|
||||
OverlayEntry(builder: (_) => underlying),
|
||||
|
||||
/// middle layer
|
||||
OverlayEntry(
|
||||
builder: (context) => Obx(() => Listener(
|
||||
onPointerDown: (_) {
|
||||
state.onMiddleBlockedClick?.call();
|
||||
},
|
||||
child: Container(
|
||||
color:
|
||||
state.middleBlocked.value ? Colors.transparent : null)))),
|
||||
];
|
||||
|
||||
if (upperLayer != null) {
|
||||
initialEntries.addAll(upperLayer!);
|
||||
}
|
||||
|
||||
/// set key
|
||||
return Overlay(key: state.key, initialEntries: initialEntries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
child: Form(
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
autofocus: true,
|
||||
decoration:
|
||||
const InputDecoration(border: OutlineInputBorder()),
|
||||
),
|
||||
@@ -996,14 +996,11 @@ void _rdpDialog(String id) async {
|
||||
Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
"${translate('Port')}:",
|
||||
textAlign: TextAlign.start,
|
||||
).marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
textAlign: TextAlign.right,
|
||||
).marginOnly(right: 10)),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
inputFormatters: [
|
||||
@@ -1013,25 +1010,19 @@ void _rdpDialog(String id) async {
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(), hintText: '3389'),
|
||||
controller: portController,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
autofocus: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
).marginOnly(bottom: 8),
|
||||
Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
"${translate('Username')}:",
|
||||
textAlign: TextAlign.start,
|
||||
).marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
textAlign: TextAlign.right,
|
||||
).marginOnly(right: 10)),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration:
|
||||
@@ -1040,19 +1031,15 @@ void _rdpDialog(String id) async {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
).marginOnly(bottom: 8),
|
||||
Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
child: Text("${translate('Password')}:")
|
||||
.marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
"${translate('Password')}:",
|
||||
textAlign: TextAlign.right,
|
||||
).marginOnly(right: 10)),
|
||||
Expanded(
|
||||
child: Obx(() => TextField(
|
||||
obscureText: secure.value,
|
||||
@@ -1067,7 +1054,7 @@ void _rdpDialog(String id) async {
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
).marginOnly(bottom: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../models/input_model.dart';
|
||||
|
||||
class RawKeyFocusScope extends StatelessWidget {
|
||||
@@ -19,6 +20,13 @@ class RawKeyFocusScope extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final FocusOnKeyCallback? onKey;
|
||||
if (isAndroid) {
|
||||
onKey = inputModel.handleRawKeyEvent;
|
||||
} else {
|
||||
onKey = stateGlobal.grabKeyboard ? inputModel.handleRawKeyEvent : null;
|
||||
}
|
||||
|
||||
return FocusScope(
|
||||
autofocus: true,
|
||||
child: Focus(
|
||||
@@ -26,8 +34,7 @@ class RawKeyFocusScope extends StatelessWidget {
|
||||
canRequestFocus: true,
|
||||
focusNode: focusNode,
|
||||
onFocusChange: onFocusChange,
|
||||
onKey:
|
||||
stateGlobal.grabKeyboard ? inputModel.handleRawKeyEvent : null,
|
||||
onKey: onKey,
|
||||
child: child));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,18 @@ const int kMobileMaxDisplayHeight = 1280;
|
||||
const int kDesktopMaxDisplayWidth = 1920;
|
||||
const int kDesktopMaxDisplayHeight = 1080;
|
||||
|
||||
const double kDesktopFileTransferNameColWidth = 200;
|
||||
const double kDesktopFileTransferModifiedColWidth = 120;
|
||||
const double kDesktopFileTransferRowHeight = 25.0;
|
||||
const double kDesktopFileTransferHeaderHeight = 25.0;
|
||||
|
||||
// https://en.wikipedia.org/wiki/Non-breaking_space
|
||||
const int $nbsp = 0x00A0;
|
||||
|
||||
extension StringExtension on String {
|
||||
String get nonBreaking => replaceAll(' ', String.fromCharCode($nbsp));
|
||||
}
|
||||
|
||||
const Size kConnectionManagerWindowSize = Size(300, 400);
|
||||
// Tabbar transition duration, now we remove the duration
|
||||
const Duration kTabTransitionDuration = Duration.zero;
|
||||
@@ -106,6 +118,12 @@ const kRemoteImageQualityLow = 'low';
|
||||
/// [kRemoteImageQualityCustom] Custom image quality.
|
||||
const kRemoteImageQualityCustom = 'custom';
|
||||
|
||||
/// [kRemoteAudioGuestToHost] Guest to host audio mode(default).
|
||||
const kRemoteAudioGuestToHost = 'guest-to-host';
|
||||
|
||||
/// [kRemoteAudioDualWay] dual-way audio mode(default).
|
||||
const kRemoteAudioDualWay = 'dual-way';
|
||||
|
||||
const kIgnoreDpi = true;
|
||||
|
||||
/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
|
||||
|
||||
@@ -66,7 +66,8 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
_idFocusNode.addListener(() {
|
||||
_idInputFocused.value = _idFocusNode.hasFocus;
|
||||
// select all to faciliate removing text, just following the behavior of address input of chrome
|
||||
_idController.selection = TextSelection(baseOffset: 0, extentOffset: _idController.value.text.length);
|
||||
_idController.selection = TextSelection(
|
||||
baseOffset: 0, extentOffset: _idController.value.text.length);
|
||||
});
|
||||
windowManager.addListener(this);
|
||||
}
|
||||
@@ -120,7 +121,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
scrollController: _scrollController,
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
@@ -149,8 +150,11 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
/// Callback for the connect button.
|
||||
/// Connects to the selected peer.
|
||||
void onConnect({bool isFileTransfer = false}) {
|
||||
final id = _idController.id;
|
||||
connect(context, id, isFileTransfer: isFileTransfer);
|
||||
var id = _idController.id;
|
||||
var forceRelay = id.endsWith(r'/r');
|
||||
if (forceRelay) id = id.substring(0, id.length - 2);
|
||||
connect(context, id,
|
||||
isFileTransfer: isFileTransfer, forceRelay: forceRelay);
|
||||
}
|
||||
|
||||
/// UI for the remote ID TextField.
|
||||
|
||||
@@ -44,6 +44,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
var watchIsCanScreenRecording = false;
|
||||
var watchIsProcessTrust = false;
|
||||
var watchIsInputMonitoring = false;
|
||||
var watchIsCanRecordAudio = false;
|
||||
Timer? _updateTimer;
|
||||
|
||||
@override
|
||||
@@ -74,12 +75,22 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
scrollController: _leftPaneScrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: _leftPaneScrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
child: Column(
|
||||
children: [
|
||||
buildTip(context),
|
||||
buildIDBoard(context),
|
||||
buildPasswordBoard(context),
|
||||
buildHelpCards(),
|
||||
FutureBuilder<Widget>(
|
||||
future: buildHelpCards(),
|
||||
builder: (_, data) {
|
||||
if (data.hasData) {
|
||||
return data.data!;
|
||||
} else {
|
||||
return const Offstage();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -302,7 +313,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildHelpCards() {
|
||||
Future<Widget> buildHelpCards() async {
|
||||
if (updateUrl.isNotEmpty) {
|
||||
return buildInstallCard(
|
||||
"Status",
|
||||
@@ -349,6 +360,15 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
bind.mainIsInstalledDaemon(prompt: true);
|
||||
});
|
||||
}
|
||||
//// Disable microphone configuration for macOS. We will request the permission when needed.
|
||||
// else if ((await osxCanRecordAudio() !=
|
||||
// PermissionAuthorizeType.authorized)) {
|
||||
// return buildInstallCard("Permissions", "config_microphone", "Configure",
|
||||
// () async {
|
||||
// osxRequestAudio();
|
||||
// watchIsCanRecordAudio = true;
|
||||
// });
|
||||
// }
|
||||
} else if (Platform.isLinux) {
|
||||
if (bind.mainCurrentIsWayland()) {
|
||||
return buildInstallCard(
|
||||
@@ -481,6 +501,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
if (watchIsCanRecordAudio) {
|
||||
if (Platform.isMacOS) {
|
||||
Future.microtask(() async {
|
||||
if ((await osxCanRecordAudio() ==
|
||||
PermissionAuthorizeType.authorized)) {
|
||||
watchIsCanRecordAudio = false;
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
watchIsCanRecordAudio = false;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
});
|
||||
Get.put<RxBool>(svcStopped, tag: 'stop-service');
|
||||
rustDeskWinManager.registerActiveWindowListener(onActiveWindowChanged);
|
||||
@@ -523,6 +557,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
isFileTransfer: call.arguments['isFileTransfer'],
|
||||
isTcpTunneling: call.arguments['isTcpTunneling'],
|
||||
isRDP: call.arguments['isRDP'],
|
||||
forceRelay: call.arguments['forceRelay'],
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -561,13 +596,13 @@ void setPasswordDialog() async {
|
||||
});
|
||||
final pass = p0.text.trim();
|
||||
if (pass.isNotEmpty) {
|
||||
for (var r in rules) {
|
||||
if (!r.validate(pass)) {
|
||||
setState(() {
|
||||
errMsg0 = '${translate('Prompt')}: ${r.name}';
|
||||
});
|
||||
return;
|
||||
}
|
||||
final Iterable violations = rules.where((r) => !r.validate(pass));
|
||||
if (violations.isNotEmpty) {
|
||||
setState(() {
|
||||
errMsg0 =
|
||||
'${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}';
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (p1.text.trim() != pass) {
|
||||
@@ -601,9 +636,12 @@ void setPasswordDialog() async {
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: errMsg0.isNotEmpty ? errMsg0 : null),
|
||||
controller: p0,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
autofocus: true,
|
||||
onChanged: (value) {
|
||||
rxPass.value = value.trim();
|
||||
setState(() {
|
||||
errMsg0 = '';
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -627,6 +665,11 @@ void setPasswordDialog() async {
|
||||
labelText: translate('Confirmation'),
|
||||
errorText: errMsg1.isNotEmpty ? errMsg1 : null),
|
||||
controller: p1,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
errMsg1 = '';
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -128,7 +128,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
scrollController: controller,
|
||||
child: PageView(
|
||||
controller: controller,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
children: const [
|
||||
_General(),
|
||||
_Safety(),
|
||||
@@ -170,7 +170,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: tabs
|
||||
.asMap()
|
||||
@@ -234,7 +234,7 @@ class _GeneralState extends State<_General> {
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: [
|
||||
theme(),
|
||||
@@ -456,7 +456,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -650,7 +650,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
context, onChanged != null)),
|
||||
),
|
||||
],
|
||||
).paddingSymmetric(horizontal: 10),
|
||||
).paddingOnly(right: 10),
|
||||
onTap: () => onChanged?.call(value),
|
||||
))
|
||||
.toList();
|
||||
@@ -675,6 +675,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
if (usePassword) radios[0],
|
||||
if (usePassword)
|
||||
_SubLabeledWidget(
|
||||
context,
|
||||
'One-time password length',
|
||||
Row(
|
||||
children: [
|
||||
@@ -701,6 +702,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
child: _OptionCheckBox(context, 'Enable RDP', 'enable-rdp',
|
||||
enabled: enabled),
|
||||
),
|
||||
shareRdp(context, enabled),
|
||||
_OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery',
|
||||
reverse: true, enabled: enabled),
|
||||
...directIp(context),
|
||||
@@ -708,6 +710,33 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
]);
|
||||
}
|
||||
|
||||
shareRdp(BuildContext context, bool enabled) {
|
||||
onChanged(bool b) async {
|
||||
await bind.mainSetShareRdp(enable: b);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
bool value = bind.mainIsShareRdp();
|
||||
return Offstage(
|
||||
offstage: !(Platform.isWindows && bind.mainIsRdpServiceOpen()),
|
||||
child: GestureDetector(
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: value,
|
||||
onChanged: enabled ? (_) => onChanged(!value) : null)
|
||||
.marginOnly(right: 5),
|
||||
Expanded(
|
||||
child: Text(translate('Enable RDP session sharing'),
|
||||
style:
|
||||
TextStyle(color: _disabledTextColor(context, enabled))),
|
||||
)
|
||||
],
|
||||
).marginOnly(left: _kCheckBoxLeftMargin),
|
||||
onTap: enabled ? () => onChanged(!value) : null),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> directIp(BuildContext context) {
|
||||
TextEditingController controller = TextEditingController();
|
||||
update() => setState(() {});
|
||||
@@ -728,9 +757,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
controller.text = data['port'].toString();
|
||||
return Offstage(
|
||||
offstage: !enabled,
|
||||
child: Row(children: [
|
||||
_SubLabeledWidget(
|
||||
'Port',
|
||||
child: _SubLabeledWidget(
|
||||
context,
|
||||
'Port',
|
||||
Row(children: [
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: TextField(
|
||||
@@ -744,28 +774,29 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
textAlign: TextAlign.end,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '21118',
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.only(right: 5),
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding:
|
||||
EdgeInsets.only(bottom: 10, top: 10, right: 10),
|
||||
isCollapsed: true,
|
||||
),
|
||||
),
|
||||
).marginOnly(right: 15),
|
||||
),
|
||||
enabled: enabled && !locked,
|
||||
).marginOnly(left: 5),
|
||||
Obx(() => ElevatedButton(
|
||||
onPressed: applyEnabled.value && enabled && !locked
|
||||
? () async {
|
||||
applyEnabled.value = false;
|
||||
await bind.mainSetOption(
|
||||
key: 'direct-access-port',
|
||||
value: controller.text);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
translate('Apply'),
|
||||
),
|
||||
).marginOnly(left: 20))
|
||||
]),
|
||||
Obx(() => ElevatedButton(
|
||||
onPressed: applyEnabled.value && enabled && !locked
|
||||
? () async {
|
||||
applyEnabled.value = false;
|
||||
await bind.mainSetOption(
|
||||
key: 'direct-access-port',
|
||||
value: controller.text);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
translate('Apply'),
|
||||
),
|
||||
))
|
||||
]),
|
||||
enabled: enabled && !locked,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -880,7 +911,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
controller: scrollController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
_lock(locked, 'Unlock Network Settings', () {
|
||||
locked = false;
|
||||
@@ -1043,7 +1074,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [_Button('Apply', submit, enabled: enabled)],
|
||||
).marginOnly(top: 15),
|
||||
).marginOnly(top: 10),
|
||||
],
|
||||
)
|
||||
]);
|
||||
@@ -1066,7 +1097,7 @@ class _DisplayState extends State<_Display> {
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
controller: scrollController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
viewStyle(context),
|
||||
scrollStyle(context),
|
||||
@@ -1306,7 +1337,7 @@ class _AccountState extends State<_Account> {
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: [
|
||||
_Card(title: 'Account', children: [accountAction()]),
|
||||
@@ -1350,7 +1381,7 @@ class _AboutState extends State<_About> {
|
||||
scrollController: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
child: _Card(title: '${translate('About')} RustDesk', children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1586,43 +1617,18 @@ Widget _SubButton(String label, Function() onPressed, [bool enabled = true]) {
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Widget _SubLabeledWidget(String label, Widget child, {bool enabled = true}) {
|
||||
RxBool hover = false.obs;
|
||||
Widget _SubLabeledWidget(BuildContext context, String label, Widget child,
|
||||
{bool enabled = true}) {
|
||||
return Row(
|
||||
children: [
|
||||
MouseRegion(
|
||||
onEnter: (_) => hover.value = true,
|
||||
onExit: (_) => hover.value = false,
|
||||
child: Obx(
|
||||
() {
|
||||
return Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: hover.value && enabled
|
||||
? const Color(0xFFD7D7D7)
|
||||
: const Color(0xFFCBCBCB),
|
||||
width: hover.value && enabled ? 2 : 1)),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
height: 28,
|
||||
color: (hover.value && enabled)
|
||||
? const Color(0xFFD7D7D7)
|
||||
: const Color(0xFFCBCBCB),
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 5, vertical: 2),
|
||||
child: Text(
|
||||
'${translate(label)}: ',
|
||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
||||
),
|
||||
).paddingAll(2),
|
||||
child,
|
||||
],
|
||||
));
|
||||
},
|
||||
)),
|
||||
Text(
|
||||
'${translate(label)}: ',
|
||||
style: TextStyle(color: _disabledTextColor(context, enabled)),
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
child,
|
||||
],
|
||||
).marginOnly(left: _kContentHSubMargin);
|
||||
}
|
||||
@@ -1691,33 +1697,30 @@ _LabeledTextField(
|
||||
bool secure) {
|
||||
return Row(
|
||||
children: [
|
||||
Spacer(flex: 1),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
'${translate(label)}:',
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 16, color: _disabledTextColor(context, enabled)),
|
||||
).marginOnly(right: 10)),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(
|
||||
'${translate(label)}:',
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(color: _disabledTextColor(context, enabled)),
|
||||
),
|
||||
),
|
||||
Spacer(flex: 1),
|
||||
Expanded(
|
||||
flex: 10,
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
enabled: enabled,
|
||||
obscureText: secure,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 15),
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.fromLTRB(14, 15, 14, 15),
|
||||
errorText: errorText.isNotEmpty ? errorText : null),
|
||||
style: TextStyle(
|
||||
color: _disabledTextColor(context, enabled),
|
||||
)),
|
||||
),
|
||||
Spacer(flex: 1),
|
||||
],
|
||||
);
|
||||
).marginOnly(bottom: 8);
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
@@ -1804,6 +1807,7 @@ void changeSocks5Proxy() async {
|
||||
var proxyController = TextEditingController(text: proxy);
|
||||
var userController = TextEditingController(text: username);
|
||||
var pwdController = TextEditingController(text: password);
|
||||
RxBool obscure = true.obs;
|
||||
|
||||
var isInProgress = false;
|
||||
gFFI.dialogManager.show((setState, close) {
|
||||
@@ -1849,35 +1853,30 @@ void changeSocks5Proxy() async {
|
||||
Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
child: Text('${translate("Hostname")}:')
|
||||
.marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
'${translate("Hostname")}:',
|
||||
textAlign: TextAlign.right,
|
||||
).marginOnly(right: 10)),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: proxyMsg.isNotEmpty ? proxyMsg : null),
|
||||
controller: proxyController,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
autofocus: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
).marginOnly(bottom: 8),
|
||||
Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
child: Text('${translate("Username")}:')
|
||||
.marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
'${translate("Username")}:',
|
||||
textAlign: TextAlign.right,
|
||||
).marginOnly(right: 10)),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: const InputDecoration(
|
||||
@@ -1887,32 +1886,30 @@ void changeSocks5Proxy() async {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
).marginOnly(bottom: 8),
|
||||
Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
child: Text('${translate("Password")}:')
|
||||
.marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
'${translate("Password")}:',
|
||||
textAlign: TextAlign.right,
|
||||
).marginOnly(right: 10)),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
controller: pwdController,
|
||||
),
|
||||
child: Obx(() => TextField(
|
||||
obscureText: obscure.value,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => obscure.value = !obscure.value,
|
||||
icon: Icon(obscure.value
|
||||
? Icons.visibility_off
|
||||
: Icons.visibility))),
|
||||
controller: pwdController,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
).marginOnly(bottom: 8),
|
||||
Offstage(
|
||||
offstage: !isInProgress, child: const LinearProgressIndicator())
|
||||
],
|
||||
|
||||
@@ -64,23 +64,17 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tabWidget = Container(
|
||||
child: Overlay(initialEntries: [
|
||||
OverlayEntry(builder: (context) {
|
||||
gFFI.dialogManager.setOverlayState(Overlay.of(context));
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: DesktopTab(
|
||||
controller: tabController,
|
||||
tail: ActionIcon(
|
||||
message: 'Settings',
|
||||
icon: IconFont.menu,
|
||||
onTap: DesktopTabPage.onAddSetting,
|
||||
isClose: false,
|
||||
),
|
||||
));
|
||||
})
|
||||
]),
|
||||
);
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: DesktopTab(
|
||||
controller: tabController,
|
||||
tail: ActionIcon(
|
||||
message: 'Settings',
|
||||
icon: IconFont.menu,
|
||||
onTap: DesktopTabPage.onAddSetting,
|
||||
isClose: false,
|
||||
),
|
||||
)));
|
||||
return Platform.isMacOS
|
||||
? tabWidget
|
||||
: Obx(
|
||||
|
||||
@@ -46,8 +46,10 @@ enum MouseFocusScope {
|
||||
}
|
||||
|
||||
class FileManagerPage extends StatefulWidget {
|
||||
const FileManagerPage({Key? key, required this.id}) : super(key: key);
|
||||
const FileManagerPage({Key? key, required this.id, this.forceRelay})
|
||||
: super(key: key);
|
||||
final String id;
|
||||
final bool? forceRelay;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _FileManagerPageState();
|
||||
@@ -80,6 +82,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
Entry? _lastClickEntry;
|
||||
|
||||
final _dropMaskVisible = false.obs; // TODO impl drop mask
|
||||
final _overlayKeyState = OverlayKeyState();
|
||||
|
||||
ScrollController getBreadCrumbScrollController(bool isLocal) {
|
||||
return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
|
||||
@@ -101,7 +104,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ffi = FFI();
|
||||
_ffi.start(widget.id, isFileTransfer: true);
|
||||
_ffi.start(widget.id, isFileTransfer: true, forceRelay: widget.forceRelay);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_ffi.dialogManager
|
||||
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
||||
@@ -115,6 +118,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
// register location listener
|
||||
_locationNodeLocal.addListener(onLocalLocationFocusChanged);
|
||||
_locationNodeRemote.addListener(onRemoteLocationFocusChanged);
|
||||
_ffi.dialogManager.setOverlayState(_overlayKeyState);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -137,9 +141,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Overlay(initialEntries: [
|
||||
OverlayEntry(builder: (context) {
|
||||
_ffi.dialogManager.setOverlayState(Overlay.of(context));
|
||||
return Overlay(key: _overlayKeyState.key, initialEntries: [
|
||||
OverlayEntry(builder: (_) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: _ffi.fileModel,
|
||||
child: Consumer<FileModel>(builder: (context, model, child) {
|
||||
@@ -235,10 +238,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: _buildDataTable(context, isLocal, scrollController),
|
||||
),
|
||||
child: _buildFileList(context, isLocal, scrollController),
|
||||
)
|
||||
],
|
||||
)),
|
||||
@@ -247,25 +247,11 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDataTable(
|
||||
Widget _buildFileList(
|
||||
BuildContext context, bool isLocal, ScrollController scrollController) {
|
||||
const rowHeight = 25.0;
|
||||
final fd = model.getCurrentDir(isLocal);
|
||||
final entries = fd.entries;
|
||||
final sortIndex = (SortBy style) {
|
||||
switch (style) {
|
||||
case SortBy.name:
|
||||
return 0;
|
||||
case SortBy.type:
|
||||
return 0;
|
||||
case SortBy.modified:
|
||||
return 1;
|
||||
case SortBy.size:
|
||||
return 2;
|
||||
}
|
||||
}(model.getSortStyle(isLocal));
|
||||
final sortAscending =
|
||||
isLocal ? model.localSortAscending : model.remoteSortAscending;
|
||||
final selectedEntries = getSelectedItems(isLocal);
|
||||
|
||||
return MouseRegion(
|
||||
onEnter: (evt) {
|
||||
@@ -286,7 +272,6 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
onNext: (buffer) {
|
||||
debugPrint("searching next for $buffer");
|
||||
assert(buffer.length == 1);
|
||||
final selectedEntries = getSelectedItems(isLocal);
|
||||
assert(selectedEntries.length <= 1);
|
||||
var skipCount = 0;
|
||||
if (selectedEntries.items.isNotEmpty) {
|
||||
@@ -311,7 +296,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
return;
|
||||
}
|
||||
_jumpToEntry(
|
||||
isLocal, searchResult.first, scrollController, rowHeight, buffer);
|
||||
isLocal, searchResult.first, scrollController,
|
||||
kDesktopFileTransferRowHeight, buffer);
|
||||
},
|
||||
onSearch: (buffer) {
|
||||
debugPrint("searching for $buffer");
|
||||
@@ -326,7 +312,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
return;
|
||||
}
|
||||
_jumpToEntry(
|
||||
isLocal, searchResult.first, scrollController, rowHeight, buffer);
|
||||
isLocal, searchResult.first, scrollController,
|
||||
kDesktopFileTransferRowHeight, buffer);
|
||||
},
|
||||
child: ObxValue<RxString>(
|
||||
(searchText) {
|
||||
@@ -335,118 +322,120 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
return element.name.contains(searchText.value);
|
||||
}).toList(growable: false)
|
||||
: entries;
|
||||
return DataTable(
|
||||
key: ValueKey(isLocal ? 0 : 1),
|
||||
showCheckboxColumn: false,
|
||||
dataRowHeight: rowHeight,
|
||||
headingRowHeight: 30,
|
||||
horizontalMargin: 8,
|
||||
columnSpacing: 8,
|
||||
showBottomBorder: true,
|
||||
sortColumnIndex: sortIndex,
|
||||
sortAscending: sortAscending,
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text(
|
||||
translate("Name"),
|
||||
).marginSymmetric(horizontal: 4),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.name,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
DataColumn(
|
||||
label: Text(
|
||||
translate("Modified"),
|
||||
),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.modified,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
DataColumn(
|
||||
label: Text(translate("Size")),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.size,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
],
|
||||
rows: filteredEntries.map((entry) {
|
||||
final rows = filteredEntries.map((entry) {
|
||||
final sizeStr =
|
||||
entry.isFile ? readableFileSize(entry.size.toDouble()) : "";
|
||||
final lastModifiedStr = entry.isDrive
|
||||
? " "
|
||||
: "${entry.lastModified().toString().replaceAll(".000", "")} ";
|
||||
return DataRow(
|
||||
key: ValueKey(entry.name),
|
||||
onSelectChanged: (s) {
|
||||
_onSelectedChanged(getSelectedItems(isLocal),
|
||||
filteredEntries, entry, isLocal);
|
||||
},
|
||||
selected: getSelectedItems(isLocal).contains(entry),
|
||||
cells: [
|
||||
DataCell(
|
||||
Container(
|
||||
width: 200,
|
||||
child: Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: entry.name,
|
||||
child: Row(children: [
|
||||
entry.isDrive
|
||||
? Image(
|
||||
image: iconHardDrive,
|
||||
fit: BoxFit.scaleDown,
|
||||
color: Theme.of(context)
|
||||
.iconTheme
|
||||
.color
|
||||
?.withOpacity(0.7))
|
||||
.paddingAll(4)
|
||||
: Icon(
|
||||
entry.isFile
|
||||
? Icons.feed_outlined
|
||||
: Icons.folder,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.iconTheme
|
||||
.color
|
||||
?.withOpacity(0.7),
|
||||
).marginSymmetric(horizontal: 2),
|
||||
Expanded(
|
||||
child: Text(entry.name,
|
||||
overflow: TextOverflow.ellipsis))
|
||||
]),
|
||||
final isSelected = selectedEntries.contains(entry);
|
||||
return SizedBox(
|
||||
key: ValueKey(entry.name),
|
||||
height: kDesktopFileTransferRowHeight,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const Divider(
|
||||
height: 1,
|
||||
),
|
||||
Expanded(
|
||||
child: Ink(
|
||||
decoration: isSelected
|
||||
? BoxDecoration(color: Theme.of(context).hoverColor)
|
||||
: null,
|
||||
child: InkWell(
|
||||
child: Row(children: [
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
width: kDesktopFileTransferNameColWidth,
|
||||
child: Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: entry.name,
|
||||
child: Row(children: [
|
||||
entry.isDrive
|
||||
? Image(
|
||||
image: iconHardDrive,
|
||||
fit: BoxFit.scaleDown,
|
||||
color: Theme.of(context)
|
||||
.iconTheme
|
||||
.color
|
||||
?.withOpacity(0.7))
|
||||
.paddingAll(4)
|
||||
: Icon(
|
||||
entry.isFile
|
||||
? Icons.feed_outlined
|
||||
: Icons.folder,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.iconTheme
|
||||
.color
|
||||
?.withOpacity(0.7),
|
||||
).marginSymmetric(horizontal: 2),
|
||||
Expanded(
|
||||
child: Text(entry.name.nonBreaking,
|
||||
overflow: TextOverflow.ellipsis))
|
||||
]),
|
||||
)),
|
||||
onTap: () {
|
||||
final items = getSelectedItems(isLocal);
|
||||
// handle double click
|
||||
if (_checkDoubleClick(entry)) {
|
||||
openDirectory(entry.path, isLocal: isLocal);
|
||||
items.clear();
|
||||
return;
|
||||
}
|
||||
_onSelectedChanged(
|
||||
items, filteredEntries, entry, isLocal);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: SizedBox(
|
||||
width: kDesktopFileTransferModifiedColWidth,
|
||||
child: Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: lastModifiedStr,
|
||||
child: Text(
|
||||
lastModifiedStr,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: MyTheme.darkGray),
|
||||
)),
|
||||
)),
|
||||
onTap: () {
|
||||
final items = getSelectedItems(isLocal);
|
||||
|
||||
// handle double click
|
||||
if (_checkDoubleClick(entry)) {
|
||||
openDirectory(entry.path, isLocal: isLocal);
|
||||
items.clear();
|
||||
return;
|
||||
}
|
||||
_onSelectedChanged(
|
||||
items, filteredEntries, entry, isLocal);
|
||||
},
|
||||
GestureDetector(
|
||||
child: Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: sizeStr,
|
||||
child: Text(
|
||||
sizeStr,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: MyTheme.darkGray),
|
||||
))),
|
||||
]),
|
||||
),
|
||||
),
|
||||
DataCell(FittedBox(
|
||||
child: Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: lastModifiedStr,
|
||||
child: Text(
|
||||
lastModifiedStr,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: MyTheme.darkGray),
|
||||
)))),
|
||||
DataCell(Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: sizeStr,
|
||||
child: Text(
|
||||
sizeStr,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 10, color: MyTheme.darkGray),
|
||||
))),
|
||||
]);
|
||||
}).toList(growable: false),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(growable: false);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Header
|
||||
_buildFileBrowserHeader(context, isLocal),
|
||||
// Body
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: scrollController,
|
||||
itemExtent: kDesktopFileTransferRowHeight,
|
||||
itemBuilder: (context, index) {
|
||||
return rows[index];
|
||||
},
|
||||
itemCount: rows.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
isLocal ? _searchTextLocal : _searchTextRemote,
|
||||
@@ -797,7 +786,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
"Please enter the folder name"),
|
||||
),
|
||||
controller: name,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
autofocus: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1132,4 +1121,60 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget headerItemFunc(
|
||||
double? width, SortBy sortBy, String name, bool isLocal) {
|
||||
final headerTextStyle =
|
||||
Theme.of(context).dataTableTheme.headingTextStyle ?? TextStyle();
|
||||
return ObxValue<Rx<bool?>>(
|
||||
(ascending) => InkWell(
|
||||
onTap: () {
|
||||
if (ascending.value == null) {
|
||||
ascending.value = true;
|
||||
} else {
|
||||
ascending.value = !ascending.value!;
|
||||
}
|
||||
model.changeSortStyle(sortBy,
|
||||
isLocal: isLocal, ascending: ascending.value!);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: width,
|
||||
height: kDesktopFileTransferHeaderHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
style: headerTextStyle,
|
||||
).marginSymmetric(
|
||||
horizontal: sortBy == SortBy.name ? 4 : 0.0),
|
||||
ascending.value != null
|
||||
? Icon(ascending.value!
|
||||
? Icons.arrow_upward
|
||||
: Icons.arrow_downward)
|
||||
: const Offstage()
|
||||
],
|
||||
),
|
||||
),
|
||||
), () {
|
||||
if (model.getSortStyle(isLocal) == sortBy) {
|
||||
return model.getSortAscending(isLocal).obs;
|
||||
} else {
|
||||
return Rx<bool?>(null);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
Widget _buildFileBrowserHeader(BuildContext context, bool isLocal) {
|
||||
return Row(
|
||||
children: [
|
||||
headerItemFunc(kDesktopFileTransferNameColWidth, SortBy.name,
|
||||
translate("Name"), isLocal),
|
||||
headerItemFunc(kDesktopFileTransferModifiedColWidth, SortBy.modified,
|
||||
translate("Modified"), isLocal),
|
||||
Expanded(
|
||||
child:
|
||||
headerItemFunc(null, SortBy.size, translate("Size"), isLocal))
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,11 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
selectedIcon: selectedIcon,
|
||||
unselectedIcon: unselectedIcon,
|
||||
onTabCloseButton: () => () => tabController.closeBy(params['id']),
|
||||
page: FileManagerPage(key: ValueKey(params['id']), id: params['id'])));
|
||||
page: FileManagerPage(
|
||||
key: ValueKey(params['id']),
|
||||
id: params['id'],
|
||||
forceRelay: params['forceRelay'],
|
||||
)));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -64,7 +68,11 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
selectedIcon: selectedIcon,
|
||||
unselectedIcon: unselectedIcon,
|
||||
onTabCloseButton: () => tabController.closeBy(id),
|
||||
page: FileManagerPage(key: ValueKey(id), id: id)));
|
||||
page: FileManagerPage(
|
||||
key: ValueKey(id),
|
||||
id: id,
|
||||
forceRelay: args['forceRelay'],
|
||||
)));
|
||||
} else if (call.method == "onDestroy") {
|
||||
tabController.clear();
|
||||
} else if (call.method == kWindowActionRebuild) {
|
||||
|
||||
@@ -26,10 +26,12 @@ class _PortForward {
|
||||
}
|
||||
|
||||
class PortForwardPage extends StatefulWidget {
|
||||
const PortForwardPage({Key? key, required this.id, required this.isRDP})
|
||||
const PortForwardPage(
|
||||
{Key? key, required this.id, required this.isRDP, this.forceRelay})
|
||||
: super(key: key);
|
||||
final String id;
|
||||
final bool isRDP;
|
||||
final bool? forceRelay;
|
||||
|
||||
@override
|
||||
State<PortForwardPage> createState() => _PortForwardPageState();
|
||||
@@ -47,7 +49,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ffi = FFI();
|
||||
_ffi.start(widget.id, isPortForward: true);
|
||||
_ffi.start(widget.id, isPortForward: true, forceRelay: widget.forceRelay);
|
||||
Get.put(_ffi, tag: 'pf_${widget.id}');
|
||||
if (!Platform.isLinux) {
|
||||
Wakelock.enable();
|
||||
@@ -179,36 +181,33 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
buildTunnelInputCell(context,
|
||||
controller: remotePortController,
|
||||
inputFormatters: portInputFormatter),
|
||||
SizedBox(
|
||||
width: _kColumn4Width,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0, side: const BorderSide(color: MyTheme.border)),
|
||||
onPressed: () async {
|
||||
int? localPort = int.tryParse(localPortController.text);
|
||||
int? remotePort = int.tryParse(remotePortController.text);
|
||||
if (localPort != null &&
|
||||
remotePort != null &&
|
||||
(remoteHostController.text.isEmpty ||
|
||||
remoteHostController.text.trim().isNotEmpty)) {
|
||||
await bind.sessionAddPortForward(
|
||||
id: 'pf_${widget.id}',
|
||||
localPort: localPort,
|
||||
remoteHost: remoteHostController.text.trim().isEmpty
|
||||
? 'localhost'
|
||||
: remoteHostController.text.trim(),
|
||||
remotePort: remotePort);
|
||||
localPortController.clear();
|
||||
remoteHostController.clear();
|
||||
remotePortController.clear();
|
||||
refreshTunnelConfig();
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
translate('Add'),
|
||||
),
|
||||
).marginAll(10),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0, side: const BorderSide(color: MyTheme.border)),
|
||||
onPressed: () async {
|
||||
int? localPort = int.tryParse(localPortController.text);
|
||||
int? remotePort = int.tryParse(remotePortController.text);
|
||||
if (localPort != null &&
|
||||
remotePort != null &&
|
||||
(remoteHostController.text.isEmpty ||
|
||||
remoteHostController.text.trim().isNotEmpty)) {
|
||||
await bind.sessionAddPortForward(
|
||||
id: 'pf_${widget.id}',
|
||||
localPort: localPort,
|
||||
remoteHost: remoteHostController.text.trim().isEmpty
|
||||
? 'localhost'
|
||||
: remoteHostController.text.trim(),
|
||||
remotePort: remotePort);
|
||||
localPortController.clear();
|
||||
remoteHostController.clear();
|
||||
remotePortController.clear();
|
||||
refreshTunnelConfig();
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
translate('Add'),
|
||||
),
|
||||
).marginAll(10),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
||||
key: ValueKey(params['id']),
|
||||
id: params['id'],
|
||||
isRDP: isRDP,
|
||||
forceRelay: params['forceRelay'],
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -72,7 +73,12 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
||||
label: id,
|
||||
selectedIcon: selectedIcon,
|
||||
unselectedIcon: unselectedIcon,
|
||||
page: PortForwardPage(id: id, isRDP: isRDP)));
|
||||
page: PortForwardPage(
|
||||
key: ValueKey(args['id']),
|
||||
id: id,
|
||||
isRDP: isRDP,
|
||||
forceRelay: args['forceRelay'],
|
||||
)));
|
||||
} else if (call.method == "onDestroy") {
|
||||
tabController.clear();
|
||||
} else if (call.method == kWindowActionRebuild) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import '../../mobile/widgets/dialog.dart';
|
||||
import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../../common/shared_state.dart';
|
||||
import '../../utils/image.dart';
|
||||
import '../widgets/remote_menubar.dart';
|
||||
import '../widgets/kb_layout_type_chooser.dart';
|
||||
|
||||
@@ -33,11 +34,13 @@ class RemotePage extends StatefulWidget {
|
||||
required this.id,
|
||||
required this.menubarState,
|
||||
this.switchUuid,
|
||||
this.forceRelay,
|
||||
}) : super(key: key);
|
||||
|
||||
final String id;
|
||||
final MenubarState menubarState;
|
||||
final String? switchUuid;
|
||||
final bool? forceRelay;
|
||||
final SimpleWrapper<State<RemotePage>?> _lastState = SimpleWrapper(null);
|
||||
|
||||
FFI get ffi => (_lastState.value! as _RemotePageState)._ffi;
|
||||
@@ -61,6 +64,8 @@ class _RemotePageState extends State<RemotePage>
|
||||
late RxBool _remoteCursorMoved;
|
||||
late RxBool _keyboardEnabled;
|
||||
|
||||
final _blockableOverlayState = BlockableOverlayState();
|
||||
|
||||
final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode");
|
||||
|
||||
Function(bool)? _onEnterOrLeaveImage4Menubar;
|
||||
@@ -104,6 +109,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
_ffi.start(
|
||||
widget.id,
|
||||
switchUuid: widget.switchUuid,
|
||||
forceRelay: widget.forceRelay,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
@@ -132,6 +138,13 @@ class _RemotePageState extends State<RemotePage>
|
||||
// });
|
||||
// _isCustomCursorInited = true;
|
||||
// }
|
||||
|
||||
_ffi.dialogManager.setOverlayState(_blockableOverlayState);
|
||||
_ffi.chatModel.setOverlayState(_blockableOverlayState);
|
||||
// make remote page penetrable automatically, effective for chat over remote
|
||||
_blockableOverlayState.onMiddleBlockedClick = () {
|
||||
_blockableOverlayState.setMiddleBlocked(false);
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -191,39 +204,50 @@ class _RemotePageState extends State<RemotePage>
|
||||
|
||||
Widget buildBody(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: Overlay(
|
||||
initialEntries: [
|
||||
OverlayEntry(builder: (context) {
|
||||
_ffi.chatModel.setOverlayState(Overlay.of(context));
|
||||
_ffi.dialogManager.setOverlayState(Overlay.of(context));
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: RawKeyFocusScope(
|
||||
focusNode: _rawKeyFocusNode,
|
||||
onFocusChange: (bool imageFocused) {
|
||||
debugPrint(
|
||||
"onFocusChange(window active:${!_isWindowBlur}) $imageFocused");
|
||||
// See [onWindowBlur].
|
||||
if (Platform.isWindows) {
|
||||
if (_isWindowBlur) {
|
||||
imageFocused = false;
|
||||
Future.delayed(Duration.zero, () {
|
||||
_rawKeyFocusNode.unfocus();
|
||||
});
|
||||
}
|
||||
if (imageFocused) {
|
||||
_ffi.inputModel.enterOrLeave(true);
|
||||
} else {
|
||||
_ffi.inputModel.enterOrLeave(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
inputModel: _ffi.inputModel,
|
||||
child: getBodyForDesktop(context)));
|
||||
})
|
||||
],
|
||||
));
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
|
||||
/// the Overlay key will be set with _blockableOverlayState in BlockableOverlay
|
||||
/// see override build() in [BlockableOverlay]
|
||||
body: BlockableOverlay(
|
||||
state: _blockableOverlayState,
|
||||
underlying: Container(
|
||||
color: Colors.black,
|
||||
child: RawKeyFocusScope(
|
||||
focusNode: _rawKeyFocusNode,
|
||||
onFocusChange: (bool imageFocused) {
|
||||
debugPrint(
|
||||
"onFocusChange(window active:${!_isWindowBlur}) $imageFocused");
|
||||
// See [onWindowBlur].
|
||||
if (Platform.isWindows) {
|
||||
if (_isWindowBlur) {
|
||||
imageFocused = false;
|
||||
Future.delayed(Duration.zero, () {
|
||||
_rawKeyFocusNode.unfocus();
|
||||
});
|
||||
}
|
||||
if (imageFocused) {
|
||||
_ffi.inputModel.enterOrLeave(true);
|
||||
} else {
|
||||
_ffi.inputModel.enterOrLeave(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
inputModel: _ffi.inputModel,
|
||||
child: getBodyForDesktop(context))),
|
||||
upperLayer: [
|
||||
OverlayEntry(
|
||||
builder: (context) => RemoteMenubar(
|
||||
id: widget.id,
|
||||
ffi: _ffi,
|
||||
state: widget.menubarState,
|
||||
onEnterOrLeaveImageSetter: (func) =>
|
||||
_onEnterOrLeaveImage4Menubar = func,
|
||||
onEnterOrLeaveImageCleaner: () =>
|
||||
_onEnterOrLeaveImage4Menubar = null,
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -344,13 +368,6 @@ class _RemotePageState extends State<RemotePage>
|
||||
QualityMonitor(_ffi.qualityMonitorModel), null, null),
|
||||
),
|
||||
);
|
||||
paints.add(RemoteMenubar(
|
||||
id: widget.id,
|
||||
ffi: _ffi,
|
||||
state: widget.menubarState,
|
||||
onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func,
|
||||
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null,
|
||||
));
|
||||
return Stack(
|
||||
children: paints,
|
||||
);
|
||||
@@ -672,40 +689,3 @@ class CursorPaint extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ImagePainter extends CustomPainter {
|
||||
ImagePainter({
|
||||
required this.image,
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.scale,
|
||||
});
|
||||
|
||||
ui.Image? image;
|
||||
double x;
|
||||
double y;
|
||||
double scale;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (image == null) return;
|
||||
if (x.isNaN || y.isNaN) return;
|
||||
canvas.scale(scale, scale);
|
||||
// https://github.com/flutter/flutter/issues/76187#issuecomment-784628161
|
||||
// https://api.flutter-io.cn/flutter/dart-ui/FilterQuality.html
|
||||
var paint = Paint();
|
||||
if ((scale - 1.0).abs() > 0.001) {
|
||||
paint.filterQuality = FilterQuality.medium;
|
||||
if (scale > 10.00000) {
|
||||
paint.filterQuality = FilterQuality.high;
|
||||
}
|
||||
}
|
||||
canvas.drawImage(
|
||||
image!, Offset(x.toInt().toDouble(), y.toInt().toDouble()), paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||
return oldDelegate != this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ import 'package:bot_toast/bot_toast.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
|
||||
class _MenuTheme {
|
||||
static const Color commonColor = MyTheme.accent;
|
||||
static const Color blueColor = MyTheme.button;
|
||||
static const Color hoverBlueColor = MyTheme.accent;
|
||||
static const Color redColor = Colors.redAccent;
|
||||
static const Color hoverRedColor = Colors.red;
|
||||
// kMinInteractiveDimension
|
||||
static const double height = 20.0;
|
||||
static const double dividerHeight = 12.0;
|
||||
@@ -70,6 +73,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
id: peerId,
|
||||
menubarState: _menubarState,
|
||||
switchUuid: params['switch_uuid'],
|
||||
forceRelay: params['forceRelay'],
|
||||
),
|
||||
));
|
||||
_update_remote_count();
|
||||
@@ -104,6 +108,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
id: id,
|
||||
menubarState: _menubarState,
|
||||
switchUuid: switchUuid,
|
||||
forceRelay: args['forceRelay'],
|
||||
),
|
||||
));
|
||||
} else if (call.method == "onDestroy") {
|
||||
@@ -280,7 +285,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: _MenuTheme.commonColor,
|
||||
commonColor: _MenuTheme.blueColor,
|
||||
height: _MenuTheme.height,
|
||||
dividerHeight: _MenuTheme.dividerHeight,
|
||||
)))
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// original cm window in Sciter version.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_hbb/utils/platform_channel.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
@@ -47,8 +49,17 @@ class _DesktopServerPageState extends State<DesktopServerPage>
|
||||
|
||||
@override
|
||||
void onWindowClose() {
|
||||
gFFI.serverModel.closeAll();
|
||||
gFFI.close();
|
||||
Future.wait([
|
||||
gFFI.serverModel.closeAll(),
|
||||
gFFI.close()
|
||||
]).then((_) {
|
||||
if (Platform.isMacOS) {
|
||||
RdPlatformChannel.instance.terminate();
|
||||
} else {
|
||||
windowManager.setPreventClose(false);
|
||||
windowManager.close();
|
||||
}
|
||||
});
|
||||
super.onWindowClose();
|
||||
}
|
||||
|
||||
@@ -68,26 +79,19 @@ class _DesktopServerPageState extends State<DesktopServerPage>
|
||||
],
|
||||
child: Consumer<ServerModel>(
|
||||
builder: (context, serverModel, child) => Container(
|
||||
decoration: BoxDecoration(
|
||||
border:
|
||||
Border.all(color: MyTheme.color(context).border!)),
|
||||
child: Overlay(initialEntries: [
|
||||
OverlayEntry(builder: (context) {
|
||||
gFFI.dialogManager.setOverlayState(Overlay.of(context));
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: ConnectionManager()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
]),
|
||||
)));
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: MyTheme.color(context).border!)),
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: ConnectionManager()),
|
||||
],
|
||||
),
|
||||
),
|
||||
))));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -521,6 +525,39 @@ class _CmControlPanel extends StatelessWidget {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: !client.inVoiceCall,
|
||||
child: buildButton(context,
|
||||
color: Colors.red,
|
||||
onClick: () => closeVoiceCall(),
|
||||
icon: Icon(Icons.phone_disabled_rounded, color: Colors.white),
|
||||
text: "Stop voice call",
|
||||
textColor: Colors.white),
|
||||
),
|
||||
Offstage(
|
||||
offstage: !client.incomingVoiceCall,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: buildButton(context,
|
||||
color: MyTheme.accent,
|
||||
onClick: () => handleVoiceCall(true),
|
||||
icon: Icon(Icons.phone_enabled, color: Colors.white),
|
||||
text: "Accept",
|
||||
textColor: Colors.white),
|
||||
),
|
||||
Expanded(
|
||||
child: buildButton(context,
|
||||
color: Colors.red,
|
||||
onClick: () => handleVoiceCall(false),
|
||||
icon:
|
||||
Icon(Icons.phone_disabled_rounded, color: Colors.white),
|
||||
text: "Dismiss",
|
||||
textColor: Colors.white),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Offstage(
|
||||
offstage: !client.fromSwitch,
|
||||
child: buildButton(context,
|
||||
@@ -626,7 +663,7 @@ class _CmControlPanel extends StatelessWidget {
|
||||
.marginSymmetric(horizontal: showElevation ? 0 : bigMargin);
|
||||
}
|
||||
|
||||
buildButton(
|
||||
Widget buildButton(
|
||||
BuildContext context, {
|
||||
required Color? color,
|
||||
required Function() onClick,
|
||||
@@ -692,6 +729,14 @@ class _CmControlPanel extends StatelessWidget {
|
||||
void handleSwitchBack(BuildContext context) {
|
||||
bind.cmSwitchBack(connId: client.id);
|
||||
}
|
||||
|
||||
void handleVoiceCall(bool accept) {
|
||||
bind.cmHandleIncomingVoiceCall(id: client.id, accept: accept);
|
||||
}
|
||||
|
||||
void closeVoiceCall() {
|
||||
bind.cmCloseVoiceCall(id: client.id);
|
||||
}
|
||||
}
|
||||
|
||||
void checkClickTime(int id, Function() callback) async {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/menu_button.dart';
|
||||
|
||||
// Examples can assume:
|
||||
// enum Commands { heroAndScholar, hurricaneCame }
|
||||
@@ -1391,22 +1393,20 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
onTap: widget.enabled ? showButtonMenu : null,
|
||||
onHover: widget.onHover,
|
||||
canRequestFocus: _canRequestFocus,
|
||||
radius: widget.splashRadius,
|
||||
enableFeedback: enableFeedback,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return IconButton(
|
||||
icon: widget.icon ?? Icon(Icons.adaptive.more),
|
||||
padding: widget.padding,
|
||||
splashRadius: widget.splashRadius,
|
||||
iconSize: widget.iconSize ?? iconTheme.size ?? _kDefaultIconSize,
|
||||
return MenuButton(
|
||||
child: widget.icon ?? Icon(Icons.adaptive.more),
|
||||
tooltip:
|
||||
widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
|
||||
onPressed: widget.enabled ? showButtonMenu : null,
|
||||
enableFeedback: enableFeedback,
|
||||
color: MyTheme.button,
|
||||
hoverColor: MyTheme.accent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
61
flutter/lib/desktop/widgets/menu_button.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MenuButton extends StatefulWidget {
|
||||
final GestureTapCallback? onPressed;
|
||||
final Color color;
|
||||
final Color hoverColor;
|
||||
final Color? splashColor;
|
||||
final Widget child;
|
||||
final String? tooltip;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final bool enableFeedback;
|
||||
const MenuButton({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.color,
|
||||
required this.hoverColor,
|
||||
required this.child,
|
||||
this.splashColor,
|
||||
this.tooltip = "",
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 3, vertical: 6),
|
||||
this.enableFeedback = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MenuButton> createState() => _MenuButtonState();
|
||||
}
|
||||
|
||||
class _MenuButtonState extends State<MenuButton> {
|
||||
bool _isHover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: widget.padding,
|
||||
child: Tooltip(
|
||||
message: widget.tooltip,
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: _isHover ? widget.hoverColor : widget.color,
|
||||
),
|
||||
child: InkWell(
|
||||
onHover: (val) {
|
||||
setState(() {
|
||||
_isHover = val;
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
splashColor: widget.splashColor,
|
||||
enableFeedback: widget.enableFeedback,
|
||||
onTap: widget.onPressed,
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/menu_button.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:debounce_throttle/debounce_throttle.dart';
|
||||
@@ -93,7 +95,10 @@ class MenubarState {
|
||||
}
|
||||
|
||||
class _MenubarTheme {
|
||||
static const Color commonColor = MyTheme.accent;
|
||||
static const Color blueColor = MyTheme.button;
|
||||
static const Color hoverBlueColor = MyTheme.accent;
|
||||
static const Color redColor = Colors.redAccent;
|
||||
static const Color hoverRedColor = Colors.red;
|
||||
// kMinInteractiveDimension
|
||||
static const double height = 20.0;
|
||||
static const double dividerHeight = 12.0;
|
||||
@@ -408,14 +413,18 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
menubarItems.add(_buildPinMenubar(context));
|
||||
menubarItems.add(_buildFullscreen(context));
|
||||
if (widget.ffi.ffiModel.isPeerAndroid) {
|
||||
menubarItems.add(IconButton(
|
||||
menubarItems.add(MenuButton(
|
||||
tooltip: translate('Mobile Actions'),
|
||||
color: _MenubarTheme.commonColor,
|
||||
icon: const Icon(Icons.build),
|
||||
child: SvgPicture.asset(
|
||||
"assets/actions_mobile.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
widget.ffi.dialogManager
|
||||
.toggleMobileActionsOverlay(ffi: widget.ffi);
|
||||
},
|
||||
color: _MenubarTheme.blueColor,
|
||||
hoverColor: _MenubarTheme.hoverBlueColor,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -425,85 +434,84 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
menubarItems.add(_buildKeyboard(context));
|
||||
if (!isWeb) {
|
||||
menubarItems.add(_buildChat(context));
|
||||
menubarItems.add(_buildVoiceCall(context));
|
||||
}
|
||||
menubarItems.add(_buildRecording(context));
|
||||
menubarItems.add(_buildClose(context));
|
||||
return PopupMenuTheme(
|
||||
data: const PopupMenuThemeData(
|
||||
textStyle: TextStyle(color: _MenubarTheme.commonColor)),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
data: const PopupMenuThemeData(
|
||||
textStyle: TextStyle(color: _MenubarTheme.blueColor)),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: MyTheme.border),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
bottom: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: menubarItems,
|
||||
)),
|
||||
children: [
|
||||
SizedBox(width: 3),
|
||||
...menubarItems,
|
||||
SizedBox(width: 3)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildDraggableShowHide(context),
|
||||
]));
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPinMenubar(BuildContext context) {
|
||||
return Obx(() => IconButton(
|
||||
tooltip: translate(pin ? 'Unpin menubar' : 'Pin menubar'),
|
||||
onPressed: () {
|
||||
widget.state.switchPin();
|
||||
},
|
||||
icon: Obx(() => Transform.rotate(
|
||||
angle: pin ? math.pi / 4 : 0,
|
||||
child: Icon(
|
||||
Icons.push_pin,
|
||||
color: pin ? _MenubarTheme.commonColor : Colors.grey,
|
||||
))),
|
||||
));
|
||||
return Obx(
|
||||
() => MenuButton(
|
||||
tooltip: translate(pin ? 'Unpin menubar' : 'Pin menubar'),
|
||||
onPressed: () {
|
||||
widget.state.switchPin();
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
pin ? "assets/pinned.svg" : "assets/unpinned.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
color: pin ? _MenubarTheme.blueColor : Colors.grey[800]!,
|
||||
hoverColor: pin ? _MenubarTheme.hoverBlueColor : Colors.grey[850]!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFullscreen(BuildContext context) {
|
||||
return IconButton(
|
||||
return MenuButton(
|
||||
tooltip: translate(isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'),
|
||||
onPressed: () {
|
||||
_setFullscreen(!isFullscreen);
|
||||
},
|
||||
icon: isFullscreen
|
||||
? const Icon(
|
||||
Icons.fullscreen_exit,
|
||||
color: _MenubarTheme.commonColor,
|
||||
)
|
||||
: const Icon(
|
||||
Icons.fullscreen,
|
||||
color: _MenubarTheme.commonColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChat(BuildContext context) {
|
||||
return IconButton(
|
||||
tooltip: translate('Chat'),
|
||||
onPressed: () {
|
||||
widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID);
|
||||
widget.ffi.chatModel.toggleChatOverlay();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.message,
|
||||
color: _MenubarTheme.commonColor,
|
||||
child: SvgPicture.asset(
|
||||
isFullscreen ? "assets/fullscreen_exit.svg" : "assets/fullscreen.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
color: _MenubarTheme.blueColor,
|
||||
hoverColor: _MenubarTheme.hoverBlueColor,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMonitor(BuildContext context) {
|
||||
final pi = widget.ffi.ffiModel.pi;
|
||||
return mod_menu.PopupMenuButton(
|
||||
final monitor = mod_menu.PopupMenuButton(
|
||||
tooltip: translate('Select Monitor'),
|
||||
padding: EdgeInsets.zero,
|
||||
position: mod_menu.PopupMenuPosition.under,
|
||||
icon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.personal_video,
|
||||
color: _MenubarTheme.commonColor,
|
||||
SvgPicture.asset(
|
||||
"assets/display.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 3.9),
|
||||
@@ -511,8 +519,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
RxInt display = CurrentDisplayState.find(widget.id);
|
||||
return Text(
|
||||
'${display.value + 1}/${pi.displays.length}',
|
||||
style: const TextStyle(
|
||||
color: _MenubarTheme.commonColor, fontSize: 8),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 8),
|
||||
);
|
||||
}),
|
||||
)
|
||||
@@ -521,41 +528,44 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
itemBuilder: (BuildContext context) {
|
||||
final List<Widget> rowChildren = [];
|
||||
for (int i = 0; i < pi.displays.length; i++) {
|
||||
rowChildren.add(
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.personal_video,
|
||||
color: _MenubarTheme.commonColor,
|
||||
),
|
||||
TextButton(
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
constraints:
|
||||
const BoxConstraints(minHeight: _MenubarTheme.height),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2.5),
|
||||
child: Text(
|
||||
(i + 1).toString(),
|
||||
style:
|
||||
const TextStyle(color: _MenubarTheme.commonColor),
|
||||
),
|
||||
)),
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
_menuDismissCallback();
|
||||
}
|
||||
RxInt display = CurrentDisplayState.find(widget.id);
|
||||
if (display.value != i) {
|
||||
bind.sessionSwitchDisplay(id: widget.id, value: i);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
rowChildren.add(MenuButton(
|
||||
color: _MenubarTheme.blueColor,
|
||||
hoverColor: _MenubarTheme.hoverBlueColor,
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
constraints:
|
||||
const BoxConstraints(minHeight: _MenubarTheme.height),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/display.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2.5),
|
||||
child: Text(
|
||||
(i + 1).toString(),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
_menuDismissCallback();
|
||||
}
|
||||
RxInt display = CurrentDisplayState.find(widget.id);
|
||||
if (display.value != i) {
|
||||
bind.sessionSwitchDisplay(id: widget.id, value: i);
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
return <mod_menu.PopupMenuEntry<String>>[
|
||||
mod_menu.PopupMenuItem<String>(
|
||||
@@ -571,14 +581,19 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
];
|
||||
},
|
||||
);
|
||||
|
||||
return Obx(() => Offstage(
|
||||
offstage: stateGlobal.displaysCount.value < 2,
|
||||
child: monitor,
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildControl(BuildContext context) {
|
||||
return mod_menu.PopupMenuButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(
|
||||
Icons.bolt,
|
||||
color: _MenubarTheme.commonColor,
|
||||
icon: SvgPicture.asset(
|
||||
"assets/actions.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: translate('Control Actions'),
|
||||
position: mod_menu.PopupMenuPosition.under,
|
||||
@@ -586,7 +601,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: _MenubarTheme.commonColor,
|
||||
commonColor: _MenubarTheme.blueColor,
|
||||
height: _MenubarTheme.height,
|
||||
dividerHeight: _MenubarTheme.dividerHeight,
|
||||
)))
|
||||
@@ -608,9 +623,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
final remoteCount = RemoteCountState.find().value;
|
||||
return mod_menu.PopupMenuButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(
|
||||
Icons.tv,
|
||||
color: _MenubarTheme.commonColor,
|
||||
icon: SvgPicture.asset(
|
||||
"assets/display.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: translate('Display Settings'),
|
||||
position: mod_menu.PopupMenuPosition.under,
|
||||
@@ -620,7 +635,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: _MenubarTheme.commonColor,
|
||||
commonColor: _MenubarTheme.blueColor,
|
||||
height: _MenubarTheme.height,
|
||||
dividerHeight: _MenubarTheme.dividerHeight,
|
||||
)))
|
||||
@@ -641,9 +656,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
return mod_menu.PopupMenuButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(
|
||||
Icons.keyboard,
|
||||
color: _MenubarTheme.commonColor,
|
||||
icon: SvgPicture.asset(
|
||||
"assets/keyboard.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: translate('Keyboard Settings'),
|
||||
position: mod_menu.PopupMenuPosition.under,
|
||||
@@ -651,7 +666,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: _MenubarTheme.commonColor,
|
||||
commonColor: _MenubarTheme.blueColor,
|
||||
height: _MenubarTheme.height,
|
||||
dividerHeight: _MenubarTheme.dividerHeight,
|
||||
)))
|
||||
@@ -664,18 +679,22 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
return Consumer<FfiModel>(builder: ((context, value, child) {
|
||||
if (value.permissions['recording'] != false) {
|
||||
return Consumer<RecordingModel>(
|
||||
builder: (context, value, child) => IconButton(
|
||||
tooltip: value.start
|
||||
? translate('Stop session recording')
|
||||
: translate('Start session recording'),
|
||||
onPressed: () => value.toggle(),
|
||||
icon: Icon(
|
||||
value.start
|
||||
? Icons.pause_circle_filled
|
||||
: Icons.videocam_outlined,
|
||||
color: _MenubarTheme.commonColor,
|
||||
),
|
||||
));
|
||||
builder: (context, value, child) => MenuButton(
|
||||
tooltip: value.start
|
||||
? translate('Stop session recording')
|
||||
: translate('Start session recording'),
|
||||
onPressed: () => value.toggle(),
|
||||
child: SvgPicture.asset(
|
||||
"assets/rec.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
color:
|
||||
value.start ? _MenubarTheme.redColor : _MenubarTheme.blueColor,
|
||||
hoverColor: value.start
|
||||
? _MenubarTheme.hoverRedColor
|
||||
: _MenubarTheme.hoverBlueColor,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Offstage();
|
||||
}
|
||||
@@ -683,18 +702,132 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
|
||||
Widget _buildClose(BuildContext context) {
|
||||
return IconButton(
|
||||
return MenuButton(
|
||||
tooltip: translate('Close'),
|
||||
onPressed: () {
|
||||
clientClose(widget.id, widget.ffi.dialogManager);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: _MenubarTheme.commonColor,
|
||||
child: SvgPicture.asset(
|
||||
"assets/close.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
color: _MenubarTheme.redColor,
|
||||
hoverColor: _MenubarTheme.hoverRedColor,
|
||||
);
|
||||
}
|
||||
|
||||
final _chatButtonKey = GlobalKey();
|
||||
Widget _buildChat(BuildContext context) {
|
||||
FfiModel ffiModel = Provider.of<FfiModel>(context);
|
||||
return mod_menu.PopupMenuButton(
|
||||
key: _chatButtonKey,
|
||||
padding: EdgeInsets.zero,
|
||||
icon: SvgPicture.asset(
|
||||
"assets/chat.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: translate('Chat'),
|
||||
position: mod_menu.PopupMenuPosition.under,
|
||||
itemBuilder: (BuildContext context) => _getChatMenu(context)
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: _MenubarTheme.blueColor,
|
||||
height: _MenubarTheme.height,
|
||||
dividerHeight: _MenubarTheme.dividerHeight,
|
||||
)))
|
||||
.expand((i) => i)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getVoiceCallIcon() {
|
||||
switch (widget.ffi.chatModel.voiceCallStatus.value) {
|
||||
case VoiceCallStatus.waitingForResponse:
|
||||
return SvgPicture.asset(
|
||||
"assets/call_wait.svg",
|
||||
color: Colors.white,
|
||||
);
|
||||
|
||||
case VoiceCallStatus.connected:
|
||||
return SvgPicture.asset(
|
||||
"assets/call_end.svg",
|
||||
color: Colors.white,
|
||||
);
|
||||
default:
|
||||
return const Offstage();
|
||||
}
|
||||
}
|
||||
|
||||
String? _getVoiceCallTooltip() {
|
||||
switch (widget.ffi.chatModel.voiceCallStatus.value) {
|
||||
case VoiceCallStatus.waitingForResponse:
|
||||
return "Waiting";
|
||||
case VoiceCallStatus.connected:
|
||||
return "Disconnect";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildVoiceCall(BuildContext context) {
|
||||
return Obx(
|
||||
() {
|
||||
final tooltipText = _getVoiceCallTooltip();
|
||||
return tooltipText == null
|
||||
? const Offstage()
|
||||
: MenuButton(
|
||||
child: _getVoiceCallIcon(),
|
||||
tooltip: translate(tooltipText),
|
||||
onPressed: () => bind.sessionCloseVoiceCall(id: widget.id),
|
||||
color: _MenubarTheme.redColor,
|
||||
hoverColor: _MenubarTheme.hoverRedColor,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<MenuEntryBase<String>> _getChatMenu(BuildContext context) {
|
||||
final List<MenuEntryBase<String>> chatMenu = [];
|
||||
const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0);
|
||||
chatMenu.addAll([
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Text chat'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
RenderBox? renderBox =
|
||||
_chatButtonKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
Offset? initPos;
|
||||
if (renderBox != null) {
|
||||
final pos = renderBox.localToGlobal(Offset.zero);
|
||||
initPos = Offset(pos.dx, pos.dy + _MenubarTheme.dividerHeight);
|
||||
}
|
||||
|
||||
widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID);
|
||||
widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Voice call'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
// Request a voice call.
|
||||
bind.sessionRequestVoiceCall(id: widget.id);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
]);
|
||||
return chatMenu;
|
||||
}
|
||||
|
||||
List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
|
||||
final pi = widget.ffi.ffiModel.pi;
|
||||
final perms = widget.ffi.ffiModel.permissions;
|
||||
@@ -884,7 +1017,6 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
// ));
|
||||
// }
|
||||
}
|
||||
|
||||
return displayMenu;
|
||||
}
|
||||
|
||||
@@ -1382,25 +1514,32 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () {
|
||||
List<MenuEntryRadioOption> list = [];
|
||||
List<String> modes = ["legacy"];
|
||||
List<KeyboardModeMenu> modes = [
|
||||
KeyboardModeMenu(key: 'legacy', menu: 'Legacy mode'),
|
||||
KeyboardModeMenu(key: 'map', menu: 'Map mode'),
|
||||
KeyboardModeMenu(key: 'translate', menu: 'Translate mode'),
|
||||
];
|
||||
|
||||
if (bind.sessionIsKeyboardModeSupported(id: widget.id, mode: "map")) {
|
||||
modes.add("map");
|
||||
}
|
||||
|
||||
for (String mode in modes) {
|
||||
if (mode == "legacy") {
|
||||
list.add(MenuEntryRadioOption(
|
||||
text: translate('Legacy mode'), value: 'legacy'));
|
||||
} else if (mode == "map") {
|
||||
list.add(MenuEntryRadioOption(
|
||||
text: translate('Map mode'), value: 'map'));
|
||||
for (KeyboardModeMenu mode in modes) {
|
||||
if (bind.sessionIsKeyboardModeSupported(
|
||||
id: widget.id, mode: mode.key)) {
|
||||
if (mode.key == 'translate') {
|
||||
if (Platform.isLinux ||
|
||||
widget.ffi.ffiModel.pi.platform == kPeerPlatformLinux) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var text = translate(mode.menu);
|
||||
if (mode.key == 'translate') {
|
||||
text = '$text beta';
|
||||
}
|
||||
list.add(MenuEntryRadioOption(text: text, value: mode.key));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
},
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy";
|
||||
return await bind.sessionGetKeyboardMode(id: widget.id) ?? 'legacy';
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionSetKeyboardMode(id: widget.id, value: newValue);
|
||||
@@ -1626,7 +1765,7 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
child: Icon(
|
||||
Icons.drag_indicator,
|
||||
size: 20,
|
||||
color: Colors.grey,
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
feedback: widget,
|
||||
onDragStarted: (() {
|
||||
@@ -1679,7 +1818,9 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: MyTheme.border),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
bottom: Radius.circular(5),
|
||||
),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
@@ -1689,3 +1830,10 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KeyboardModeMenu {
|
||||
final String key;
|
||||
final String menu;
|
||||
|
||||
KeyboardModeMenu({required this.key, required this.menu});
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ class DesktopTab extends StatelessWidget {
|
||||
Key? key,
|
||||
required this.controller,
|
||||
this.showLogo = true,
|
||||
this.showTitle = true,
|
||||
this.showTitle = false,
|
||||
this.showMinimize = true,
|
||||
this.showMaximize = true,
|
||||
this.showClose = true,
|
||||
@@ -327,14 +327,32 @@ class DesktopTab extends StatelessWidget {
|
||||
));
|
||||
}
|
||||
|
||||
List<Widget> _tabWidgets = [];
|
||||
Widget _buildPageView() {
|
||||
return _buildBlock(
|
||||
child: Obx(() => PageView(
|
||||
controller: state.value.pageController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: state.value.tabs
|
||||
.map((tab) => tab.page)
|
||||
.toList(growable: false))));
|
||||
children: () {
|
||||
/// to-do refactor, separate connection state and UI state for remote session.
|
||||
/// [workaround] PageView children need an immutable list, after it has been passed into PageView
|
||||
final tabLen = state.value.tabs.length;
|
||||
if (tabLen == _tabWidgets.length) {
|
||||
return _tabWidgets;
|
||||
} else if (_tabWidgets.isNotEmpty &&
|
||||
tabLen == _tabWidgets.length + 1) {
|
||||
/// On add. Use the previous list(pointer) to prevent item's state init twice.
|
||||
/// *[_tabWidgets.isNotEmpty] means TabsWindow(remote_tab_page or file_manager_tab_page) opened before, but was hidden. In this case, we have to reload, otherwise the child can't be built.
|
||||
_tabWidgets.add(state.value.tabs.last.page);
|
||||
return _tabWidgets;
|
||||
} else {
|
||||
/// On remove or change. Use new list(pointer) to reload list children so that items loading order is normal.
|
||||
/// the Widgets in list must enable [AutomaticKeepAliveClientMixin]
|
||||
final newList = state.value.tabs.map((v) => v.page).toList();
|
||||
_tabWidgets = newList;
|
||||
return newList;
|
||||
}
|
||||
}())));
|
||||
}
|
||||
|
||||
/// Check whether to show ListView
|
||||
@@ -767,7 +785,8 @@ class _ListView extends StatelessWidget {
|
||||
tabBuilder: tabBuilder,
|
||||
tabMenuBuilder: tabMenuBuilder,
|
||||
maxLabelWidth: maxLabelWidth,
|
||||
selectedTabBackgroundColor: selectedTabBackgroundColor ?? MyTheme.tabbar(context).selectedTabBackgroundColor,
|
||||
selectedTabBackgroundColor: selectedTabBackgroundColor ??
|
||||
MyTheme.tabbar(context).selectedTabBackgroundColor,
|
||||
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
|
||||
);
|
||||
}).toList()));
|
||||
@@ -1121,7 +1140,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
|
||||
dividerColor: dividerColor ?? this.dividerColor,
|
||||
hoverColor: hoverColor ?? this.hoverColor,
|
||||
closeHoverColor: closeHoverColor ?? this.closeHoverColor,
|
||||
selectedTabBackgroundColor: selectedTabBackgroundColor ?? this.selectedTabBackgroundColor,
|
||||
selectedTabBackgroundColor:
|
||||
selectedTabBackgroundColor ?? this.selectedTabBackgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1147,7 +1167,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
|
||||
dividerColor: Color.lerp(dividerColor, other.dividerColor, t),
|
||||
hoverColor: Color.lerp(hoverColor, other.hoverColor, t),
|
||||
closeHoverColor: Color.lerp(closeHoverColor, other.closeHoverColor, t),
|
||||
selectedTabBackgroundColor: Color.lerp(selectedTabBackgroundColor, other.selectedTabBackgroundColor, t),
|
||||
selectedTabBackgroundColor: Color.lerp(
|
||||
selectedTabBackgroundColor, other.selectedTabBackgroundColor, t),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,47 +24,8 @@ class DesktopTitleBar extends StatelessWidget {
|
||||
Expanded(
|
||||
child: child ?? Offstage(),
|
||||
)
|
||||
// const WindowButtons()
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// final buttonColors = WindowButtonColors(
|
||||
// iconNormal: const Color(0xFF805306),
|
||||
// mouseOver: const Color(0xFFF6A00C),
|
||||
// mouseDown: const Color(0xFF805306),
|
||||
// iconMouseOver: const Color(0xFF805306),
|
||||
// iconMouseDown: const Color(0xFFFFD500));
|
||||
//
|
||||
// final closeButtonColors = WindowButtonColors(
|
||||
// mouseOver: const Color(0xFFD32F2F),
|
||||
// mouseDown: const Color(0xFFB71C1C),
|
||||
// iconNormal: const Color(0xFF805306),
|
||||
// iconMouseOver: Colors.white);
|
||||
//
|
||||
// class WindowButtons extends StatelessWidget {
|
||||
// const WindowButtons({Key? key}) : super(key: key);
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Row(
|
||||
// children: [
|
||||
// MinimizeWindowButton(colors: buttonColors, onPressed: () {
|
||||
// windowManager.minimize();
|
||||
// },),
|
||||
// MaximizeWindowButton(colors: buttonColors, onPressed: () async {
|
||||
// if (await windowManager.isMaximized()) {
|
||||
// windowManager.restore();
|
||||
// } else {
|
||||
// windowManager.maximize();
|
||||
// }
|
||||
// },),
|
||||
// CloseWindowButton(colors: closeButtonColors, onPressed: () {
|
||||
// windowManager.close();
|
||||
// },),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -216,6 +216,7 @@ void runMultiWindow(
|
||||
|
||||
void runConnectionManagerScreen(bool hide) async {
|
||||
await initEnv(kAppTypeConnectionManager);
|
||||
await bind.cmStartListenIpcThread();
|
||||
_runApp(
|
||||
'',
|
||||
const DesktopServerPage(),
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/mobile/widgets/gesture_help.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
@@ -17,6 +18,7 @@ import '../../common/widgets/remote_input.dart';
|
||||
import '../../models/input_model.dart';
|
||||
import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../../utils/image.dart';
|
||||
import '../widgets/dialog.dart';
|
||||
import '../widgets/gestures.dart';
|
||||
|
||||
@@ -32,17 +34,16 @@ class RemotePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RemotePageState extends State<RemotePage> {
|
||||
Timer? _interval;
|
||||
Timer? _timer;
|
||||
bool _showBar = !isWebDesktop;
|
||||
double _bottom = 0;
|
||||
bool _showGestureHelp = false;
|
||||
String _value = '';
|
||||
double _scale = 1;
|
||||
double _mouseScrollIntegral = 0; // mouse scroll speed controller
|
||||
Orientation? _currentOrientation;
|
||||
|
||||
var _more = true;
|
||||
var _fn = false;
|
||||
final keyboardVisibilityController = KeyboardVisibilityController();
|
||||
late final StreamSubscription<bool> keyboardSubscription;
|
||||
final FocusNode _mobileFocusNode = FocusNode();
|
||||
final FocusNode _physicalFocusNode = FocusNode();
|
||||
var _showEdit = false; // use soft keyboard
|
||||
@@ -57,14 +58,14 @@ class _RemotePageState extends State<RemotePage> {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
gFFI.dialogManager
|
||||
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
||||
_interval =
|
||||
Timer.periodic(Duration(milliseconds: 30), (timer) => interval());
|
||||
});
|
||||
Wakelock.enable();
|
||||
_physicalFocusNode.requestFocus();
|
||||
gFFI.ffiModel.updateEventListener(widget.id);
|
||||
gFFI.inputModel.listenToMouse(true);
|
||||
gFFI.qualityMonitorModel.checkShowQualityMonitor(widget.id);
|
||||
keyboardSubscription =
|
||||
keyboardVisibilityController.onChange.listen(onSoftKeyboardChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -75,47 +76,26 @@ class _RemotePageState extends State<RemotePage> {
|
||||
_mobileFocusNode.dispose();
|
||||
_physicalFocusNode.dispose();
|
||||
gFFI.close();
|
||||
_interval?.cancel();
|
||||
_timer?.cancel();
|
||||
gFFI.dialogManager.dismissAll();
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values);
|
||||
Wakelock.disable();
|
||||
keyboardSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void resetTool() {
|
||||
inputModel.resetModifiers();
|
||||
}
|
||||
|
||||
bool isKeyboardShown() {
|
||||
return _bottom >= 100;
|
||||
}
|
||||
|
||||
// crash on web before widget initiated.
|
||||
void intervalUnsafe() {
|
||||
var v = MediaQuery.of(context).viewInsets.bottom;
|
||||
if (v != _bottom) {
|
||||
resetTool();
|
||||
setState(() {
|
||||
_bottom = v;
|
||||
if (v < 100) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: []);
|
||||
// [pi.version.isNotEmpty] -> check ready or not, avoid login without soft-keyboard
|
||||
if (gFFI.chatModel.chatWindowOverlayEntry == null &&
|
||||
gFFI.ffiModel.pi.version.isNotEmpty) {
|
||||
gFFI.invokeMethod("enable_soft_keyboard", false);
|
||||
}
|
||||
}
|
||||
});
|
||||
void onSoftKeyboardChanged(bool visible) {
|
||||
if (!visible) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
// [pi.version.isNotEmpty] -> check ready or not, avoid login without soft-keyboard
|
||||
if (gFFI.chatModel.chatWindowOverlayEntry == null &&
|
||||
gFFI.ffiModel.pi.version.isNotEmpty) {
|
||||
gFFI.invokeMethod("enable_soft_keyboard", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void interval() {
|
||||
try {
|
||||
intervalUnsafe();
|
||||
} catch (e) {}
|
||||
// update for Scaffold
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
// handle mobile virtual keyboard
|
||||
@@ -218,8 +198,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pi = Provider.of<FfiModel>(context).pi;
|
||||
final hideKeyboard = isKeyboardShown() && _showEdit;
|
||||
final showActionButton = !_showBar || hideKeyboard;
|
||||
final keyboardIsVisible =
|
||||
keyboardVisibilityController.isVisible && _showEdit;
|
||||
final showActionButton = !_showBar || keyboardIsVisible || _showGestureHelp;
|
||||
final keyboard = gFFI.ffiModel.permissions['keyboard'] != false;
|
||||
|
||||
return WillPopScope(
|
||||
@@ -228,29 +209,40 @@ class _RemotePageState extends State<RemotePage> {
|
||||
return false;
|
||||
},
|
||||
child: getRawPointerAndKeyBody(Scaffold(
|
||||
// resizeToAvoidBottomInset: true,
|
||||
// workaround for https://github.com/rustdesk/rustdesk/issues/3131
|
||||
floatingActionButtonLocation: keyboardIsVisible
|
||||
? FABLocation(FloatingActionButtonLocation.endFloat, 0, -35)
|
||||
: null,
|
||||
floatingActionButton: !showActionButton
|
||||
? null
|
||||
: FloatingActionButton(
|
||||
mini: !hideKeyboard,
|
||||
mini: !keyboardIsVisible,
|
||||
child: Icon(
|
||||
hideKeyboard ? Icons.expand_more : Icons.expand_less),
|
||||
(keyboardIsVisible || _showGestureHelp)
|
||||
? Icons.expand_more
|
||||
: Icons.expand_less,
|
||||
color: Colors.white,
|
||||
),
|
||||
backgroundColor: MyTheme.accent,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (hideKeyboard) {
|
||||
if (keyboardIsVisible) {
|
||||
_showEdit = false;
|
||||
gFFI.invokeMethod("enable_soft_keyboard", false);
|
||||
_mobileFocusNode.unfocus();
|
||||
_physicalFocusNode.requestFocus();
|
||||
} else if (_showGestureHelp) {
|
||||
_showGestureHelp = false;
|
||||
} else {
|
||||
_showBar = !_showBar;
|
||||
}
|
||||
});
|
||||
}),
|
||||
bottomNavigationBar: _showBar && pi.displays.isNotEmpty
|
||||
? getBottomAppBar(keyboard)
|
||||
: null,
|
||||
bottomNavigationBar: _showGestureHelp
|
||||
? getGestureHelp()
|
||||
: (_showBar && pi.displays.isNotEmpty
|
||||
? getBottomAppBar(keyboard)
|
||||
: null),
|
||||
body: Overlay(
|
||||
initialEntries: [
|
||||
OverlayEntry(builder: (context) {
|
||||
@@ -340,7 +332,8 @@ class _RemotePageState extends State<RemotePage> {
|
||||
icon: Icon(gFFI.ffiModel.touchMode
|
||||
? Icons.touch_app
|
||||
: Icons.mouse),
|
||||
onPressed: changeTouchMode,
|
||||
onPressed: () => setState(
|
||||
() => _showGestureHelp = !_showGestureHelp),
|
||||
),
|
||||
]) +
|
||||
(isWeb
|
||||
@@ -492,6 +485,7 @@ class _RemotePageState extends State<RemotePage> {
|
||||
}
|
||||
|
||||
Widget getBodyForMobile() {
|
||||
final keyboardIsVisible = keyboardVisibilityController.isVisible;
|
||||
return Container(
|
||||
color: MyTheme.canvasColor,
|
||||
child: Stack(children: () {
|
||||
@@ -502,7 +496,7 @@ class _RemotePageState extends State<RemotePage> {
|
||||
right: 10,
|
||||
child: QualityMonitor(gFFI.qualityMonitorModel),
|
||||
),
|
||||
getHelpTools(),
|
||||
KeyHelpTools(requestShow: (keyboardIsVisible || _showGestureHelp)),
|
||||
SizedBox(
|
||||
width: 0,
|
||||
height: 0,
|
||||
@@ -575,9 +569,10 @@ class _RemotePageState extends State<RemotePage> {
|
||||
child: Text(translate('Reset canvas')), value: 'reset_canvas'));
|
||||
}
|
||||
if (perms['keyboard'] != false) {
|
||||
more.add(PopupMenuItem<String>(
|
||||
child: Text(translate('Physical Keyboard Input Mode')),
|
||||
value: 'input-mode'));
|
||||
// * Currently mobile does not enable map mode
|
||||
// more.add(PopupMenuItem<String>(
|
||||
// child: Text(translate('Physical Keyboard Input Mode')),
|
||||
// value: 'input-mode'));
|
||||
if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) {
|
||||
more.add(PopupMenuItem<String>(
|
||||
child: Text('${translate('Insert')} Ctrl + Alt + Del'),
|
||||
@@ -632,8 +627,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
);
|
||||
if (value == 'cad') {
|
||||
bind.sessionCtrlAltDel(id: widget.id);
|
||||
} else if (value == 'input-mode') {
|
||||
changePhysicalKeyboardInputMode();
|
||||
// * Currently mobile does not enable map mode
|
||||
// } else if (value == 'input-mode') {
|
||||
// changePhysicalKeyboardInputMode();
|
||||
} else if (value == 'lock') {
|
||||
bind.sessionLockScreen(id: widget.id);
|
||||
} else if (value == 'block-input') {
|
||||
@@ -670,94 +666,110 @@ class _RemotePageState extends State<RemotePage> {
|
||||
}();
|
||||
}
|
||||
|
||||
void changeTouchMode() {
|
||||
setState(() => _showEdit = false);
|
||||
showModalBottomSheet(
|
||||
// backgroundColor: MyTheme.grayBg,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(5))),
|
||||
builder: (context) => DraggableScrollableSheet(
|
||||
expand: false,
|
||||
builder: (context, scrollController) {
|
||||
return SingleChildScrollView(
|
||||
controller: ScrollController(),
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: GestureHelp(
|
||||
touchMode: gFFI.ffiModel.touchMode,
|
||||
onTouchModeChange: (t) {
|
||||
gFFI.ffiModel.toggleTouchMode();
|
||||
final v = gFFI.ffiModel.touchMode ? 'Y' : '';
|
||||
bind.sessionPeerOption(
|
||||
id: widget.id, name: "touch", value: v);
|
||||
}));
|
||||
}));
|
||||
/// aka changeTouchMode
|
||||
BottomAppBar getGestureHelp() {
|
||||
return BottomAppBar(
|
||||
child: SingleChildScrollView(
|
||||
controller: ScrollController(),
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: GestureHelp(
|
||||
touchMode: gFFI.ffiModel.touchMode,
|
||||
onTouchModeChange: (t) {
|
||||
gFFI.ffiModel.toggleTouchMode();
|
||||
final v = gFFI.ffiModel.touchMode ? 'Y' : '';
|
||||
bind.sessionPeerOption(
|
||||
id: widget.id, name: "touch", value: v);
|
||||
})));
|
||||
}
|
||||
|
||||
void changePhysicalKeyboardInputMode() async {
|
||||
var current = await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy";
|
||||
gFFI.dialogManager.show((setState, close) {
|
||||
void setMode(String? v) async {
|
||||
await bind.sessionPeerOption(
|
||||
id: widget.id, name: "keyboard-mode", value: v ?? "");
|
||||
setState(() => current = v ?? '');
|
||||
Future.delayed(Duration(milliseconds: 300), close);
|
||||
}
|
||||
// * Currently mobile does not enable map mode
|
||||
// void changePhysicalKeyboardInputMode() async {
|
||||
// var current = await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy";
|
||||
// gFFI.dialogManager.show((setState, close) {
|
||||
// void setMode(String? v) async {
|
||||
// await bind.sessionSetKeyboardMode(id: widget.id, value: v ?? "");
|
||||
// setState(() => current = v ?? '');
|
||||
// Future.delayed(Duration(milliseconds: 300), close);
|
||||
// }
|
||||
//
|
||||
// return CustomAlertDialog(
|
||||
// title: Text(translate('Physical Keyboard Input Mode')),
|
||||
// content: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
// getRadio('Legacy mode', 'legacy', current, setMode,
|
||||
// contentPadding: EdgeInsets.zero),
|
||||
// getRadio('Map mode', 'map', current, setMode,
|
||||
// contentPadding: EdgeInsets.zero),
|
||||
// ]));
|
||||
// }, clickMaskDismiss: true);
|
||||
// }
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate('Physical Keyboard Input Mode')),
|
||||
content: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
getRadio('Legacy mode', 'legacy', current, setMode,
|
||||
contentPadding: EdgeInsets.zero),
|
||||
getRadio('Map mode', 'map', current, setMode,
|
||||
contentPadding: EdgeInsets.zero),
|
||||
]));
|
||||
}, clickMaskDismiss: true);
|
||||
class KeyHelpTools extends StatefulWidget {
|
||||
/// need to show by external request, etc [keyboardIsVisible] or [changeTouchMode]
|
||||
final bool requestShow;
|
||||
|
||||
KeyHelpTools({required this.requestShow});
|
||||
|
||||
@override
|
||||
State<KeyHelpTools> createState() => _KeyHelpToolsState();
|
||||
}
|
||||
|
||||
class _KeyHelpToolsState extends State<KeyHelpTools> {
|
||||
var _more = true;
|
||||
var _fn = false;
|
||||
var _pin = false;
|
||||
final _keyboardVisibilityController = KeyboardVisibilityController();
|
||||
|
||||
InputModel get inputModel => gFFI.inputModel;
|
||||
|
||||
Widget wrap(String text, void Function() onPressed,
|
||||
{bool? active, IconData? icon}) {
|
||||
return TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
minimumSize: Size(0, 0),
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 9.75),
|
||||
//adds padding inside the button
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
//limits the touch area to the button area
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
),
|
||||
backgroundColor: active == true ? MyTheme.accent80 : null,
|
||||
),
|
||||
child: icon != null
|
||||
? Icon(icon, size: 14, color: Colors.white)
|
||||
: Text(translate(text),
|
||||
style: TextStyle(color: Colors.white, fontSize: 11)),
|
||||
onPressed: onPressed);
|
||||
}
|
||||
|
||||
Widget getHelpTools() {
|
||||
final keyboard = isKeyboardShown();
|
||||
if (!keyboard) {
|
||||
return SizedBox();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasModifierOn = inputModel.ctrl ||
|
||||
inputModel.alt ||
|
||||
inputModel.shift ||
|
||||
inputModel.command;
|
||||
|
||||
if (!_pin && !hasModifierOn && !widget.requestShow) {
|
||||
return Offstage();
|
||||
}
|
||||
final size = MediaQuery.of(context).size;
|
||||
wrap(String text, void Function() onPressed,
|
||||
[bool? active, IconData? icon]) {
|
||||
return TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
minimumSize: Size(0, 0),
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 9.75),
|
||||
//adds padding inside the button
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
//limits the touch area to the button area
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
),
|
||||
backgroundColor: active == true ? MyTheme.accent80 : null,
|
||||
),
|
||||
child: icon != null
|
||||
? Icon(icon, size: 17, color: Colors.white)
|
||||
: Text(translate(text),
|
||||
style: TextStyle(color: Colors.white, fontSize: 11)),
|
||||
onPressed: onPressed);
|
||||
}
|
||||
|
||||
final pi = gFFI.ffiModel.pi;
|
||||
final isMac = pi.platform == kPeerPlatformMacOS;
|
||||
final modifiers = <Widget>[
|
||||
wrap('Ctrl ', () {
|
||||
setState(() => inputModel.ctrl = !inputModel.ctrl);
|
||||
}, inputModel.ctrl),
|
||||
}, active: inputModel.ctrl),
|
||||
wrap(' Alt ', () {
|
||||
setState(() => inputModel.alt = !inputModel.alt);
|
||||
}, inputModel.alt),
|
||||
}, active: inputModel.alt),
|
||||
wrap('Shift', () {
|
||||
setState(() => inputModel.shift = !inputModel.shift);
|
||||
}, inputModel.shift),
|
||||
}, active: inputModel.shift),
|
||||
wrap(isMac ? ' Cmd ' : ' Win ', () {
|
||||
setState(() => inputModel.command = !inputModel.command);
|
||||
}, inputModel.command),
|
||||
}, active: inputModel.command),
|
||||
];
|
||||
final keys = <Widget>[
|
||||
wrap(
|
||||
@@ -770,7 +782,14 @@ class _RemotePageState extends State<RemotePage> {
|
||||
}
|
||||
},
|
||||
),
|
||||
_fn),
|
||||
active: _fn),
|
||||
wrap(
|
||||
'',
|
||||
() => setState(
|
||||
() => _pin = !_pin,
|
||||
),
|
||||
active: _pin,
|
||||
icon: Icons.push_pin),
|
||||
wrap(
|
||||
' ... ',
|
||||
() => setState(
|
||||
@@ -781,7 +800,7 @@ class _RemotePageState extends State<RemotePage> {
|
||||
}
|
||||
},
|
||||
),
|
||||
_more),
|
||||
active: _more),
|
||||
];
|
||||
final fn = <Widget>[
|
||||
SizedBox(width: 9999),
|
||||
@@ -806,6 +825,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
wrap('End', () {
|
||||
inputModel.inputKey('VK_END');
|
||||
}),
|
||||
wrap('Ins', () {
|
||||
inputModel.inputKey('VK_INSERT');
|
||||
}),
|
||||
wrap('Del', () {
|
||||
inputModel.inputKey('VK_DELETE');
|
||||
}),
|
||||
@@ -818,16 +840,16 @@ class _RemotePageState extends State<RemotePage> {
|
||||
SizedBox(width: 9999),
|
||||
wrap('', () {
|
||||
inputModel.inputKey('VK_LEFT');
|
||||
}, false, Icons.keyboard_arrow_left),
|
||||
}, icon: Icons.keyboard_arrow_left),
|
||||
wrap('', () {
|
||||
inputModel.inputKey('VK_UP');
|
||||
}, false, Icons.keyboard_arrow_up),
|
||||
}, icon: Icons.keyboard_arrow_up),
|
||||
wrap('', () {
|
||||
inputModel.inputKey('VK_DOWN');
|
||||
}, false, Icons.keyboard_arrow_down),
|
||||
}, icon: Icons.keyboard_arrow_down),
|
||||
wrap('', () {
|
||||
inputModel.inputKey('VK_RIGHT');
|
||||
}, false, Icons.keyboard_arrow_right),
|
||||
}, icon: Icons.keyboard_arrow_right),
|
||||
wrap(isMac ? 'Cmd+C' : 'Ctrl+C', () {
|
||||
sendPrompt(isMac, 'VK_C');
|
||||
}),
|
||||
@@ -842,14 +864,15 @@ class _RemotePageState extends State<RemotePage> {
|
||||
return Container(
|
||||
color: Color(0xAA000000),
|
||||
padding: EdgeInsets.only(
|
||||
top: keyboard ? 24 : 4, left: 0, right: 0, bottom: 8),
|
||||
top: _keyboardVisibilityController.isVisible ? 24 : 4, bottom: 8),
|
||||
child: Wrap(
|
||||
spacing: space,
|
||||
runSpacing: space,
|
||||
children: <Widget>[SizedBox(width: 9999)] +
|
||||
(keyboard
|
||||
? modifiers + keys + (_fn ? fn : []) + (_more ? more : [])
|
||||
: modifiers),
|
||||
modifiers +
|
||||
keys +
|
||||
(_fn ? fn : []) +
|
||||
(_more ? more : []),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -893,32 +916,6 @@ class CursorPaint extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ImagePainter extends CustomPainter {
|
||||
ImagePainter({
|
||||
required this.image,
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.scale,
|
||||
});
|
||||
|
||||
ui.Image? image;
|
||||
double x;
|
||||
double y;
|
||||
double scale;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (image == null) return;
|
||||
canvas.scale(scale, scale);
|
||||
canvas.drawImage(image!, Offset(x, y), Paint());
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||
return oldDelegate != this;
|
||||
}
|
||||
}
|
||||
|
||||
void showOptions(
|
||||
BuildContext context, String id, OverlayDialogManager dialogManager) async {
|
||||
String quality =
|
||||
@@ -1134,3 +1131,16 @@ void sendPrompt(bool isMac, String key) {
|
||||
gFFI.inputModel.ctrl = old;
|
||||
}
|
||||
}
|
||||
|
||||
class FABLocation extends FloatingActionButtonLocation {
|
||||
FloatingActionButtonLocation location;
|
||||
double offsetX;
|
||||
double offsetY;
|
||||
FABLocation(this.location, this.offsetX, this.offsetY);
|
||||
|
||||
@override
|
||||
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
||||
final offset = location.getOffset(scaffoldGeometry);
|
||||
return Offset(offset.dx + offsetX, offset.dy + offsetY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:toggle_switch/toggle_switch.dart';
|
||||
|
||||
import '../../models/model.dart';
|
||||
|
||||
class GestureIcons {
|
||||
static const String _family = 'gestureicons';
|
||||
|
||||
@@ -79,7 +77,10 @@ class _GestureHelpState extends State<GestureHelp> {
|
||||
children: <Widget>[
|
||||
ToggleSwitch(
|
||||
initialLabelIndex: _selectedIndex,
|
||||
inactiveBgColor: MyTheme.darkGray,
|
||||
activeFgColor: Colors.white,
|
||||
inactiveFgColor: Colors.white60,
|
||||
activeBgColor: [MyTheme.accent],
|
||||
inactiveBgColor: Theme.of(context).hintColor,
|
||||
totalSwitches: 2,
|
||||
minWidth: 150,
|
||||
fontSize: 15,
|
||||
@@ -188,7 +189,7 @@ class GestureInfo extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: this.width,
|
||||
width: width,
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
@@ -199,11 +200,14 @@ class GestureInfo extends StatelessWidget {
|
||||
SizedBox(height: 6),
|
||||
Text(fromText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 9, color: Colors.grey)),
|
||||
style:
|
||||
TextStyle(fontSize: 9, color: Theme.of(context).hintColor)),
|
||||
SizedBox(height: 3),
|
||||
Text(toText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 12, color: Colors.black))
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color))
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dash_chat_2/dash_chat_2.dart';
|
||||
import 'package:draggable_float_widget/draggable_float_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../consts.dart';
|
||||
@@ -26,15 +30,17 @@ class MessageBody {
|
||||
class ChatModel with ChangeNotifier {
|
||||
static final clientModeID = -1;
|
||||
|
||||
/// _overlayState:
|
||||
/// Desktop: store session overlay by using [setOverlayState].
|
||||
/// Mobile: always null, use global overlay.
|
||||
/// see [_getOverlayState] in [showChatIconOverlay] or [showChatWindowOverlay]
|
||||
OverlayState? _overlayState;
|
||||
OverlayEntry? chatIconOverlayEntry;
|
||||
OverlayEntry? chatWindowOverlayEntry;
|
||||
|
||||
bool isConnManager = false;
|
||||
|
||||
RxBool isWindowFocus = true.obs;
|
||||
BlockableOverlayState? _blockableOverlayState;
|
||||
final Rx<VoiceCallStatus> _voiceCallStatus = Rx(VoiceCallStatus.notStarted);
|
||||
|
||||
Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus;
|
||||
|
||||
final ChatUser me = ChatUser(
|
||||
id: "",
|
||||
firstName: "Me",
|
||||
@@ -52,6 +58,19 @@ class ChatModel with ChangeNotifier {
|
||||
|
||||
bool get isShowCMChatPage => _isShowCMChatPage;
|
||||
|
||||
void setOverlayState(BlockableOverlayState blockableOverlayState) {
|
||||
_blockableOverlayState = blockableOverlayState;
|
||||
|
||||
_blockableOverlayState!.addMiddleBlockedListener((v) {
|
||||
if (!v) {
|
||||
isWindowFocus.value = false;
|
||||
if (isWindowFocus.value) {
|
||||
isWindowFocus.toggle();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final WeakReference<FFI> parent;
|
||||
|
||||
ChatModel(this.parent);
|
||||
@@ -68,20 +87,6 @@ class ChatModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
setOverlayState(OverlayState? os) {
|
||||
_overlayState = os;
|
||||
}
|
||||
|
||||
OverlayState? _getOverlayState() {
|
||||
if (_overlayState == null) {
|
||||
if (globalKey.currentState == null ||
|
||||
globalKey.currentState!.overlay == null) return null;
|
||||
return globalKey.currentState!.overlay;
|
||||
} else {
|
||||
return _overlayState;
|
||||
}
|
||||
}
|
||||
|
||||
showChatIconOverlay({Offset offset = const Offset(200, 50)}) {
|
||||
if (chatIconOverlayEntry != null) {
|
||||
chatIconOverlayEntry!.remove();
|
||||
@@ -94,7 +99,7 @@ class ChatModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
final overlayState = _getOverlayState();
|
||||
final overlayState = _blockableOverlayState?.state;
|
||||
if (overlayState == null) return;
|
||||
|
||||
final overlay = OverlayEntry(builder: (context) {
|
||||
@@ -126,23 +131,35 @@ class ChatModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
showChatWindowOverlay() {
|
||||
showChatWindowOverlay({Offset? chatInitPos}) {
|
||||
if (chatWindowOverlayEntry != null) return;
|
||||
final overlayState = _getOverlayState();
|
||||
isWindowFocus.value = true;
|
||||
_blockableOverlayState?.setMiddleBlocked(true);
|
||||
|
||||
final overlayState = _blockableOverlayState?.state;
|
||||
if (overlayState == null) return;
|
||||
final overlay = OverlayEntry(builder: (context) {
|
||||
return DraggableChatWindow(
|
||||
position: const Offset(20, 80),
|
||||
width: 250,
|
||||
height: 350,
|
||||
chatModel: this);
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
if (!isWindowFocus.value) {
|
||||
isWindowFocus.value = true;
|
||||
_blockableOverlayState?.setMiddleBlocked(true);
|
||||
}
|
||||
},
|
||||
child: DraggableChatWindow(
|
||||
position: chatInitPos ?? Offset(20, 80),
|
||||
width: 250,
|
||||
height: 350,
|
||||
chatModel: this));
|
||||
});
|
||||
overlayState.insert(overlay);
|
||||
chatWindowOverlayEntry = overlay;
|
||||
requestChatInputFocus();
|
||||
}
|
||||
|
||||
hideChatWindowOverlay() {
|
||||
if (chatWindowOverlayEntry != null) {
|
||||
_blockableOverlayState?.setMiddleBlocked(false);
|
||||
chatWindowOverlayEntry!.remove();
|
||||
chatWindowOverlayEntry = null;
|
||||
return;
|
||||
@@ -152,13 +169,13 @@ class ChatModel with ChangeNotifier {
|
||||
_isChatOverlayHide() => ((!isDesktop && chatIconOverlayEntry == null) ||
|
||||
chatWindowOverlayEntry == null);
|
||||
|
||||
toggleChatOverlay() {
|
||||
toggleChatOverlay({Offset? chatInitPos}) {
|
||||
if (_isChatOverlayHide()) {
|
||||
gFFI.invokeMethod("enable_soft_keyboard", true);
|
||||
if (!isDesktop) {
|
||||
showChatIconOverlay();
|
||||
}
|
||||
showChatWindowOverlay();
|
||||
showChatWindowOverlay(chatInitPos: chatInitPos);
|
||||
} else {
|
||||
hideChatIconOverlay();
|
||||
hideChatWindowOverlay();
|
||||
@@ -188,6 +205,7 @@ class ChatModel with ChangeNotifier {
|
||||
await windowManager.setSizeAlignment(
|
||||
kConnectionManagerWindowSize, Alignment.topRight);
|
||||
} else {
|
||||
requestChatInputFocus();
|
||||
await windowManager.show();
|
||||
await windowManager.setSizeAlignment(Size(600, 400), Alignment.topRight);
|
||||
_isShowCMChatPage = !_isShowCMChatPage;
|
||||
@@ -285,11 +303,48 @@ class ChatModel with ChangeNotifier {
|
||||
close() {
|
||||
hideChatIconOverlay();
|
||||
hideChatWindowOverlay();
|
||||
_overlayState = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
resetClientMode() {
|
||||
_messages[clientModeID]?.clear();
|
||||
}
|
||||
|
||||
void requestChatInputFocus() {
|
||||
Timer(Duration(milliseconds: 100), () {
|
||||
if (inputNode.hasListeners && inputNode.canRequestFocus) {
|
||||
inputNode.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void onVoiceCallWaiting() {
|
||||
_voiceCallStatus.value = VoiceCallStatus.waitingForResponse;
|
||||
}
|
||||
|
||||
void onVoiceCallStarted() {
|
||||
_voiceCallStatus.value = VoiceCallStatus.connected;
|
||||
}
|
||||
|
||||
void onVoiceCallClosed(String reason) {
|
||||
_voiceCallStatus.value = VoiceCallStatus.notStarted;
|
||||
}
|
||||
|
||||
void onVoiceCallIncoming() {
|
||||
if (isConnManager) {
|
||||
_voiceCallStatus.value = VoiceCallStatus.incoming;
|
||||
}
|
||||
}
|
||||
|
||||
void closeVoiceCall(String id) {
|
||||
bind.sessionCloseVoiceCall(id: id);
|
||||
}
|
||||
}
|
||||
|
||||
enum VoiceCallStatus {
|
||||
notStarted,
|
||||
waitingForResponse,
|
||||
connected,
|
||||
// Connection manager only.
|
||||
incoming
|
||||
}
|
||||
|
||||
@@ -75,6 +75,10 @@ class FileModel extends ChangeNotifier {
|
||||
return isLocal ? _localSortStyle : _remoteSortStyle;
|
||||
}
|
||||
|
||||
bool getSortAscending(bool isLocal) {
|
||||
return isLocal ? _localSortAscending : _remoteSortAscending;
|
||||
}
|
||||
|
||||
FileDirectory _currentLocalDir = FileDirectory();
|
||||
|
||||
FileDirectory get currentLocalDir => _currentLocalDir;
|
||||
|
||||
@@ -58,9 +58,12 @@ class InputModel {
|
||||
InputModel(this.parent);
|
||||
|
||||
KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) {
|
||||
bind.sessionGetKeyboardMode(id: id).then((result) {
|
||||
keyboardMode = result.toString();
|
||||
});
|
||||
// * Currently mobile does not enable map mode
|
||||
if (isDesktop) {
|
||||
bind.sessionGetKeyboardMode(id: id).then((result) {
|
||||
keyboardMode = result.toString();
|
||||
});
|
||||
}
|
||||
|
||||
final key = e.logicalKey;
|
||||
if (e is RawKeyDownEvent) {
|
||||
@@ -93,10 +96,9 @@ class InputModel {
|
||||
}
|
||||
}
|
||||
|
||||
if (keyboardMode == 'map') {
|
||||
// * Currently mobile does not enable map mode
|
||||
if (isDesktop && keyboardMode == 'map') {
|
||||
mapKeyboardMode(e);
|
||||
} else if (keyboardMode == 'translate') {
|
||||
legacyKeyboardMode(e);
|
||||
} else {
|
||||
legacyKeyboardMode(e);
|
||||
}
|
||||
@@ -483,10 +485,19 @@ class InputModel {
|
||||
y /= canvasModel.scale;
|
||||
x += d.x;
|
||||
y += d.y;
|
||||
|
||||
if (x < d.x || y < d.y || x > (d.x + d.width) || y > (d.y + d.height)) {
|
||||
// If left mouse up, no early return.
|
||||
if (evt['buttons'] != kPrimaryMouseButton || type != 'up') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (type != '') {
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
evt['x'] = '${x.round()}';
|
||||
evt['y'] = '${y.round()}';
|
||||
var buttons = '';
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi' hide Size;
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
@@ -18,7 +20,6 @@ import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:flutter_hbb/models/user_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:image/image.dart' as img2;
|
||||
import 'package:flutter_custom_cursor/cursor_manager.dart';
|
||||
@@ -26,13 +27,13 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../common.dart';
|
||||
import '../common/shared_state.dart';
|
||||
import '../utils/image.dart' as img;
|
||||
import '../mobile/widgets/dialog.dart';
|
||||
import 'input_model.dart';
|
||||
import 'platform_model.dart';
|
||||
|
||||
typedef HandleMsgBox = Function(Map<String, dynamic> evt, String id);
|
||||
typedef ReconnectHandle = Function(OverlayDialogManager, String, bool);
|
||||
final _waitForImage = <String, bool>{};
|
||||
|
||||
class FfiModel with ChangeNotifier {
|
||||
@@ -139,6 +140,8 @@ class FfiModel with ChangeNotifier {
|
||||
handleMsgBox(evt, peerId);
|
||||
} else if (name == 'peer_info') {
|
||||
handlePeerInfo(evt, peerId);
|
||||
} else if (name == 'sync_peer_info') {
|
||||
handleSyncPeerInfo(evt, peerId);
|
||||
} else if (name == 'connection_ready') {
|
||||
setConnectionType(
|
||||
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
|
||||
@@ -203,6 +206,23 @@ class FfiModel with ChangeNotifier {
|
||||
} else if (name == "on_url_scheme_received") {
|
||||
final url = evt['url'].toString();
|
||||
parseRustdeskUri(url);
|
||||
} else if (name == "on_voice_call_waiting") {
|
||||
// Waiting for the response from the peer.
|
||||
parent.target?.chatModel.onVoiceCallWaiting();
|
||||
} else if (name == "on_voice_call_started") {
|
||||
// Voice call is connected.
|
||||
parent.target?.chatModel.onVoiceCallStarted();
|
||||
} else if (name == "on_voice_call_closed") {
|
||||
// Voice call is closed with reason.
|
||||
final reason = evt['reason'].toString();
|
||||
parent.target?.chatModel.onVoiceCallClosed(reason);
|
||||
} else if (name == "on_voice_call_incoming") {
|
||||
// Voice call is requested by the peer.
|
||||
parent.target?.chatModel.onVoiceCallIncoming();
|
||||
} else if (name == "update_voice_call_state") {
|
||||
parent.target?.serverModel.updateVoiceCallState(evt);
|
||||
} else {
|
||||
debugPrint("Unknown event name: $name");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -281,6 +301,8 @@ class FfiModel with ChangeNotifier {
|
||||
showWaitUacDialog(id, dialogManager, type);
|
||||
} else if (type == 'elevation-error') {
|
||||
showElevationError(id, type, title, text, dialogManager);
|
||||
} else if (type == "relay-hint") {
|
||||
showRelayHintDialog(id, type, title, text, dialogManager);
|
||||
} else {
|
||||
var hasRetry = evt['hasRetry'] == 'true';
|
||||
showMsgBox(id, type, title, text, link, hasRetry, dialogManager);
|
||||
@@ -291,14 +313,12 @@ class FfiModel with ChangeNotifier {
|
||||
showMsgBox(String id, String type, String title, String text, String link,
|
||||
bool hasRetry, OverlayDialogManager dialogManager,
|
||||
{bool? hasCancel}) {
|
||||
msgBox(id, type, title, text, link, dialogManager, hasCancel: hasCancel);
|
||||
msgBox(id, type, title, text, link, dialogManager,
|
||||
hasCancel: hasCancel, reconnect: reconnect);
|
||||
_timer?.cancel();
|
||||
if (hasRetry) {
|
||||
_timer = Timer(Duration(seconds: _reconnects), () {
|
||||
bind.sessionReconnect(id: id);
|
||||
clearPermissions();
|
||||
dialogManager.showLoading(translate('Connecting...'),
|
||||
onCancel: closeConnection);
|
||||
reconnect(dialogManager, id, false);
|
||||
});
|
||||
_reconnects *= 2;
|
||||
} else {
|
||||
@@ -306,6 +326,47 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void reconnect(
|
||||
OverlayDialogManager dialogManager, String id, bool forceRelay) {
|
||||
bind.sessionReconnect(id: id, forceRelay: forceRelay);
|
||||
clearPermissions();
|
||||
dialogManager.showLoading(translate('Connecting...'),
|
||||
onCancel: closeConnection);
|
||||
}
|
||||
|
||||
void showRelayHintDialog(String id, String type, String title, String text,
|
||||
OverlayDialogManager dialogManager) {
|
||||
dialogManager.show(tag: '$id-$type', (setState, close) {
|
||||
onClose() {
|
||||
closeConnection();
|
||||
close();
|
||||
}
|
||||
|
||||
final style =
|
||||
ElevatedButton.styleFrom(backgroundColor: Colors.green[700]);
|
||||
return CustomAlertDialog(
|
||||
title: null,
|
||||
content: msgboxContent(type, title,
|
||||
"${translate(text)}\n\n${translate('relay_hint_tip')}"),
|
||||
actions: [
|
||||
dialogButton('Close', onPressed: onClose, isOutline: true),
|
||||
dialogButton('Retry',
|
||||
onPressed: () => reconnect(dialogManager, id, false)),
|
||||
dialogButton('Connect via relay',
|
||||
onPressed: () => reconnect(dialogManager, id, true),
|
||||
buttonStyle: style),
|
||||
dialogButton('Always connect via relay', onPressed: () {
|
||||
const option = 'force-always-relay';
|
||||
bind.sessionPeerOption(
|
||||
id: id, name: option, value: bool2option(option, true));
|
||||
reconnect(dialogManager, id, true);
|
||||
}, buttonStyle: style),
|
||||
],
|
||||
onCancel: onClose,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Handle the peer info event based on [evt].
|
||||
handlePeerInfo(Map<String, dynamic> evt, String peerId) async {
|
||||
// recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
|
||||
@@ -356,6 +417,7 @@ class FfiModel with ChangeNotifier {
|
||||
d.cursorEmbedded = d0['cursor_embedded'] == 1;
|
||||
_pi.displays.add(d);
|
||||
}
|
||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
||||
if (_pi.currentDisplay < _pi.displays.length) {
|
||||
_display = _pi.displays[_pi.currentDisplay];
|
||||
}
|
||||
@@ -372,6 +434,27 @@ class FfiModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Handle the peer info synchronization event based on [evt].
|
||||
handleSyncPeerInfo(Map<String, dynamic> evt, String peerId) async {
|
||||
if (evt['displays'] != null) {
|
||||
List<dynamic> displays = json.decode(evt['displays']);
|
||||
List<Display> newDisplays = [];
|
||||
for (int i = 0; i < displays.length; ++i) {
|
||||
Map<String, dynamic> d0 = displays[i];
|
||||
var d = Display();
|
||||
d.x = d0['x'].toDouble();
|
||||
d.y = d0['y'].toDouble();
|
||||
d.width = d0['width'];
|
||||
d.height = d0['height'];
|
||||
d.cursorEmbedded = d0['cursor_embedded'] == 1;
|
||||
newDisplays.add(d);
|
||||
}
|
||||
_pi.displays = newDisplays;
|
||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
|
||||
_inputBlocked = evt['input_state'] == 'on';
|
||||
notifyListeners();
|
||||
@@ -419,12 +502,17 @@ class ImageModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final pid = parent.target?.id;
|
||||
ui.decodeImageFromPixels(
|
||||
img.decodeImageFromPixels(
|
||||
rgba,
|
||||
parent.target?.ffiModel.display.width ?? 0,
|
||||
parent.target?.ffiModel.display.height ?? 0,
|
||||
isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888, (image) {
|
||||
isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888,
|
||||
onPixelsCopied: () {
|
||||
// Unlock the rgba memory from rust codes.
|
||||
platformFFI.nextRgba(id);
|
||||
}).then((image) {
|
||||
if (parent.target?.id != pid) return;
|
||||
try {
|
||||
// my throw exception, because the listener maybe already dispose
|
||||
@@ -1319,7 +1407,8 @@ class FFI {
|
||||
void start(String id,
|
||||
{bool isFileTransfer = false,
|
||||
bool isPortForward = false,
|
||||
String? switchUuid}) {
|
||||
String? switchUuid,
|
||||
bool? forceRelay}) {
|
||||
assert(!(isFileTransfer && isPortForward), 'more than one connect type');
|
||||
if (isFileTransfer) {
|
||||
connType = ConnType.fileTransfer;
|
||||
@@ -1335,16 +1424,20 @@ class FFI {
|
||||
}
|
||||
// ignore: unused_local_variable
|
||||
final addRes = bind.sessionAddSync(
|
||||
id: id,
|
||||
isFileTransfer: isFileTransfer,
|
||||
isPortForward: isPortForward,
|
||||
switchUuid: switchUuid ?? "",
|
||||
);
|
||||
id: id,
|
||||
isFileTransfer: isFileTransfer,
|
||||
isPortForward: isPortForward,
|
||||
switchUuid: switchUuid ?? "",
|
||||
forceRelay: forceRelay ?? false);
|
||||
final stream = bind.sessionStart(id: id);
|
||||
final cb = ffiModel.startEventListener(id);
|
||||
() async {
|
||||
// Preserved for the rgba data.
|
||||
await for (final message in stream) {
|
||||
if (message is EventToUI_Event) {
|
||||
if (message.field0 == "close") {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Map<String, dynamic> event = json.decode(message.field0);
|
||||
await cb(event);
|
||||
@@ -1352,9 +1445,18 @@ class FFI {
|
||||
debugPrint('json.decode fail1(): $e, ${message.field0}');
|
||||
}
|
||||
} else if (message is EventToUI_Rgba) {
|
||||
imageModel.onRgba(message.field0);
|
||||
// Fetch the image buffer from rust codes.
|
||||
final sz = platformFFI.getRgbaSize(id);
|
||||
if (sz == null || sz == 0) {
|
||||
return;
|
||||
}
|
||||
final rgba = platformFFI.getRgba(id, sz);
|
||||
if (rgba != null) {
|
||||
imageModel.onRgba(rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
debugPrint('Exit session event loop');
|
||||
}();
|
||||
// every instance will bind a stream
|
||||
this.id = id;
|
||||
@@ -1375,14 +1477,14 @@ class FFI {
|
||||
await setCanvasConfig(id, cursorModel.x, cursorModel.y, canvasModel.x,
|
||||
canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
|
||||
}
|
||||
bind.sessionClose(id: id);
|
||||
id = '';
|
||||
imageModel.update(null);
|
||||
cursorModel.clear();
|
||||
ffiModel.clear();
|
||||
canvasModel.clear();
|
||||
inputModel.resetModifiers();
|
||||
await bind.sessionClose(id: id);
|
||||
debugPrint('model $id closed');
|
||||
id = '';
|
||||
}
|
||||
|
||||
void setMethodCallHandler(FMethod callback) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:win32/win32.dart' as win32;
|
||||
@@ -23,7 +24,11 @@ class RgbaFrame extends Struct {
|
||||
}
|
||||
|
||||
typedef F2 = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);
|
||||
typedef F3 = void Function(Pointer<Utf8>, Pointer<Utf8>);
|
||||
typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>);
|
||||
typedef F4 = Uint64 Function(Pointer<Utf8>);
|
||||
typedef F4Dart = int Function(Pointer<Utf8>);
|
||||
typedef F5 = Void Function(Pointer<Utf8>);
|
||||
typedef F5Dart = void Function(Pointer<Utf8>);
|
||||
typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt);
|
||||
|
||||
/// FFI wrapper around the native Rust core.
|
||||
@@ -44,6 +49,9 @@ class PlatformFFI {
|
||||
final _toAndroidChannel = const MethodChannel('mChannel');
|
||||
|
||||
RustdeskImpl get ffiBind => _ffiBind;
|
||||
F3? _session_get_rgba;
|
||||
F4Dart? _session_get_rgba_size;
|
||||
F5Dart? _session_next_rgba;
|
||||
|
||||
static get localeName => Platform.localeName;
|
||||
|
||||
@@ -92,6 +100,36 @@ class PlatformFFI {
|
||||
return res;
|
||||
}
|
||||
|
||||
Uint8List? getRgba(String id, int bufSize) {
|
||||
if (_session_get_rgba == null) return null;
|
||||
var a = id.toNativeUtf8();
|
||||
try {
|
||||
final buffer = _session_get_rgba!(a);
|
||||
if (buffer == nullptr) {
|
||||
return null;
|
||||
}
|
||||
final data = buffer.asTypedList(bufSize);
|
||||
return data;
|
||||
} finally {
|
||||
malloc.free(a);
|
||||
}
|
||||
}
|
||||
|
||||
int? getRgbaSize(String id) {
|
||||
if (_session_get_rgba_size == null) return null;
|
||||
var a = id.toNativeUtf8();
|
||||
final bufferSize = _session_get_rgba_size!(a);
|
||||
malloc.free(a);
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
void nextRgba(String id) {
|
||||
if (_session_next_rgba == null) return;
|
||||
final a = id.toNativeUtf8();
|
||||
_session_next_rgba!(a);
|
||||
malloc.free(a);
|
||||
}
|
||||
|
||||
/// Init the FFI class, loads the native Rust core library.
|
||||
Future<void> init(String appType) async {
|
||||
_appType = appType;
|
||||
@@ -107,6 +145,11 @@ class PlatformFFI {
|
||||
debugPrint('initializing FFI $_appType');
|
||||
try {
|
||||
_translate = dylib.lookupFunction<F2, F2>('translate');
|
||||
_session_get_rgba = dylib.lookupFunction<F3, F3>("session_get_rgba");
|
||||
_session_get_rgba_size =
|
||||
dylib.lookupFunction<F4, F4Dart>("session_get_rgba_size");
|
||||
_session_next_rgba =
|
||||
dylib.lookupFunction<F5, F5Dart>("session_next_rgba");
|
||||
try {
|
||||
// SYSTEM user failed
|
||||
_dir = (await getApplicationDocumentsDirectory()).path;
|
||||
@@ -118,8 +161,12 @@ class PlatformFFI {
|
||||
// Start a dbus service, no need to await
|
||||
_ffiBind.mainStartDbusServer();
|
||||
} else if (Platform.isMacOS && isMain) {
|
||||
// Start an ipc server for handling url schemes.
|
||||
_ffiBind.mainStartIpcUrlServer();
|
||||
Future.wait([
|
||||
// Start dbus service.
|
||||
_ffiBind.mainStartDbusServer(),
|
||||
// Start local audio pulseaudio server.
|
||||
_ffiBind.mainStartPa()
|
||||
]);
|
||||
}
|
||||
_startListenEvent(_ffiBind); // global event
|
||||
try {
|
||||
|
||||
@@ -560,10 +560,8 @@ class ServerModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
for (var client in _clients) {
|
||||
bind.cmCloseConnection(connId: client.id);
|
||||
}
|
||||
Future<void> closeAll() async {
|
||||
await Future.wait(_clients.map((client) => bind.cmCloseConnection(connId: client.id)));
|
||||
_clients.clear();
|
||||
tabController.state.value.tabs.clear();
|
||||
}
|
||||
@@ -579,6 +577,26 @@ class ServerModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void updateVoiceCallState(Map<String, dynamic> evt) {
|
||||
try {
|
||||
final client = Client.fromJson(jsonDecode(evt["client"]));
|
||||
final index = _clients.indexWhere((element) => element.id == client.id);
|
||||
if (index != -1) {
|
||||
_clients[index].inVoiceCall = client.inVoiceCall;
|
||||
_clients[index].incomingVoiceCall = client.incomingVoiceCall;
|
||||
if (client.incomingVoiceCall) {
|
||||
// Has incoming phone call, let's set the window on top.
|
||||
Future.delayed(Duration.zero, () {
|
||||
window_on_top(null);
|
||||
});
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("updateVoiceCallState failed: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ClientType {
|
||||
@@ -602,6 +620,8 @@ class Client {
|
||||
bool recording = false;
|
||||
bool disconnected = false;
|
||||
bool fromSwitch = false;
|
||||
bool inVoiceCall = false;
|
||||
bool incomingVoiceCall = false;
|
||||
|
||||
RxBool hasUnreadChatMessage = false.obs;
|
||||
|
||||
@@ -623,6 +643,8 @@ class Client {
|
||||
recording = json['recording'];
|
||||
disconnected = json['disconnected'];
|
||||
fromSwitch = json['from_switch'];
|
||||
inVoiceCall = json['in_voice_call'];
|
||||
incomingVoiceCall = json['incoming_voice_call'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
||||
@@ -14,6 +14,7 @@ class StateGlobal {
|
||||
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
|
||||
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
||||
final RxBool showRemoteMenuBar = false.obs;
|
||||
final RxInt displaysCount = 0.obs;
|
||||
|
||||
int get windowId => _windowId;
|
||||
bool get fullscreen => _fullscreen;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
Future<ui.Image> decodeImageFromPixels(
|
||||
Uint8List pixels,
|
||||
int width,
|
||||
@@ -9,6 +11,7 @@ Future<ui.Image> decodeImageFromPixels(
|
||||
int? rowBytes,
|
||||
int? targetWidth,
|
||||
int? targetHeight,
|
||||
VoidCallback? onPixelsCopied,
|
||||
bool allowUpscaling = true,
|
||||
}) async {
|
||||
if (targetWidth != null) {
|
||||
@@ -20,6 +23,7 @@ Future<ui.Image> decodeImageFromPixels(
|
||||
|
||||
final ui.ImmutableBuffer buffer =
|
||||
await ui.ImmutableBuffer.fromUint8List(pixels);
|
||||
onPixelsCopied?.call();
|
||||
final ui.ImageDescriptor descriptor = ui.ImageDescriptor.raw(
|
||||
buffer,
|
||||
width: width,
|
||||
@@ -47,3 +51,40 @@ Future<ui.Image> decodeImageFromPixels(
|
||||
descriptor.dispose();
|
||||
return frameInfo.image;
|
||||
}
|
||||
|
||||
class ImagePainter extends CustomPainter {
|
||||
ImagePainter({
|
||||
required this.image,
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.scale,
|
||||
});
|
||||
|
||||
ui.Image? image;
|
||||
double x;
|
||||
double y;
|
||||
double scale;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (image == null) return;
|
||||
if (x.isNaN || y.isNaN) return;
|
||||
canvas.scale(scale, scale);
|
||||
// https://github.com/flutter/flutter/issues/76187#issuecomment-784628161
|
||||
// https://api.flutter-io.cn/flutter/dart-ui/FilterQuality.html
|
||||
var paint = Paint();
|
||||
if ((scale - 1.0).abs() > 0.001) {
|
||||
paint.filterQuality = FilterQuality.medium;
|
||||
if (scale > 10.00000) {
|
||||
paint.filterQuality = FilterQuality.high;
|
||||
}
|
||||
}
|
||||
canvas.drawImage(
|
||||
image!, Offset(x.toInt().toDouble(), y.toInt().toDouble()), paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||
return oldDelegate != this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,11 +41,15 @@ class RustDeskMultiWindowManager {
|
||||
int? _fileTransferWindowId;
|
||||
int? _portForwardWindowId;
|
||||
|
||||
Future<dynamic> newRemoteDesktop(String remoteId,
|
||||
{String? switch_uuid}) async {
|
||||
Future<dynamic> newRemoteDesktop(
|
||||
String remoteId, {
|
||||
String? switch_uuid,
|
||||
bool? forceRelay,
|
||||
}) async {
|
||||
var params = {
|
||||
"type": WindowType.RemoteDesktop.index,
|
||||
"id": remoteId,
|
||||
"forceRelay": forceRelay
|
||||
};
|
||||
if (switch_uuid != null) {
|
||||
params['switch_uuid'] = switch_uuid;
|
||||
@@ -78,9 +82,12 @@ class RustDeskMultiWindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> newFileTransfer(String remoteId) async {
|
||||
final msg =
|
||||
jsonEncode({"type": WindowType.FileTransfer.index, "id": remoteId});
|
||||
Future<dynamic> newFileTransfer(String remoteId, {bool? forceRelay}) async {
|
||||
var msg = jsonEncode({
|
||||
"type": WindowType.FileTransfer.index,
|
||||
"id": remoteId,
|
||||
"forceRelay": forceRelay,
|
||||
});
|
||||
|
||||
try {
|
||||
final ids = await DesktopMultiWindow.getAllSubWindowIds();
|
||||
@@ -107,9 +114,14 @@ class RustDeskMultiWindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> newPortForward(String remoteId, bool isRDP) async {
|
||||
final msg = jsonEncode(
|
||||
{"type": WindowType.PortForward.index, "id": remoteId, "isRDP": isRDP});
|
||||
Future<dynamic> newPortForward(String remoteId, bool isRDP,
|
||||
{bool? forceRelay}) async {
|
||||
final msg = jsonEncode({
|
||||
"type": WindowType.PortForward.index,
|
||||
"id": remoteId,
|
||||
"isRDP": isRDP,
|
||||
"forceRelay": forceRelay,
|
||||
});
|
||||
|
||||
try {
|
||||
final ids = await DesktopMultiWindow.getAllSubWindowIds();
|
||||
|
||||
@@ -26,10 +26,6 @@
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
7E4BCD762966B0EC006D24E2 /* mac-tray-light.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E4BCD742966B0EC006D24E2 /* mac-tray-light.png */; };
|
||||
7E4BCD772966B0EC006D24E2 /* mac-tray-dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E4BCD752966B0EC006D24E2 /* mac-tray-dark.png */; };
|
||||
7E881462296E98EE00A0C54F /* mac-tray-light-x2.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E881461296E98ED00A0C54F /* mac-tray-light-x2.png */; };
|
||||
7E881464296E991200A0C54F /* mac-tray-dark-x2.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E881463296E991200A0C54F /* mac-tray-dark-x2.png */; };
|
||||
84010BA8292CF66600152837 /* liblibrustdesk.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 84010BA7292CF66600152837 /* liblibrustdesk.dylib */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
84010BA9292CF68300152837 /* liblibrustdesk.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 84010BA7292CF66600152837 /* liblibrustdesk.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
C5E54335B73C89F72DB1B606 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26C84465887F29AE938039CB /* Pods_Runner.framework */; };
|
||||
@@ -64,7 +60,7 @@
|
||||
295AD07E63F13855C270A0E0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* rustdesk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = rustdesk.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10ED2044A3C60003C045 /* RustDesk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RustDesk.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
@@ -78,10 +74,6 @@
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
7436B85D94E8F7B5A9324869 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
7E4BCD742966B0EC006D24E2 /* mac-tray-light.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "mac-tray-light.png"; path = "../../res/mac-tray-light.png"; sourceTree = "<group>"; };
|
||||
7E4BCD752966B0EC006D24E2 /* mac-tray-dark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "mac-tray-dark.png"; path = "../../res/mac-tray-dark.png"; sourceTree = "<group>"; };
|
||||
7E881461296E98ED00A0C54F /* mac-tray-light-x2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "mac-tray-light-x2.png"; path = "../../res/mac-tray-light-x2.png"; sourceTree = "<group>"; };
|
||||
7E881463296E991200A0C54F /* mac-tray-dark-x2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "mac-tray-dark-x2.png"; path = "../../res/mac-tray-dark-x2.png"; sourceTree = "<group>"; };
|
||||
84010BA7292CF66600152837 /* liblibrustdesk.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = liblibrustdesk.dylib; path = ../../target/release/liblibrustdesk.dylib; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
C3BB669FF6190AE1B11BCAEA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -127,7 +119,7 @@
|
||||
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10ED2044A3C60003C045 /* rustdesk.app */,
|
||||
33CC10ED2044A3C60003C045 /* RustDesk.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -135,10 +127,6 @@
|
||||
33CC11242044D66E0003C045 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7E881463296E991200A0C54F /* mac-tray-dark-x2.png */,
|
||||
7E881461296E98ED00A0C54F /* mac-tray-light-x2.png */,
|
||||
7E4BCD752966B0EC006D24E2 /* mac-tray-dark.png */,
|
||||
7E4BCD742966B0EC006D24E2 /* mac-tray-light.png */,
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */,
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */,
|
||||
33CC10F72044A3C60003C045 /* Info.plist */,
|
||||
@@ -212,7 +200,7 @@
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 33CC10ED2044A3C60003C045 /* rustdesk.app */;
|
||||
productReference = 33CC10ED2044A3C60003C045 /* RustDesk.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
@@ -265,12 +253,8 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7E881462296E98EE00A0C54F /* mac-tray-light-x2.png in Resources */,
|
||||
7E4BCD762966B0EC006D24E2 /* mac-tray-light.png in Resources */,
|
||||
7E4BCD772966B0EC006D24E2 /* mac-tray-dark.png in Resources */,
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||
7E881464296E991200A0C54F /* mac-tray-dark-x2.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -462,7 +446,6 @@
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
|
||||
PRODUCT_NAME = rustdesk;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -608,7 +591,6 @@
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
|
||||
PRODUCT_NAME = rustdesk;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;
|
||||
@@ -646,7 +628,6 @@
|
||||
/dev/null,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
|
||||
PRODUCT_NAME = rustdesk;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 978 B After Width: | Height: | Size: 448 B |