- Send multiple files in one message, with preview.

- Message preview manage huge heights.
- Chat design rework.
- Sort timelines by unread chat rooms.
- Fix thumbnails that weren't deleted.
- Play audio record on playback device instead of ringer device.
- Fix binding loops on scrollable areas.
- Change timeline filter to a minimal version.
- Fix record button hovering.
- Fix camera button in fullscreen.
This commit is contained in:
Julien Wadel 2022-03-08 11:10:48 +01:00
parent ac24d93c94
commit 879db5f496
66 changed files with 1360 additions and 479 deletions

View file

@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Features:
* Messages features : Reply, forward (to contact, to a SIP address or to a timeline), Vocal record and play, multi contents.
* Messages features : Reply, forward (to contact, to a SIP address or to a timeline), Vocal record and play, multi contents, preview.
- Add a feedback on fetching remote provisioning when it failed.
- Option to enable message notifications.
- CPIM on basic chat rooms.
@ -18,7 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Based on Linphone SDK 5.1
### Fixed
- Simplify filtering timelines on 3 kind of search : security level, simple/group chats, ephemerals.
- Simplify filtering timelines with 2 modes (minimal or exhaustive) and on 3 kind of search : security level, simple/group chats, ephemerals.
- Sort timelines by taken account of unread events in chat rooms.
- Fix systemTrayIcon that could be cloned on each restart.
- Fix errors on Action-Buttons on restart.
- Enable G729 on public builds.
@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Adapt UserAgent with device name.
- Video freeze on network change.
- Set default log size to 50MB
- Crash on the smart search bar.
## 4.3.2

View file

@ -128,6 +128,7 @@ set(SOURCES
src/components/calls/CallsListProxyModel.cpp
src/components/camera/Camera.cpp
src/components/camera/CameraPreview.cpp
src/components/chat/ChatModel.cpp
src/components/chat-events/ChatCallModel.cpp
src/components/chat-events/ChatEvent.cpp
src/components/chat-events/ChatMessageModel.cpp
@ -238,6 +239,7 @@ set(HEADERS
src/components/calls/CallsListProxyModel.hpp
src/components/camera/Camera.hpp
src/components/camera/CameraPreview.hpp
src/components/chat/ChatModel.hpp
src/components/chat-events/ChatCallModel.hpp
src/components/chat-events/ChatEvent.hpp
src/components/chat-events/ChatMessageModel.hpp

View file

@ -5,8 +5,8 @@
viewBox="0 0 80 80"
version="1.1"
id="svg8"
sodipodi:docname="chat_audio_pause.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
sodipodi:docname="chat_audio_pause_custom.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
@ -22,26 +22,17 @@
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="5.3231707"
inkscape:cx="28.460481"
inkscape:cy="64.623139"
inkscape:zoom="7.5281002"
inkscape:cx="28.493245"
inkscape:cy="43.304418"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<g
fill="none"
fill-rule="evenodd"
id="g6"
transform="scale(0.96277665,0.97560976)">
<g
fill="#444444"
id="g4">
<path
d="m 41.547,0 c 22.945,0 41.546,18.356 41.546,41 0,22.644 -18.6,41 -41.546,41 C 18.6,82 0,63.644 0,41 0,18.356 18.601,0 41.547,0 Z M 30.04,21.5 c -1.933,0 -3.5,1.567 -3.5,3.5 v 32 l 0.005,0.192 c 0.1,1.844 1.626,3.308 3.495,3.308 1.933,0 3.5,-1.567 3.5,-3.5 V 25 L 33.535,24.808 C 33.435,22.964 31.909,21.5 30.04,21.5 Z m 21,0 c -1.933,0 -3.5,1.567 -3.5,3.5 v 32 l 0.005,0.192 c 0.1,1.844 1.626,3.308 3.495,3.308 1.933,0 3.5,-1.567 3.5,-3.5 V 25 L 54.535,24.808 C 54.435,22.964 52.909,21.5 51.04,21.5 Z"
id="path2" />
</g>
</g>
<path
d="m 28.078632,24.714815 v 30.46462 M 49.08815,24.714815 v 30.46462"
id="path823"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:8.94461;stroke-linecap:round;stroke-linejoin:round" />
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -4,44 +4,51 @@
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg8"
sodipodi:docname="chat_audio_play.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
id="svg14"
sodipodi:docname="chat_audio_play_custom.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
id="defs18" />
<sodipodi:namedview
id="namedview10"
id="namedview16"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:pagecheckerboard="true"
showgrid="false"
inkscape:zoom="5.3231707"
inkscape:cx="29.024055"
inkscape:cy="63.683849"
inkscape:zoom="6.5078125"
inkscape:cx="7.7599039"
inkscape:cy="15.827131"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
inkscape:current-layer="g8" />
<g
fill="none"
fill-rule="evenodd"
id="g6"
transform="scale(0.96277665,0.97560976)">
id="g12">
<g
fill="#444444"
id="g4">
<path
d="m 41.547,0 c 22.945,0 41.546,18.356 41.546,41 0,22.644 -18.6,41 -41.546,41 C 18.6,82 0,63.644 0,41 0,18.356 18.601,0 41.547,0 Z M 29.975,20.97 V 59.812 L 61.75,41.12 Z"
id="path2" />
id="g10">
<g
id="g8">
<g
transform="scale(4.9340074,5)"
id="g6">
<path
fill="#000000"
fill-rule="nonzero"
d="m 5.849,4.092 v 7.579 l 6.2,-3.648 z"
id="path4" />
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg8"
sodipodi:docname="chat_audio_pause.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="5.3231707"
inkscape:cx="28.460481"
inkscape:cy="64.623139"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<g
fill="none"
fill-rule="evenodd"
id="g6"
transform="scale(0.96277665,0.97560976)">
<g
fill="#444444"
id="g4">
<path
d="m 41.547,0 c 22.945,0 41.546,18.356 41.546,41 0,22.644 -18.6,41 -41.546,41 C 18.6,82 0,63.644 0,41 0,18.356 18.601,0 41.547,0 Z M 30.04,21.5 c -1.933,0 -3.5,1.567 -3.5,3.5 v 32 l 0.005,0.192 c 0.1,1.844 1.626,3.308 3.495,3.308 1.933,0 3.5,-1.567 3.5,-3.5 V 25 L 33.535,24.808 C 33.435,22.964 31.909,21.5 30.04,21.5 Z m 21,0 c -1.933,0 -3.5,1.567 -3.5,3.5 v 32 l 0.005,0.192 c 0.1,1.844 1.626,3.308 3.495,3.308 1.933,0 3.5,-1.567 3.5,-3.5 V 25 L 54.535,24.808 C 54.435,22.964 52.909,21.5 51.04,21.5 Z"
id="path2" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg8"
sodipodi:docname="chat_audio_play.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="5.3231707"
inkscape:cx="29.024055"
inkscape:cy="63.683849"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<g
fill="none"
fill-rule="evenodd"
id="g6"
transform="scale(0.96277665,0.97560976)">
<g
fill="#444444"
id="g4">
<path
d="m 41.547,0 c 22.945,0 41.546,18.356 41.546,41 0,22.644 -18.6,41 -41.546,41 C 18.6,82 0,63.644 0,41 0,18.356 18.601,0 41.547,0 Z M 29.975,20.97 V 59.812 L 61.75,41.12 Z"
id="path2" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="500"
width="60"
height="60"
viewBox="0 0 500 60"
viewBox="0 0 60 60"
version="1.1"
id="svg8"
sodipodi:docname="chat_audio_soundwave_custom.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
@ -22,27 +22,29 @@
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.2704082"
inkscape:cx="250.70682"
inkscape:cy="-59.036143"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
inkscape:zoom="2.5408164"
inkscape:cx="-1.37751"
inkscape:cy="35.421686"
inkscape:window-width="1458"
inkscape:window-height="749"
inkscape:window-x="146"
inkscape:window-y="99"
inkscape:window-maximized="0"
inkscape:current-layer="svg8"
showguides="false" />
<g
fill="none"
fill-rule="evenodd"
id="g6"
transform="matrix(1.0204082,0,0,0.66666667,0,10)">
transform="matrix(0.5,0,0,0.66666667,0,10)">
<g
fill="#444444"
fill-rule="nonzero"
id="g4">
<path
d="m 65,0 c 2.761,0 5,2.239 5,5 v 50 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 5 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 50 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 5 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 50 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 5 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 50 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 5 c 0,-2.761 2.239,-5 5,-5 z M 85,5 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z M 45,5 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z M 25,15 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m -280,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z M 5,25 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z"
id="path2" />
id="path2"
d="m 65,0 c 2.761,0 5,2.239 5,5 v 50 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 5 c 0,-2.761 2.239,-5 5,-5 z m 20,5 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 C 80,7.239 82.239,5 85,5 Z M 45,5 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 C 40,7.239 42.239,5 45,5 Z M 25,15 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 80,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z M 5,25 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z m 115,5 C 40,5.0000002e-8 80,15 120,30 Z"
sodipodi:nodetypes="sssssssssssssssssssssssssssssssssssssssscc" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -2,18 +2,19 @@
<svg
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg4"
id="svg12"
sodipodi:docname="menu_copy_text_custom.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
id="defs16" />
<sodipodi:namedview
id="namedview6"
id="namedview14"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
@ -21,19 +22,33 @@
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="4.2868349"
inkscape:cx="15.512611"
inkscape:cy="30.791949"
inkscape:zoom="6.4191176"
inkscape:cx="21.887744"
inkscape:cy="29.599084"
inkscape:window-width="1920"
inkscape:window-height="1131"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M 44.50799,60 H 28.995997 C 25.416665,60 22.5,57.195556 22.5,53.748889 V 32.577778 c 0,-3.444445 2.914332,-6.248889 6.495997,-6.248889 H 44.50799 c 3.581666,0 6.495998,2.802222 6.495998,6.248889 V 53.748889 C 51.003988,57.195556 48.087322,60 44.50799,60 Z M 28.995997,29.453333 a 3.2666653,3.1111111 0 0 0 -3.247998,3.124445 v 21.171111 a 3.2666653,3.1111111 0 0 0 3.247998,3.126667 H 44.50799 a 3.2666653,3.1111111 0 0 0 3.247999,-3.126667 V 32.577778 A 3.2666653,3.1111111 0 0 0 44.50799,29.453333 Z M 57.499985,49.844444 V 26.251111 C 57.499985,22.804444 54.585653,20 51.003988,20 H 32.976662 a 1.6333326,1.5555556 0 0 0 -1.623999,1.562222 1.6333326,1.5555556 0 0 0 1.623999,1.562222 h 18.027326 a 3.2666653,3.1111111 0 0 1 3.247998,3.126667 v 23.593333 a 1.6333326,1.5555556 0 0 0 1.624,1.562223 1.6333326,1.5555556 0 0 0 1.623999,-1.562223 z"
fill="#000000"
fill-rule="nonzero"
id="path2"
style="stroke-width:2.2771" />
inkscape:current-layer="svg12" />
<g
fill="none"
fill-rule="evenodd"
id="g10"
transform="matrix(2.8947749,0,0,2.9411765,20,15)">
<g
fill="#000000"
fill-rule="nonzero"
id="g8">
<g
id="g6">
<g
id="g4">
<path
d="m 8.446,17 c 1.399,0 2.095,-0.716 2.095,-2.122 v -1.25 h 1.182 c 1.392,0 2.095,-0.716 2.095,-2.121 V 6.02 c 0,-0.83 -0.17,-1.358 -0.676,-1.878 L 9.73,0.676 C 9.25,0.182 8.682,0 7.96,0 H 5.371 C 3.98,0 3.277,0.716 3.277,2.122 v 1.25 H 2.095 C 0.703,3.372 0,4.082 0,5.493 v 9.385 C 0,16.291 0.696,17 2.095,17 h 6.35 z m 3.216,-4.46 H 10.541 V 9.602 c 0,-0.865 -0.102,-1.243 -0.642,-1.797 L 6.176,4.015 C 5.662,3.487 5.236,3.373 4.48,3.373 H 4.365 v -1.23 c 0,-0.669 0.358,-1.054 1.06,-1.054 h 2.987 v 3.466 c 0,0.784 0.379,1.155 1.156,1.155 h 3.162 v 5.777 c 0,0.676 -0.365,1.055 -1.068,1.055 z M 12.446,4.73 H 9.723 C 9.486,4.73 9.392,4.628 9.392,4.392 V 1.622 Z M 8.392,15.912 h -6.25 c -0.696,0 -1.054,-0.378 -1.054,-1.054 V 5.507 c 0,-0.662 0.358,-1.048 1.06,-1.048 h 2.217 v 3.92 c 0,0.85 0.432,1.276 1.27,1.276 h 3.818 v 5.203 c 0,0.676 -0.365,1.054 -1.061,1.054 z M 9.243,8.635 H 5.763 C 5.493,8.635 5.385,8.527 5.385,8.257 V 4.709 Z"
id="path2" />
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -6,7 +6,7 @@
version="1.1"
id="svg10"
sodipodi:docname="send_custom.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
@ -26,8 +26,8 @@
inkscape:cx="9.408651"
inkscape:cy="46.187923"
inkscape:window-width="1920"
inkscape:window-height="1131"
inkscape:window-x="0"
inkscape:window-height="1043"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg10" />
@ -35,7 +35,7 @@
fill="none"
fill-rule="evenodd"
id="g8"
transform="matrix(2.0833639,0,0,2.083344,15.000137,14.999696)">
transform="matrix(2.5000367,0,0,2.5000128,10.000164,9.9996351)">
<g
fill="#444444"
fill-rule="nonzero"

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -2634,6 +2634,16 @@ Click here: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Mipmap filtering gives better visual quality when scaling down compared to smooth, but it may come at a performance cost (both when initializing the image and during rendering).&apos; : Second line of a tooltip about Mipmap mode.</extracomment>
<translation>Mipmap filtering gives better visual quality when scaling down compared to smooth, but it may come at a performance cost (both when initializing the image and during rendering).</translation>
</message>
<message>
<source>minimalTimelineFilterLabel</source>
<extracomment>&apos;Minimal Timeline filter&apos;</extracomment>
<translation>Minimal Timeline filter</translation>
</message>
<message>
<source>minimalTimelineFilterTooltip</source>
<extracomment>&apos;Show a minimal version of what to display in timeline.&apos; :</extracomment>
<translation>Show a minimal version of what to display in timeline.</translation>
</message>
</context>
<context>
<name>SettingsVideo</name>
@ -2833,6 +2843,11 @@ Click here: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;No Ephemerals&apos; : Filter item. Selecting it will hide all chat rooms where the ephemeral mode has been enabled.</extracomment>
<translation>Without ephemerals</translation>
</message>
<message>
<source>timelineFilterConferences</source>
<extracomment>&apos;Conferences&apos; : Filter item. Selecting it will show all conferences.</extracomment>
<translation>Conferences</translation>
</message>
</context>
<context>
<name>UseAppSipAccount</name>

View file

@ -33,7 +33,9 @@
<file>assets/images/chat_audio_pause_custom.svg</file>
<file>assets/images/chat_audio_play_custom.svg</file>
<file>assets/images/chat_audio_soundwave_custom.svg</file>
<file>assets/images/chat_audio_stop_custom.svg</file>
<file>assets/images/chat_audio_preview_pause_custom.svg</file>
<file>assets/images/chat_audio_preview_play_custom.svg</file>
<file>assets/images/chat_audio_preview_stop_custom.svg</file>
<file>assets/images/chat_count.svg</file>
<file>assets/images/chat_delivered.svg</file>
<file>assets/images/chat_error.svg</file>
@ -281,6 +283,7 @@
<file>ui/modules/Linphone/Chat/ChatAudioMessage.qml</file>
<file>ui/modules/Linphone/Chat/ChatAudioPreview.qml</file>
<file>ui/modules/Linphone/Chat/ChatFileMessage.qml</file>
<file>ui/modules/Linphone/Chat/ChatFilePreview.qml</file>
<file>ui/modules/Linphone/Chat/ChatForwardMessage.qml</file>
<file>ui/modules/Linphone/Chat/ChatMessagePreview.qml</file>
<file>ui/modules/Linphone/Chat/ChatReplyMessage.qml</file>
@ -301,6 +304,7 @@
<file>ui/modules/Linphone/Contact/Contact.qml</file>
<file>ui/modules/Linphone/Dialog/OnlineInstallerDialog.qml</file>
<file>ui/modules/Linphone/Dialog/SipAddressDialog.qml</file>
<file>ui/modules/Linphone/File/FileView.qml</file>
<file>ui/modules/Linphone/History/History.qml</file>
<file>ui/modules/Linphone/History/History.js</file>
<file>ui/modules/Linphone/History/Event.qml</file>
@ -327,6 +331,7 @@
<file>ui/modules/Linphone/Styles/Chat/ChatStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatAudioMessageStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatAudioPreviewStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatFilePreviewStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatForwardMessageStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatReplyMessageStyle.qml</file>
<file>ui/modules/Linphone/Styles/Codecs/CodecsViewerStyle.qml</file>

View file

@ -120,33 +120,36 @@ QString ChatMessageModel::AppDataManager::toString(){
}
ChatMessageModel::ChatMessageModel ( std::shared_ptr<linphone::ChatMessage> chatMessage, QObject * parent) : ChatEvent(ChatRoomModel::EntryType::MessageEntry, parent) {
App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it
mParticipantImdnStateListModel = std::make_shared<ParticipantImdnStateListModel>(chatMessage);
mChatMessageListener = std::make_shared<ChatMessageListener>(this, parent);
mChatMessage = chatMessage;
if(chatMessage){
mParticipantImdnStateListModel = std::make_shared<ParticipantImdnStateListModel>(chatMessage);
mChatMessageListener = std::make_shared<ChatMessageListener>(this, parent);
mChatMessage = chatMessage;
mChatMessage->addListener(mChatMessageListener);
if( mChatMessage->isReply()){
auto replyMessage = mChatMessage->getReplyMessage();
if( replyMessage)// Reply message could be inexistant (for example : when locally deleted)
mReplyChatMessageModel = create(replyMessage, parent);
}
connect(this, &ChatMessageModel::remove, dynamic_cast<ChatRoomModel*>(parent), &ChatRoomModel::removeEntry);
std::list<std::shared_ptr<linphone::Content>> contents = chatMessage->getContents();
QString txt;
for(auto content : contents){
if(content->isText())
txt += content->getUtf8Text().c_str();
}
mContent = txt;
}
mWasDownloaded = false;
mChatMessage->addListener(mChatMessageListener);
mTimestamp = QDateTime::fromMSecsSinceEpoch(chatMessage->getTime() * 1000);
if( mChatMessage->isReply()){
auto replyMessage = mChatMessage->getReplyMessage();
if( replyMessage)// Reply message could be inexistant (for example : when locally deleted)
mReplyChatMessageModel = create(replyMessage, parent);
}
connect(this, &ChatMessageModel::remove, dynamic_cast<ChatRoomModel*>(parent), &ChatRoomModel::removeEntry);
std::list<std::shared_ptr<linphone::Content>> contents = chatMessage->getContents();
QString txt;
for(auto content : contents){
if(content->isText())
txt += content->getUtf8Text().c_str();
}
mContent = txt;
mContentListModel = std::make_shared<ContentListModel>(this);
}
ChatMessageModel::~ChatMessageModel(){
mChatMessage->removeListener(mChatMessageListener);
if(mChatMessage)
mChatMessage->removeListener(mChatMessageListener);
}
std::shared_ptr<ChatMessageModel> ChatMessageModel::create(std::shared_ptr<linphone::ChatMessage> chatMessage, QObject * parent){
auto model = std::make_shared<ChatMessageModel>(chatMessage, parent);
@ -163,7 +166,7 @@ std::shared_ptr<ContentModel> ChatMessageModel::getContentModel(std::shared_ptr<
//-----------------------------------------------------------------------------------------------------------------------
QString ChatMessageModel::getFromDisplayName() const{
return Utils::getDisplayName(mChatMessage->getFromAddress());
return mChatMessage ? Utils::getDisplayName(mChatMessage->getFromAddress()) : "";
}
QString ChatMessageModel::getFromDisplayNameReplyMessage() const{
@ -174,40 +177,40 @@ QString ChatMessageModel::getFromDisplayNameReplyMessage() const{
}
QString ChatMessageModel::getFromSipAddress() const{
return Utils::cleanSipAddress(Utils::coreStringToAppString(mChatMessage->getFromAddress()->asStringUriOnly()));
return mChatMessage ? Utils::cleanSipAddress(Utils::coreStringToAppString(mChatMessage->getFromAddress()->asStringUriOnly())) : "";
}
QString ChatMessageModel::getToDisplayName() const{
return Utils::getDisplayName(mChatMessage->getToAddress());
return mChatMessage ? Utils::getDisplayName(mChatMessage->getToAddress()) : "";
}
QString ChatMessageModel::getToSipAddress() const{
return Utils::cleanSipAddress(Utils::coreStringToAppString(mChatMessage->getToAddress()->asStringUriOnly()));
return mChatMessage ? Utils::cleanSipAddress(Utils::coreStringToAppString(mChatMessage->getToAddress()->asStringUriOnly())) : "";
}
ContactModel * ChatMessageModel::getContactModel() const{
return CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(Utils::coreStringToAppString(mChatMessage->getFromAddress()->asString()));
return mChatMessage ? CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(Utils::coreStringToAppString(mChatMessage->getFromAddress()->asString())) : nullptr;
}
bool ChatMessageModel::isEphemeral() const{
return mChatMessage->isEphemeral();
return mChatMessage && mChatMessage->isEphemeral();
}
qint64 ChatMessageModel::getEphemeralExpireTime() const{
time_t t = mChatMessage->getEphemeralExpireTime();
time_t t = mChatMessage ? mChatMessage->getEphemeralExpireTime() : 0;
return t >0 ? t - QDateTime::currentSecsSinceEpoch() : 0;
}
long ChatMessageModel::getEphemeralLifetime() const{
return mChatMessage->getEphemeralLifetime();
return mChatMessage ? mChatMessage->getEphemeralLifetime() : 0;
}
LinphoneEnums::ChatMessageState ChatMessageModel::getState() const{
return LinphoneEnums::fromLinphone(mChatMessage->getState());
return mChatMessage ? LinphoneEnums::fromLinphone(mChatMessage->getState()) : LinphoneEnums::ChatMessageStateIdle;
}
bool ChatMessageModel::isOutgoing() const{
return mChatMessage->isOutgoing();
return mChatMessage && mChatMessage->isOutgoing();
}
ParticipantImdnStateProxyModel * ChatMessageModel::getProxyImdnStates(){
@ -229,7 +232,7 @@ std::shared_ptr<ContentListModel> ChatMessageModel::getContents() const{
}
bool ChatMessageModel::isReply() const{
return mChatMessage->isReply();
return mChatMessage && mChatMessage->isReply();
}
ChatMessageModel * ChatMessageModel::getReplyChatMessageModel() const{
@ -237,11 +240,11 @@ ChatMessageModel * ChatMessageModel::getReplyChatMessageModel() const{
}
bool ChatMessageModel::isForward() const{
return mChatMessage->isForward();
return mChatMessage && mChatMessage->isForward();
}
QString ChatMessageModel::getForwardInfo() const{
return Utils::coreStringToAppString(mChatMessage->getForwardInfo());
return mChatMessage ? Utils::coreStringToAppString(mChatMessage->getForwardInfo()) : "";
}
QString ChatMessageModel::getForwardInfoDisplayName() const{
@ -279,7 +282,6 @@ void ChatMessageModel::resendMessage (){
}
}
void ChatMessageModel::deleteEvent(){
if (mChatMessage && mChatMessage->getFileTransferInformation()) {// Remove thumbnail
mChatMessage->cancelFileTransfer();
@ -293,7 +295,8 @@ void ChatMessageModel::deleteEvent(){
}
mChatMessage->setAppdata("");// Remove completely Thumbnail from the message
}
mChatMessage->getChatRoom()->deleteMessage(mChatMessage);
if(mChatMessage)
mChatMessage->getChatRoom()->deleteMessage(mChatMessage);
}
void ChatMessageModel::updateFileTransferInformation(){
mContentListModel->updateContents(this);
@ -317,7 +320,7 @@ void ChatMessageModel::onFileTransferProgressIndication (const std::shared_ptr<l
void ChatMessageModel::onMsgStateChanged (const std::shared_ptr<linphone::ChatMessage> &message, linphone::ChatMessage::State state) {
updateFileTransferInformation();// On message state, file transfert information Content can be changed
// File message downloaded.
if (state == linphone::ChatMessage::State::FileTransferDone && !mChatMessage->isOutgoing()) {
if (mChatMessage && state == linphone::ChatMessage::State::FileTransferDone && !mChatMessage->isOutgoing()) {
mContentListModel->downloaded();
setWasDownloaded(true);
App::getInstance()->getNotifier()->notifyReceivedFileMessage(message);

View file

@ -38,6 +38,7 @@
#include "app/paths/Paths.hpp"
#include "app/providers/ThumbnailProvider.hpp"
#include "components/calls/CallsListModel.hpp"
#include "components/chat/ChatModel.hpp"
#include "components/chat-events/ChatCallModel.hpp"
#include "components/chat-events/ChatEvent.hpp"
#include "components/chat-events/ChatMessageModel.hpp"
@ -45,6 +46,8 @@
#include "components/contact/ContactModel.hpp"
#include "components/contact/VcardModel.hpp"
#include "components/contacts/ContactsListModel.hpp"
#include "components/content/ContentListModel.hpp"
#include "components/content/ContentModel.hpp"
#include "components/core/CoreHandlers.hpp"
#include "components/core/CoreManager.hpp"
#include "components/notifier/Notifier.hpp"
@ -567,6 +570,10 @@ QString ChatRoomModel::getParticipantAddress(){
}
}
int ChatRoomModel::getAllUnreadCount(){
return mUnreadMessagesCount + mMissedCallsCount;
}
//------------------------------------------------------------------------------------------------
void ChatRoomModel::setSubject(QString& subject){
@ -654,7 +661,6 @@ ChatMessageModel * ChatRoomModel::getReply()const{
return mReplyModel.get();
}
//------------------------------------------------------------------------------------------------
void ChatRoomModel::deleteChatRoom(){
@ -694,63 +700,33 @@ void ChatRoomModel::updateParticipants(const QVariantList& participants){
void ChatRoomModel::sendMessage (const QString &message) {
shared_ptr<linphone::ChatMessage> _message;
if(mReplyModel && mReplyModel->getChatMessage())
if(mReplyModel && mReplyModel->getChatMessage()) {
_message = mChatRoom->createReplyMessage(mReplyModel->getChatMessage());
else
}else
_message= mChatRoom->createEmptyMessage();
auto recorder = CoreManager::getInstance()->getRecorderManager();
if(recorder->haveVocalRecorder()) {
recorder->getVocalRecorder()->stop();
auto content = recorder->getVocalRecorder()->getRecorder()->createContent();
if(content)
if(content) {
_message->addContent(content);
}
if(!message.isEmpty())
_message->addUtf8TextContent(message.toUtf8().toStdString());
_message->send();
emit messageSent(_message);
setReply(nullptr);
if(recorder->haveVocalRecorder())
recorder->clearVocalRecorder();
}
void ChatRoomModel::sendFileMessage (const QString &path) {
QFile file(path);
if (!file.exists())
return;
qint64 fileSize = file.size();
if (fileSize > Constants::FileSizeLimit) {
qWarning() << QStringLiteral("Unable to send file. (Size limit=%1)").arg(Constants::FileSizeLimit);
return;
}
shared_ptr<linphone::Content> content = CoreManager::getInstance()->getCore()->createContent();
{
QStringList mimeType = QMimeDatabase().mimeTypeForFile(path).name().split('/');
if (mimeType.length() != 2) {
qWarning() << QStringLiteral("Unable to get supported mime type for: `%1`.").arg(path);
return;
}
content->setType(Utils::appStringToCoreString(mimeType[0]));
content->setSubtype(Utils::appStringToCoreString(mimeType[1]));
}
content->setSize(size_t(fileSize));
content->setName(QFileInfo(file).fileName().toStdString());
shared_ptr<linphone::ChatMessage> message = mChatRoom->createFileTransferMessage(content);
message->getContents().front()->setFilePath(Utils::appStringToCoreString(path));
auto recorder = CoreManager::getInstance()->getRecorderManager();
if(recorder->haveVocalRecorder()) {
auto content = recorder->getVocalRecorder()->getRecorder()->createContent();
if(content)
message->addContent(content);
auto fileContents = CoreManager::getInstance()->getChatModel()->getContentListModel()->getContents();
for(auto content : fileContents){
_message->addFileContent(content->getContent());
}
if(!message.isEmpty()) {
_message->addUtf8TextContent(message.toUtf8().toStdString());
}
if(_message->getContents().size() > 0){// Have something to send
_message->send();
emit messageSent(_message);
setReply(nullptr);
if(recorder->haveVocalRecorder())
recorder->clearVocalRecorder();
CoreManager::getInstance()->getChatModel()->clear();
}
message->send();
emit messageSent(message);
}
void ChatRoomModel::forwardMessage(ChatMessageModel * model){

View file

@ -154,8 +154,6 @@ public:
Q_PROPERTY(bool entriesLoading READ isEntriesLoading WRITE setEntriesLoading NOTIFY entriesLoadingChanged)
//ChatRoomModel (const QString &peerAddress, const QString &localAddress, const bool& isSecure);
static std::shared_ptr<ChatRoomModel> create(std::shared_ptr<linphone::ChatRoom> chatRoom);
ChatRoomModel (std::shared_ptr<linphone::ChatRoom> chatRoom, QObject * parent = nullptr);
~ChatRoomModel ();
@ -202,6 +200,7 @@ public:
std::shared_ptr<linphone::ChatRoom> getChatRoom();
QList<QString> getComposers();
QString getParticipantAddress(); // return peerAddress if not secure else return the first participant SIP address.
int getAllUnreadCount(); // Return unread messages and missed call.
//---- Setters
void setSubject(QString& subject);
@ -219,6 +218,8 @@ public:
void setReply(ChatMessageModel * model);
ChatMessageModel * getReply()const;
void clearReply();
void clearFilesToSend();
// Tools
@ -226,7 +227,6 @@ public:
Q_INVOKABLE void leaveChatRoom ();
Q_INVOKABLE void updateParticipants(const QVariantList& participants);
void sendMessage (const QString &message);
void sendFileMessage (const QString &path);
Q_INVOKABLE void forwardMessage(ChatMessageModel * model);
void compose ();
Q_INVOKABLE void resetMessageCount ();

View file

@ -82,7 +82,6 @@ ChatRoomProxyModel::ChatRoomProxyModel (QObject *parent) : QSortFilterProxyModel
CREATE_PARENT_MODEL_FUNCTION(removeAllEntries)
CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(sendFileMessage, const QString &)
CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(sendMessage, const QString &)
CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(forwardMessage, ChatMessageModel *)

View file

@ -67,8 +67,6 @@ public:
Q_INVOKABLE void sendMessage (const QString &message);
Q_INVOKABLE void sendFileMessage (const QString &path);
Q_INVOKABLE void forwardMessage(ChatMessageModel * model);
Q_INVOKABLE void compose (const QString& text);

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2022 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ChatModel.hpp"
#include <QQmlApplicationEngine>
#include <QDesktopServices>
#include <QImageReader>
#include <QMessageBox>
#include "app/App.hpp"
#include "app/paths/Paths.hpp"
#include "app/providers/ThumbnailProvider.hpp"
#include "components/chat-events/ChatMessageModel.hpp"
#include "utils/QExifImageHeader.hpp"
#include "utils/Utils.hpp"
#include "utils/Constants.hpp"
#include "components/Components.hpp"
// =============================================================================
ChatModel::ChatModel(QObject * parent ) : QObject(parent){
App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
mContents = std::make_shared<ContentListModel>(nullptr);
}
std::shared_ptr<ContentListModel> ChatModel::getContentListModel() const {
return mContents;
}
void ChatModel::clear() {
mContents->clear();
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2022 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Used to store data between chats
#ifndef CHAT_MODEL_H_
#define CHAT_MODEL_H_
#include <linphone++/linphone.hh>
// =============================================================================
#include <QObject>
#include <QDateTime>
#include <QString>
class ContentListModel;
class ChatModel : public QObject{
Q_OBJECT
public:
ChatModel(QObject * parent = nullptr);
// Getters
std::shared_ptr<ContentListModel> getContentListModel() const;
// Tools
Q_INVOKABLE void clear();
private:
std::shared_ptr<ContentListModel> mContents;
};
#endif

View file

@ -24,6 +24,7 @@
#include "ContentListModel.hpp"
#include "ContentModel.hpp"
#include "utils/Constants.hpp"
#include "utils/Utils.hpp"
#include "components/Components.hpp"
@ -31,11 +32,14 @@
// =============================================================================
ContentListModel::ContentListModel (ChatMessageModel * message) : QAbstractListModel(message) {
std::list<std::shared_ptr<linphone::Content>> contents = message->getChatMessage()->getContents() ;
for(auto content : contents){
auto contentModel = std::make_shared<ContentModel>(content, message);
connect(this, &ContentListModel::updateTransferDataRequested, contentModel.get(), &ContentModel::updateTransferData);
mList << contentModel;
mParent = message;
if(message){
std::list<std::shared_ptr<linphone::Content>> contents = message->getChatMessage()->getContents() ;
for(auto content : contents){
auto contentModel = std::make_shared<ContentModel>(content, message);
connect(this, &ContentListModel::updateTransferDataRequested, contentModel.get(), &ContentModel::updateTransferData);
mList << contentModel;
}
}
}
@ -65,6 +69,57 @@ QVariant ContentListModel::data (const QModelIndex &index, int role) const {
return QVariant();
}
std::shared_ptr<ContentModel> ContentListModel::add(std::shared_ptr<linphone::Content> content){
int row = mList.count();
auto contentModel = std::make_shared<ContentModel>(content, mParent);
beginInsertRows(QModelIndex(), row, row);
mList << contentModel;
endInsertRows();
emit contentsChanged();
return contentModel;
}
void ContentListModel::addFile(const QString& path){
QFile file(path);
if (!file.exists())
return;
qint64 fileSize = file.size();
if (fileSize > Constants::FileSizeLimit) {
qWarning() << QStringLiteral("Unable to send file. (Size limit=%1)").arg(Constants::FileSizeLimit);
return;
}
std::shared_ptr<linphone::Content> content = CoreManager::getInstance()->getCore()->createContent();
{
QStringList mimeType = QMimeDatabase().mimeTypeForFile(path).name().split('/');
if (mimeType.length() != 2) {
qWarning() << QStringLiteral("Unable to get supported mime type for: `%1`.").arg(path);
return;
}
content->setType(Utils::appStringToCoreString(mimeType[0]));
content->setSubtype(Utils::appStringToCoreString(mimeType[1]));
}
content->setSize(size_t(fileSize));
content->setName(QFileInfo(file).fileName().toStdString());
content->setFilePath(Utils::appStringToCoreString(path));
auto modelAdded = add(content);
if(!content->isFile())
modelAdded->createThumbnail(true); // Was not created because linphone::Content is not considered as a file (yet)
}
void ContentListModel::remove(ContentModel * model){
int count = 0;
for(auto it = mList.begin() ; it != mList.end() ; ++count, ++it) {
if( it->get() == model) {
model->removeThumbnail();
removeRow(count, QModelIndex());
return;
}
}
}
bool ContentListModel::removeRow (int row, const QModelIndex &parent){
return removeRows(row, 1, parent);
}
@ -84,6 +139,17 @@ bool ContentListModel::removeRows (int row, int count, const QModelIndex &parent
return true;
}
void ContentListModel::clear(){
// Delete thumbnails
for(auto contentModel : mList){
contentModel->removeThumbnail();
}
beginResetModel();
mList.clear();
endResetModel();
}
std::shared_ptr<ContentModel> ContentListModel::getContentModel(std::shared_ptr<linphone::Content> content){
for(auto c : mList)
if(c->getContent() == content)
@ -95,6 +161,11 @@ std::shared_ptr<ContentModel> ContentListModel::getContentModel(std::shared_ptr<
}
return nullptr;
}
QList<std::shared_ptr<ContentModel>> ContentListModel::getContents(){
return mList;
}
void ContentListModel::updateContent(std::shared_ptr<linphone::Content> oldContent, std::shared_ptr<linphone::Content> newContent){
int row = 0;
for(auto content = mList.begin() ; content != mList.end() ; ++content, ++row){

View file

@ -43,8 +43,15 @@ public:
virtual QHash<int, QByteArray> roleNames () const override;
virtual QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override;
std::shared_ptr<ContentModel> add(std::shared_ptr<linphone::Content> content);
void addFile(const QString& path);
Q_INVOKABLE void remove(ContentModel * model);
void clear();
std::shared_ptr<ContentModel> getContentModel(std::shared_ptr<linphone::Content> content);// Return the contentModel by checking Content, or if it is the same file.
QList<std::shared_ptr<ContentModel>> getContents();
void updateContent(std::shared_ptr<linphone::Content> oldContent, std::shared_ptr<linphone::Content> newContent);
void updateContents(ChatMessageModel * messageModel);
void updateAllTransferData();
@ -52,12 +59,14 @@ public:
signals:
void updateTransferDataRequested();
void contentsChanged();
private:
bool removeRow (int row, const QModelIndex &parent = QModelIndex());
virtual bool removeRows (int row, int count, const QModelIndex &parent = QModelIndex()) override;
QList<std::shared_ptr<ContentModel>> mList;
ChatMessageModel * mParent;
};

View file

@ -38,13 +38,13 @@
// =============================================================================
ContentModel::ContentModel(ChatMessageModel* chatModel){
ContentModel::ContentModel(ChatMessageModel* chatModel) : mAppData(""){
App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
mChatMessageModel = chatModel;
mWasDownloaded = false;
mFileOffset = 0;
}
ContentModel::ContentModel(std::shared_ptr<linphone::Content> content, ChatMessageModel* chatModel){
ContentModel::ContentModel(std::shared_ptr<linphone::Content> content, ChatMessageModel* chatModel) : mAppData(""){
App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
mChatMessageModel = chatModel;
mWasDownloaded = false;
@ -131,12 +131,12 @@ bool ContentModel::isVoiceRecording()const{
}
// Create a thumbnail from the first content that have a file and store it in Appdata
void ContentModel::createThumbnail () {
if(isFile() || isFileEncrypted() || isFileTransfer()){
void ContentModel::createThumbnail (const bool& force) {
if(force || isFile() || isFileEncrypted() || isFileTransfer()){
QString id;
QString path = getFilePath();
auto appdata = ChatMessageModel::AppDataManager(QString::fromStdString(mChatMessageModel->getChatMessage()->getAppdata()));
auto appdata = ChatMessageModel::AppDataManager(mChatMessageModel ? QString::fromStdString(mChatMessageModel->getChatMessage()->getAppdata()) : "");
if(!appdata.mData.contains(path)
|| !QFileInfo(QString::fromStdString(Paths::getThumbnailsDirPath())+appdata.mData[path]).isFile()){
@ -179,7 +179,9 @@ void ContentModel::createThumbnail () {
qWarning() << QStringLiteral("Unable to create thumbnail of: `%1`.").arg(path);
}else{
appdata.mData[path] = id;
mChatMessageModel->getChatMessage()->setAppdata(appdata.toString().toStdString());
mAppData.mData[path] = id;
if(mChatMessageModel)
mChatMessageModel->getChatMessage()->setAppdata(appdata.toString().toStdString());
}
}
}
@ -192,6 +194,16 @@ void ContentModel::createThumbnail () {
}
}
void ContentModel::removeThumbnail(){
for(QMap<QString, QString>::iterator itData = mAppData.mData.begin() ; itData != mAppData.mData.end() ; ++itData){
QString thumbnailPath = QString::fromStdString(Paths::getThumbnailsDirPath()) +itData.value();
if( QFileInfo(thumbnailPath).isFile()){
QFile(thumbnailPath).remove();
}
}
mAppData.mData.clear();
}
void ContentModel::downloadFile(){
switch (mChatMessageModel->getState()) {
case LinphoneEnums::ChatMessageStateDelivered:
@ -228,10 +240,11 @@ void ContentModel::downloadFile(){
}
void ContentModel::openFile (bool showDirectory) {
if ((!mWasDownloaded && !mChatMessageModel->isOutgoing()) || mContent->getFilePath() == "") {
if (mChatMessageModel && ((!mWasDownloaded && !mChatMessageModel->isOutgoing()) || mContent->getFilePath() == "")) {
downloadFile();
}else{
QFileInfo info( Utils::coreStringToAppString(mContent->getFilePath()));
showDirectory = showDirectory || !info.exists();
QDesktopServices::openUrl(
QUrl(QStringLiteral("file:///%1").arg(showDirectory ? info.absolutePath() : info.absoluteFilePath()))
);

View file

@ -27,7 +27,8 @@
#include <QObject>
#include <QDateTime>
#include <QString>
class ChatMessageModel;
#include "components/chat-events/ChatMessageModel.hpp"
class ContentModel : public QObject{
Q_OBJECT
@ -67,9 +68,11 @@ public:
Q_INVOKABLE bool isText() const;
Q_INVOKABLE bool isVoiceRecording()const;
void createThumbnail ();
void createThumbnail (const bool& force = false);
void removeThumbnail ();
Q_INVOKABLE void downloadFile();
Q_INVOKABLE void openFile (bool showDirectory = false);
Q_INVOKABLE void openFile (bool showDirectory = false);
QString mThumbnail;
@ -88,6 +91,7 @@ signals:
private:
std::shared_ptr<linphone::Content> mContent;
ChatMessageModel* mChatMessageModel;
ChatMessageModel::AppDataManager mAppData; // Used if there is no Chat Message model set.
};
Q_DECLARE_METATYPE(std::shared_ptr<ContentModel>)

View file

@ -26,13 +26,14 @@
#include "utils/Utils.hpp"
#include "components/Components.hpp"
#include "components/chat/ChatModel.hpp"
#include "components/chat-events/ChatMessageModel.hpp"
#include "ContentListModel.hpp"
// =============================================================================
ContentProxyModel::ContentProxyModel (QObject * parent) : QSortFilterProxyModel(parent){
setContentListModel(CoreManager::getInstance()->getChatModel()->getContentListModel().get());
}
void ContentProxyModel::setChatMessageModel(ChatMessageModel * message){
@ -43,6 +44,17 @@ void ContentProxyModel::setChatMessageModel(ChatMessageModel * message){
emit chatMessageModelChanged();
}
void ContentProxyModel::setContentListModel(ContentListModel * model){
setSourceModel(model);
sort(0);
emit chatMessageModelChanged();
}
void ContentProxyModel::addFile(const QString& path){
ContentListModel* model = dynamic_cast<ContentListModel*>(sourceModel());
model->addFile(path);
}
bool ContentProxyModel::filterAcceptsRow (
int sourceRow,
const QModelIndex &sourceParent
@ -55,13 +67,13 @@ bool ContentProxyModel::filterAcceptsRow (
bool ContentProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
const ContentModel *contentA = sourceModel()->data(left).value<ContentModel *>();
const ContentModel *contentB = sourceModel()->data(right).value<ContentModel *>();
bool aIsForward = contentA->getChatMessageModel()->isForward();
bool aIsReply = contentA->getChatMessageModel()->isReply();
bool aIsForward = contentA->getChatMessageModel() && contentA->getChatMessageModel()->isForward();
bool aIsReply = contentA->getChatMessageModel() && contentA->getChatMessageModel()->isReply();
bool aIsVoiceRecording = contentA->isVoiceRecording();
bool aIsFile = contentA->isFile() || contentA->isFileEncrypted() || contentA->isFileTransfer();
bool aIsText = contentA->isText() ;
bool bIsForward = contentB->getChatMessageModel()->isForward();
bool bIsReply = contentB->getChatMessageModel()->isReply();
bool bIsForward = contentB->getChatMessageModel() && contentB->getChatMessageModel()->isForward();
bool bIsReply = contentB->getChatMessageModel() && contentB->getChatMessageModel()->isReply();
bool bIsVoiceRecording = contentB->isVoiceRecording();
bool bIsFile = contentB->isFile() || contentB->isFileEncrypted() || contentB->isFileTransfer();
bool bIsText = contentB->isText() ;
@ -76,3 +88,10 @@ bool ContentProxyModel::lessThan (const QModelIndex &left, const QModelIndex &ri
)
);
}
void ContentProxyModel::remove(ContentModel * model){
dynamic_cast<ContentListModel*>(sourceModel())->remove(model);
}
void ContentProxyModel::clear(){
dynamic_cast<ContentListModel*>(sourceModel())->clear();
}

View file

@ -41,6 +41,10 @@ public:
Q_PROPERTY(ChatMessageModel * chatMessageModel WRITE setChatMessageModel NOTIFY chatMessageModelChanged)
void setChatMessageModel(ChatMessageModel * message);
Q_INVOKABLE void setContentListModel(ContentListModel * model);
Q_INVOKABLE void addFile(const QString& path);
Q_INVOKABLE void remove(ContentModel * model);
Q_INVOKABLE void clear();
signals:
void chatMessageModelChanged();
@ -49,7 +53,7 @@ protected:
virtual bool filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const override;
virtual bool lessThan (const QModelIndex &left, const QModelIndex &right) const override;
std::shared_ptr<ContentListModel> mContens;
std::shared_ptr<ContentListModel> mContents;
};

View file

@ -29,6 +29,7 @@
#include "app/paths/Paths.hpp"
#include "components/calls/CallsListModel.hpp"
#include "components/chat/ChatModel.hpp"
#include "components/chat-room/ChatRoomModel.hpp"
#include "components/chat-room/ChatRoomListModel.hpp"
#include "components/contact/VcardModel.hpp"
@ -90,6 +91,7 @@ CoreManager::~CoreManager(){
void CoreManager::initCoreManager(){
mCallsListModel = new CallsListModel(this);
mChatModel = new ChatModel(this);
mChatRoomListModel = new ChatRoomListModel(this);
mContactsListModel = new ContactsListModel(this);
mContactsImporterListModel = new ContactsImporterListModel(this);

View file

@ -34,6 +34,7 @@ class QTimer;
class AbstractEventCountNotifier;
class AccountSettingsModel;
class CallsListModel;
class ChatModel;
class ChatRoomModel;
class ChatRoomListModel;
class ContactsListModel;
@ -140,6 +141,10 @@ public:
AbstractEventCountNotifier * getEventCountNotifier();
ChatModel * getChatModel() const{
return mChatModel;
}
static CoreManager *getInstance ();
// ---------------------------------------------------------------------------
@ -220,6 +225,7 @@ private:
ContactsImporterListModel *mContactsImporterListModel = nullptr;
TimelineListModel *mTimelineListModel = nullptr;
ChatRoomListModel *mChatRoomListModel = nullptr;
ChatModel *mChatModel = nullptr;
SipAddressesModel *mSipAddressesModel = nullptr;
SettingsModel *mSettingsModel = nullptr;

View file

@ -217,7 +217,17 @@ class ColorListModel : public QAbstractListModel {
ADD_COLOR("me_d_b_inv_fg", "#80FFFFFF", "[M] Menu disabled button : inverse foreground")
ADD_COLOR("me_h_b_inv_fg", "#B0FFFFFF", "[M] Menu hovered button : inverse foreground")
ADD_COLOR("me_p_b_inv_fg", "white", "[M] Menu pressed button : inverse foreground")
//-------------------------------------
// Wave Play
ADD_COLOR_WITH_LINK("w_n_b_bg", "", "[M] Wave play normal button : background", "ma_n_b_bg")
ADD_COLOR_WITH_LINK("w_d_b_bg", "", "[M] Wave play disabled button : background", "ma_d_b_bg")
ADD_COLOR_WITH_LINK("w_h_b_bg", "", "[M] Wave play hovered button : background", "ma_h_b_bg")
ADD_COLOR_WITH_LINK("w_p_b_bg", "", "[M] Wave play pressed button : background", "ma_p_b_bg")
ADD_COLOR_WITH_LINK("w_n_b_fg", "", "[M] Wave play normal button : foreground", "ma_n_b_fg")
ADD_COLOR_WITH_LINK("w_d_b_fg", "", "[M] Wave play disabled button : foreground", "ma_d_b_fg")
ADD_COLOR_WITH_LINK("w_h_b_fg", "", "[M] Wave play hovered button : foreground", "ma_h_b_fg")
ADD_COLOR_WITH_LINK("w_p_b_fg", "", "[M] Wave play pressed button : foreground", "ma_p_b_fg")
//--------------------------------------------------------------------------------------------------------------------
/*

View file

@ -1372,7 +1372,16 @@ void SettingsModel::setMipmapEnabled(const bool& enabled){
mConfig->setInt(UiSection, "mipmap_enabled", enabled);
emit mipmapEnabledChanged();
}
bool SettingsModel::useMinimalTimelineFilter() const{
return !!mConfig->getInt(UiSection, "use_minimal_timeline_filter", 1);
}
void SettingsModel::setUseMinimalTimelineFilter(const bool& useMinimal) {
mConfig->setInt(UiSection, "use_minimal_timeline_filter", useMinimal);
emit useMinimalTimelineFilterChanged();
}
// =============================================================================
// Advanced.
// =============================================================================

View file

@ -197,6 +197,7 @@ class SettingsModel : public QObject {
Q_PROPERTY(bool showStartVideoCallButton READ getShowStartVideoCallButton CONSTANT)
Q_PROPERTY(bool mipmapEnabled READ isMipmapEnabled WRITE setMipmapEnabled NOTIFY mipmapEnabledChanged)
Q_PROPERTY(bool useMinimalTimelineFilter READ useMinimalTimelineFilter WRITE setUseMinimalTimelineFilter NOTIFY useMinimalTimelineFilterChanged)
// Advanced. -----------------------------------------------------------------
@ -509,6 +510,9 @@ public:
bool isMipmapEnabled() const;
void setMipmapEnabled(const bool& enabled);
bool useMinimalTimelineFilter() const;
void setUseMinimalTimelineFilter(const bool& useMinimal);
// Advanced. ---------------------------------------------------------------------------
@ -688,6 +692,7 @@ signals:
void exitOnCloseChanged (bool value);
void mipmapEnabledChanged();
void useMinimalTimelineFilterChanged();
void checkForUpdateEnabledChanged();
void versionCheckUrlChanged();

View file

@ -138,7 +138,7 @@ void SoundPlayer::buildInternalPlayer () {
SettingsModel *settingsModel = coreManager->getSettingsModel();
mInternalPlayer = coreManager->getCore()->createLocalPlayer(
Utils::appStringToCoreString(settingsModel->getRingerDevice()), "", nullptr
Utils::appStringToCoreString(settingsModel->getPlaybackDevice()), "", nullptr
);
if(mInternalPlayer)
mInternalPlayer->addListener(mHandlers);

View file

@ -123,6 +123,8 @@ bool TimelineProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex &sou
bool TimelineProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
const TimelineModel* a = sourceModel()->data(left).value<TimelineModel*>();
const TimelineModel* b = sourceModel()->data(right).value<TimelineModel*>();
return a->getChatRoomModel()->mLastUpdateTime > b->getChatRoomModel()->mLastUpdateTime;
bool aHaveUnread = a->getChatRoomModel()->getAllUnreadCount() > 0;
bool bHaveUnread = b->getChatRoomModel()->getAllUnreadCount() > 0;
return (aHaveUnread && !bHaveUnread)
|| (aHaveUnread == bHaveUnread && a->getChatRoomModel()->mLastUpdateTime > b->getChatRoomModel()->mLastUpdateTime);
}

View file

@ -19,6 +19,7 @@ Item {
property alias placeholderText: textArea.placeholderText
property alias text: textArea.text
property alias cursorPosition: textArea.cursorPosition
property alias recordAudioToggled: recordAudioButton.toggled
property bool dropEnabled: true
property string dropDisabledReason
@ -56,11 +57,11 @@ Item {
// ---------------------------------------------------------------------------
RowLayout{
anchors.fill: parent
spacing: DroppableTextAreaStyle.fileChooserButton.margins
spacing: 0
// Handle click to select files.
ActionButton {
id: fileChooserButton
property int totalWidth: DroppableTextAreaStyle.fileChooserButton.margins + width
property int totalWidth: width
Layout.leftMargin: DroppableTextAreaStyle.fileChooserButton.margins
Layout.alignment: Qt.AlignVCenter
@ -88,12 +89,11 @@ Item {
}
// Record audio
ActionButton {
visible:droppableTextArea.enabled
id: recordAudioButton
visible: droppableTextArea.enabled
//anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: 0
enabled: droppableTextArea.dropEnabled
isCustom: true
backgroundRadius: 8
@ -111,6 +111,7 @@ Item {
Layout.maximumHeight: parent.height-20
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 2
//anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
clip:true
@ -137,9 +138,7 @@ Item {
}
}
function handleValidation () {
if (RecorderManager.haveVocalRecorder || text.length !== 0) {
validText(text)
}
}
background: Rectangle {
@ -194,7 +193,7 @@ Item {
ActionButton {
id: sendButton
property int totalWidth: Layout.rightMargin + Layout.leftMargin + width
Layout.rightMargin: DroppableTextAreaStyle.fileChooserButton.margins+15
Layout.rightMargin: 15
Layout.leftMargin: 10
Layout.alignment: Qt.AlignVCenter
visible: droppableTextArea.enabled

View file

@ -19,7 +19,7 @@ Controls.TextField {
property string error: ''
property var tools
property QtObject textFieldStyle : TextFieldStyle.normal
property bool persistentIcon: false
property bool showWhenEmpty: true
onTextFieldStyleChanged: if( !textFieldStyle) textFieldStyle = TextFieldStyle.normal
signal iconClicked()
@ -87,7 +87,7 @@ Controls.TextField {
}
iconSize: parent.contentHeight
visible: persistentIcon || !parent.text
visible: showWhenEmpty && !parent.text || !showWhenEmpty && parent.text
MouseArea{
anchors.fill: parent
onClicked: textField.iconClicked()

View file

@ -21,6 +21,7 @@ ProgressBar {
property alias colorSet: progression.colorSet
property alias backgroundColor: backgroundArea.color
property alias durationTextColor: durationText.color
property int waveLeftMargin: 0
function start(){
progressBar.value = 0
@ -61,13 +62,14 @@ ProgressBar {
progression.percentageDisplayed = value
}
anchors.topMargin: 5
anchors.bottomMargin: 5
anchors.topMargin: 2
anchors.bottomMargin: 2
background: Rectangle {
id: backgroundArea
color: MediaProgressBarStyle.backgroundColor
radius: 5
clip: false
}
@ -76,16 +78,19 @@ ProgressBar {
anchors.fill: parent
radius: 5
color: 'transparent'
clip: false
RowLayout{
anchors.fill: parent
spacing: 10
spacing: 0
ActionButton{
id: progression
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: progressBar.waveLeftMargin
backgroundRadius: 5
fillMode: Image.TileHorizontally
verticalAlignment: Image.AlignLeft
horizontalAlignment: Image.AlignLeft
isCustom: true
colorSet: MediaProgressBarStyle.progressionWave
percentageDisplayed: 0
@ -95,13 +100,15 @@ ProgressBar {
id: durationText
Layout.fillHeight: true
Layout.preferredWidth: implicitWidth
Layout.rightMargin: 5
Layout.leftMargin: 15
Layout.rightMargin: 6
horizontalAlignment: Qt.AlignRight
verticalAlignment: Qt.AlignVCenter
text: progressBar.progressPosition >= 0 ? Utils.formatElapsedTime( progressBar.progressPosition / 1000 ) : '-'
text: progressBar.progressPosition > 0 ? Utils.formatElapsedTime( progressBar.progressPosition / 1000 )
:( progressBar.progressPosition == 0 ? Utils.formatElapsedTime( progressBar.progressDuration / 1000) : '-')
property font customFont : SettingsModel.textMessageFont
font.family: customFont.family
font.pointSize: Units.dp * (customFont.pointSize + 2)
font.pointSize: Units.dp * (customFont.pointSize + 1)
}
}
}

View file

@ -22,11 +22,11 @@ QtObject {
property int iconSize: 40
property string icon : 'attachment_custom'
property string name : 'attachment'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_h_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_n_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
@ -35,24 +35,27 @@ QtObject {
property int iconSize: 40
property string name : 'micro'
property string icon : 'chat_micro_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_h_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_n_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color backgroundUpdatingColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_u', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
property color foregroundUpdatingColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_u', icon, 'me_p_b_fg').color
}
property QtObject send: QtObject {
property int margins: 6
property int iconSize: 40
property int margins: 5
property int iconSize: 30
property string name : 'send'
property string icon : 'send_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_h_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_n_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}

View file

@ -12,9 +12,9 @@ QtObject {
property string gaugeIcon: 'chat_audio_soundwave_custom'
property QtObject progressionWave: QtObject{
property int iconSize: 30
property int iconHeight: 40
property int iconWidth: 250
property int iconSize: 60
property int iconHeight: 60
property int iconWidth: 60
property string name : 'progression_soundwave'
property string icon : 'chat_audio_soundwave_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'a_n_b_bg').color

View file

@ -7,6 +7,9 @@ import Common 1.0
ListView {
id: view
property bool hideScrollBars: false
property alias verticalScrollPolicy : vScrollBar.policy
property alias horizontalScrollPolicy : hScrollBar.policy
function getVisibleIndex(checkMax) {
var center_x = view.x + view.width / 2
@ -35,21 +38,55 @@ ListView {
ScrollBar.vertical: ForceScrollBar {
id: vScrollBar
onPressedChanged: pressed ? view.movementStarted() : view.movementEnded()
// ScrollBar.AsNeeded doesn't work. Do it ourself.
policy: ScrollBar.AlwaysOff
function updatePolicy(){
policy = (view.orientation == Qt.Vertical && view.contentHeight > view.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff)
}
Timer{// Delay to avoid binding loops
id:delayUpdateVPolicy
interval:10
onTriggered: vScrollBar.updatePolicy()
}
Component.onCompleted: if(!hideScrollBars) updatePolicy()
}
ScrollBar.horizontal: ForceScrollBar {
id: hScrollBar
onPressedChanged: pressed ? view.movementStarted() : view.movementEnded()
// ScrollBar.AsNeeded doesn't work. Do it ourself.
policy: (view.contentHeight > view.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff)
policy: ScrollBar.AlwaysOff
function updatePolicy() {
policy = (view.orientation == Qt.Horizontal && view.contentWidth > view.width? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff)
}
Timer{// Delay to avoid binding loops
id:delayUpdateHPolicy
interval:10
onTriggered: hScrollBar.updatePolicy()
}
Component.onCompleted: if(!hideScrollBars) updatePolicy()
}
// ---------------------------------------------------------------------------
boundsMovement: Flickable.StopAtBounds
boundsBehavior: Flickable.DragOverBounds
clip: true
contentWidth: width - (vScrollBar.visible?vScrollBar.width:0)
contentHeight: height - (hScrollBar.visible?hScrollBar.height:0)
spacing: 0
synchronousDrag: true
onContentHeightChanged: cacheBuffer=view.contentHeight
cacheBuffer: height
onContentHeightChanged: {
cacheBuffer= (view.contentHeight > 0 ? view.contentHeight : 0)
if(!hideScrollBars)
delayUpdateVPolicy.restart()
}
onHeightChanged: {
if(!hideScrollBars)
delayUpdateVPolicy.restart()
}
onContentWidthChanged: if(!hideScrollBars) delayUpdateHPolicy.restart()
onWidthChanged: if(!hideScrollBars) delayUpdateHPolicy.restart()
cacheBuffer: height > 0 ? height : 0
// ---------------------------------------------------------------------------
// TODO: Find a solution at this bug =>

View file

@ -54,7 +54,7 @@ function getComponentFromEntry (chatEntry) {
function handleFilesDropped (files) {
chat.bindToEnd = true
files.forEach(container.proxyModel.sendFileMessage)
files.forEach(chatMessagePreview.addFile)
}
function handleMoreEntriesLoaded (n) {

View file

@ -7,6 +7,7 @@ import Linphone 1.0
import Linphone.Styles 1.0
import Utils 1.0
import UtilsCpp 1.0
import LinphoneEnums 1.0
import Units 1.0
@ -46,7 +47,6 @@ Rectangle {
ScrollableListView {
id: chat
// -----------------------------------------------------------------------
property bool bindToEnd: false
property bool displaying: false
@ -371,128 +371,140 @@ Rectangle {
}
ChatMessagePreview{
id: chatMessagePreview
Layout.fillWidth: true
replyChatRoomModel: proxyModel.chatRoomModel
}
Rectangle{
id: messageBlock
onHeightChanged: height = Layout.preferredHeight
Layout.preferredHeight: visible && opacity > 0 ? 32 : 0
Layout.fillWidth: true
Layout.leftMargin: ChatStyle.entry.leftMargin
Layout.rightMargin: ChatStyle.entry.rightMargin
color: ChatStyle.messageBanner.color
radius: 10
state: "hidden"
Timer{
id: hideNoticeBanner
interval: 4000
repeat: false
onTriggered: messageBlock.state = "hidden"
}
RowLayout{
anchors.centerIn: parent
spacing: 5
Icon{
icon: ChatStyle.copyTextIcon
overwriteColor: ChatStyle.messageBanner.textColor
iconSize: 20
}
Text{
Layout.fillHeight: true
Layout.fillWidth: true
text: container.noticeBannerText
font {
pointSize: ChatStyle.messageBanner.pointSize
}
color: ChatStyle.messageBanner.textColor
}
}
states: [
State {
name: "hidden"
PropertyChanges { target: messageBlock; opacity: 0 }
},
State {
name: "showed"
PropertyChanges { target: messageBlock; opacity: 1 }
}
]
transitions: [
Transition {
from: "*"; to: "showed"
SequentialAnimation{
NumberAnimation{ properties: "opacity"; easing.type: Easing.OutBounce; duration: 500 }
ScriptAction{ script: hideNoticeBanner.start()}
}
},
Transition {
SequentialAnimation{
NumberAnimation{ properties: "opacity"; duration: 1000 }
ScriptAction{ script: container.noticeBannerText = '' }
}
}
]
}
// -------------------------------------------------------------------------
// Send area.
// -------------------------------------------------------------------------
Borders {
id: textAreaBorders
Rectangle {
id: bottomChatBackground
Layout.fillWidth: true
Layout.preferredHeight: textArea.height
borderColor: ChatStyle.sendArea.border.color
topWidth: ChatStyle.sendArea.border.width
visible: proxyModel.chatRoomModel && !proxyModel.chatRoomModel.hasBeenLeft && (!proxyModel.chatRoomModel.haveEncryption && SettingsModel.standardChatEnabled || proxyModel.chatRoomModel.haveEncryption && SettingsModel.secureChatEnabled)
DroppableTextArea {
id: textArea
enabled:proxyModel && proxyModel.chatRoomModel ? !proxyModel.chatRoomModel.hasBeenLeft:false
isEphemeral : proxyModel && proxyModel.chatRoomModel ? proxyModel.chatRoomModel.ephemeralEnabled:false
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width
minimumHeight:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width
maximumHeight:container.height/2
dropEnabled: SettingsModel.fileTransferUrl.length > 0
dropDisabledReason: qsTr('noFileTransferUrl')
placeholderText: qsTr('newMessagePlaceholder')
onDropped: Logic.handleFilesDropped(files)
onTextChanged: Logic.handleTextChanged(text)
onValidText: {
textArea.text = ''
chat.bindToEnd = true
if(proxyModel.chatRoomModel) {
proxyModel.sendMessage(text)
}else{
console.log("Peer : " +proxyModel.peerAddress+ "/"+chat.model.peerAddress)
proxyModel.chatRoomModel = CallsListModel.createChat(proxyModel.peerAddress)
proxyModel.sendMessage(text)
}
}
onAudioRecordRequest: RecorderManager.resetVocalRecorder()
Component.onCompleted: {text = proxyModel.cachedText; cursorPosition=text.length}
Layout.preferredHeight: textAreaBorders.height + chatMessagePreview.height+messageBlock.height
color: ChatStyle.sendArea.backgroundBorder.color
clip: true
ColumnLayout{
anchors.fill: parent
spacing: 0
Rectangle{
anchors.fill:parent
color:'white'
opacity: 0.5
visible:!textArea.enabled
id: messageBlock
onHeightChanged: height = Layout.preferredHeight
Layout.preferredHeight: visible && opacity > 0 ? 32 : 0
Layout.fillWidth: true
Layout.leftMargin: ChatStyle.entry.leftMargin
Layout.rightMargin: ChatStyle.entry.rightMargin
color: ChatStyle.messageBanner.color
radius: 10
state: "hidden"
Timer{
id: hideNoticeBanner
interval: 4000
repeat: false
onTriggered: messageBlock.state = "hidden"
}
RowLayout{
anchors.centerIn: parent
spacing: 5
Icon{
icon: ChatStyle.copyTextIcon
overwriteColor: ChatStyle.messageBanner.textColor
iconSize: 20
}
Text{
Layout.fillHeight: true
Layout.fillWidth: true
text: container.noticeBannerText
font {
pointSize: ChatStyle.messageBanner.pointSize
}
color: ChatStyle.messageBanner.textColor
}
}
states: [
State {
name: "hidden"
PropertyChanges { target: messageBlock; opacity: 0 }
},
State {
name: "showed"
PropertyChanges { target: messageBlock; opacity: 1 }
}
]
transitions: [
Transition {
from: "*"; to: "showed"
SequentialAnimation{
NumberAnimation{ properties: "opacity"; easing.type: Easing.OutBounce; duration: 500 }
ScriptAction{ script: hideNoticeBanner.start()}
}
},
Transition {
SequentialAnimation{
NumberAnimation{ properties: "opacity"; duration: 1000 }
ScriptAction{ script: container.noticeBannerText = '' }
}
}
]
}// MessageBlock
ChatMessagePreview{
id: chatMessagePreview
Layout.fillWidth: true
Layout.leftMargin: ChatStyle.sendArea.backgroundBorder.width
maxHeight: container.height - textAreaBorders.height
replyChatRoomModel: proxyModel.chatRoomModel
}
}
}
// -------------------------------------------------------------------------
// Send area.
// -------------------------------------------------------------------------
Borders {
id: textAreaBorders
Layout.fillWidth: true
Layout.preferredHeight: textArea.height
Layout.leftMargin: ChatStyle.sendArea.backgroundBorder.width
borderColor: ChatStyle.sendArea.border.color
topWidth: ChatStyle.sendArea.border.width
visible: proxyModel.chatRoomModel && !proxyModel.chatRoomModel.hasBeenLeft && (!proxyModel.chatRoomModel.haveEncryption && SettingsModel.standardChatEnabled || proxyModel.chatRoomModel.haveEncryption && SettingsModel.secureChatEnabled)
DroppableTextArea {
id: textArea
enabled:proxyModel && proxyModel.chatRoomModel ? !proxyModel.chatRoomModel.hasBeenLeft:false
isEphemeral : proxyModel && proxyModel.chatRoomModel ? proxyModel.chatRoomModel.ephemeralEnabled:false
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width
minimumHeight:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width
maximumHeight:container.height/2
dropEnabled: SettingsModel.fileTransferUrl.length > 0
dropDisabledReason: qsTr('noFileTransferUrl')
placeholderText: qsTr('newMessagePlaceholder')
recordAudioToggled: RecorderManager.haveVocalRecorder && RecorderManager.getVocalRecorder().state != LinphoneEnums.RecorderStateClosed
onDropped: Logic.handleFilesDropped(files)
onTextChanged: Logic.handleTextChanged(text)
onValidText: {
textArea.text = ''
chat.bindToEnd = true
if(proxyModel.chatRoomModel) {
proxyModel.sendMessage(text)
}else{
console.log("Peer : " +proxyModel.peerAddress+ "/"+chat.model.peerAddress)
proxyModel.chatRoomModel = CallsListModel.createChat(proxyModel.peerAddress)
proxyModel.sendMessage(text)
}
}
onAudioRecordRequest: RecorderManager.resetVocalRecorder()
Component.onCompleted: {text = proxyModel.cachedText; cursorPosition=text.length}
Rectangle{
anchors.fill:parent
color:'white'
opacity: 0.5
visible:!textArea.enabled
}
}
}// Send Area
}// ColumnLayout
}// Bottom background
}

View file

@ -26,12 +26,14 @@ Loader{
property ContentModel contentModel
property int maxWidth : parent.width
property int fitWidth: active ? Math.max(maxWidth - ChatAudioMessageStyle.emptySpace, ChatAudioMessageStyle.minWidth) : 0
property int fitHeight: active ? 40 : 0
property int fitHeight: active ? 60 : 0
property font customFont : SettingsModel.textMessageFont
property bool isOutgoing : contentModel && (contentModel.chatMessageModel.isOutgoing || contentModel.chatMessageModel.state == LinphoneEnums.ChatMessageStateIdle);
property bool isOutgoing : contentModel && contentModel.chatMessageModel && (contentModel.chatMessageModel.isOutgoing || contentModel.chatMessageModel.state == LinphoneEnums.ChatMessageStateIdle);
property bool isActive: active
active: contentModel && contentModel.isVoiceRecording()
sourceComponent: Item{
id: loadedItem
property bool isPlaying : vocalPlayer.item && vocalPlayer.item.playbackState === SoundPlayer.PlayingState
@ -70,6 +72,7 @@ Loader{
Layout.leftMargin: 15
Layout.alignment: Qt.AlignVCenter
isCustom: true
backgroundRadius: width
colorSet: (loadedItem.isPlaying ? ChatAudioMessageStyle.pauseAction
: ChatAudioMessageStyle.playAction)
onClicked:{
@ -84,7 +87,9 @@ Loader{
Layout.fillHeight: true
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: 15
Layout.rightMargin: 10
Layout.topMargin: 10
Layout.bottomMargin: 10
MediaProgressBar{
id: mediaProgressBar
anchors.fill: parent

View file

@ -28,7 +28,7 @@ Rectangle{
mediaProgressBar.stop()
onIsPlayingChanged: isPlaying ? mediaProgressBar.resume() : mediaProgressBar.stop()
Layout.preferredHeight: visible ? 70 : 0
Layout.preferredHeight: visible ? ChatAudioPreviewStyle.height : 0
color: ChatAudioPreviewStyle.backgroundColor
radius: 0
@ -54,17 +54,19 @@ Rectangle{
RowLayout{
id: lineLayout
anchors.fill: parent
spacing: 10
spacing: 0
ActionButton{
Layout.preferredHeight: iconSize
Layout.preferredWidth: iconSize
Layout.leftMargin: 10
Layout.leftMargin: 6
Layout.alignment: Qt.AlignVCenter
isCustom: true
colorSet: ChatAudioPreviewStyle.deleteAction
onClicked: RecorderManager.clearVocalRecorder()
}
VuMeter {
Layout.leftMargin: 6
Layout.rightMargin: 6
Timer {
interval: 50
repeat: true
@ -78,17 +80,22 @@ Rectangle{
Layout.fillHeight: true
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Layout.topMargin: 5
Layout.bottomMargin: 5
Layout.topMargin: 10
Layout.bottomMargin: 10
Layout.leftMargin: 6
MediaProgressBar{
id: mediaProgressBar
anchors.fill: parent
waveLeftMargin: !vocalPlayer.item && vocalRecorder ? 10 : 0
progressDuration: !vocalPlayer.item && vocalRecorder? vocalRecorder.getDuration() : 0
progressPosition: !vocalPlayer.item ? progressDuration : 0
value: !vocalPlayer.item ? 0.01 * progressDuration / 5 : 100
stopAtEnd: !audioPreviewBlock.isRecording
resetAtEnd: false
colorSet: isRecording ? ChatAudioPreviewStyle.recordingProgressionWave : ChatAudioPreviewStyle.progressionWave
function progressComputation(t) {
return 1 * Math.sqrt(1 - (t=t/1-1)*t);
}
function refresh(){
if( vocalPlayer.item){
progressPosition = vocalPlayer.item.getPosition()
@ -96,7 +103,10 @@ Rectangle{
}else{// Recording
progressDuration = vocalRecorder.getDuration()
progressPosition = progressDuration
value = value + 0.01
if( value == 0)
value = 1
else
value = value + Math.pow(value,-0.7)
}
}
onEndReached:{
@ -114,8 +124,8 @@ Rectangle{
ActionButton{
Layout.preferredHeight: iconSize
Layout.preferredWidth: iconSize
Layout.rightMargin: 15
Layout.leftMargin: 5
Layout.rightMargin: ChatStyle.rightButtonMargin
Layout.leftMargin: ChatStyle.rightButtonLMargin
Layout.alignment: Qt.AlignVCenter
isCustom: true
colorSet: audioPreviewBlock.isRecording ? ChatAudioPreviewStyle.stopAction
@ -124,7 +134,7 @@ Rectangle{
onClicked:{
if(audioPreviewBlock.isRecording){// Stop the record and save the file
audioPreviewBlock.vocalRecorder.stop()
//mediaProgressBar.value = 100
mediaProgressBar.value = 0
}else if(audioPreviewBlock.isPlaying){// Pause the play
vocalPlayer.item.pause()
}else{// Play the audio

View file

@ -35,7 +35,7 @@ Column{
spacing: 0
property bool isOutgoing : contentModel && (contentModel.chatMessageModel.isOutgoing || contentModel.chatMessageModel.state == LinphoneEnums.ChatMessageStateIdle);
property bool isOutgoing : contentModel && contentModel.chatMessageModel && (contentModel.chatMessageModel.isOutgoing || contentModel.chatMessageModel.state == LinphoneEnums.ChatMessageStateIdle);
ChatAudioMessage{
id: audioMessage

View file

@ -18,7 +18,7 @@ Row {
property ChatMessageModel chatMessageModel: contentModel && contentModel.chatMessageModel
property ContentModel contentModel
property bool isOutgoing : contentModel && ( chatMessageModel.isOutgoing || chatMessageModel.state == LinphoneEnums.ChatMessageStateIdle);
property bool isOutgoing : chatMessageModel && ( chatMessageModel.isOutgoing || chatMessageModel.state == LinphoneEnums.ChatMessageStateIdle);
property int fitWidth: visible ? Math.max(fileName.implicitWidth + 5 + thumbnailProvider.width + 3*ChatStyle.entry.message.file.margins
, Math.max(ChatStyle.entry.message.file.width, ChatStyle.entry.message.outgoing.areaSize)) : 0
property int fitHeight: visible ? rectangle.height : 0

View file

@ -0,0 +1,86 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Common 1.0
import Linphone 1.0
import Linphone.Styles 1.0
import Utils 1.0
import UtilsCpp 1.0
import Units 1.0
import 'Chat.js' as Logic
// =============================================================================
Item{
visible: mainListView.count > 0
Layout.preferredHeight: visible ? ChatFilePreviewStyle.height : 0
function addFile(path){
contents.addFile(path)
}
ScrollableListView{
id: mainListView
spacing: ChatFilePreviewStyle.filePreview.closeButton.iconSize
anchors.fill: parent
anchors.rightMargin: ChatStyle.rightButtonMargin + ChatStyle.rightButtonLMargin + ChatStyle.rightButtonSize
orientation: Qt.Horizontal
model: ContentProxyModel{
id: contents
}
header:Component{
Item{
width: ChatFilePreviewStyle.filePreview.closeButton.iconSize/2
height:mainListView.height
}
}
footer: Component{
Item{
width: ChatFilePreviewStyle.filePreview.closeButton.iconSize
height:mainListView.height
}
}
delegate:
FileView{
height:mainListView.height-ChatFilePreviewStyle.filePreview.heightMargins
width: height * ChatFilePreviewStyle.filePreview.format
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 7
//anchors.horizontalCenter: parent.horizontalCenter
thumbnail: modelData.thumbnail
name: modelData.name
animationScale: 1.1
onClickOnFile: {
modelData.openFile()
}
ActionButton{
anchors.bottom: parent.top
anchors.bottomMargin: -height/2
anchors.left: parent.right
anchors.leftMargin: -width/2
isCustom: true
backgroundRadius: width
colorSet: ChatFilePreviewStyle.filePreview.removeButton
z: parent.z+1
onClicked:{
contents.remove(modelData)
}
}
}
}
ActionButton{
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: ChatStyle.rightButtonMargin
isCustom: true
backgroundRadius: width
colorSet: ChatFilePreviewStyle.filePreview.closeButton
z: parent.z+1
onClicked:{
contents.clear()
}
}
}

View file

@ -15,18 +15,48 @@ import 'Chat.js' as Logic
// =============================================================================
ColumnLayout{
property alias replyChatRoomModel : replyPreview.chatRoomModel
property int maxHeight: parent.height - ( audioPreview.visible ? audioPreview.height : 0)
property int maxHeight: parent.height
property int fitHeight: (replyPreview.visible ? replyPreview.height + replySeparator.height: 0 )
+ (audioPreview.visible ? audioPreview.height + audioSeparator.height: 0)
+ (filesPreview.visible ? filesPreview.height + filesSeparator.height: 0)
spacing: 0
Layout.preferredHeight: (replyPreview.visible ? replyPreview.height : 0 ) + (audioPreview.visible ? audioPreview.height : 0)
Layout.maximumHeight: Layout.preferredHeight
Layout.preferredHeight: fitHeight
Layout.maximumHeight: fitHeight> maxHeight ? maxHeight : fitHeight // ?? just using maxHeight doesn't work.
function hide(){
}
function addFile(path){
filesPreview.addFile(path)
}
ChatReplyPreview{
id: replyPreview
Layout.fillWidth: true
maxHeight: parent.maxHeight - (audioPreview.visible ? audioPreview.height + audioSeparator.height: 0)
- (filesPreview.visible ? filesPreview.height + filesSeparator.height: 0)
}
Item{
id: replySeparator
visible: replyPreview.visible
Layout.preferredHeight: visible ? ChatStyle.separatorHeight : 0
Layout.fillWidth: true
}
ChatAudioPreview{
id: audioPreview
Layout.fillWidth: true
}
Item{
id: audioSeparator
visible: audioPreview.visible
Layout.preferredHeight: visible ? ChatStyle.separatorHeight : 0
Layout.fillWidth: true
}
ChatFilePreview{
id: filesPreview
Layout.fillWidth: true
}
Item{
id: filesSeparator
visible: filesPreview.visible
Layout.preferredHeight: visible ? ChatStyle.separatorHeight : 0
Layout.fillWidth: true
}
}

View file

@ -108,9 +108,10 @@ Item {
color: ChatReplyMessageStyle.replyArea.foregroundColor
}
ListView {
ScrollableListView {
id: replyMessage
property int fitWidth : 0
hideScrollBars: true
anchors.top: usernameReplied.bottom
anchors.left: parent.left
anchors.right: parent.right
@ -130,7 +131,13 @@ Item {
model: ContentProxyModel{
chatMessageModel: mainItem.chatMessageModel
}
height: contentHeight
Timer{// Delay to avoid binding loops
id:delayUpdate
interval:10
onTriggered: replyMessage.height = replyMessage.contentHeight
}
onContentHeightChanged: delayUpdate.restart()
//height: contentHeight
delegate: ChatContent{
contentModel: modelData
@ -140,6 +147,13 @@ Item {
onFitWidthChanged:{
replyMessage.updateWidth()
}
Rectangle{
anchors.left: parent.left
anchors.right: parent.right
color: ChatStyle.entry.separator.color
height: visible ? ChatStyle.entry.separator.width : 0
visible: (index !== (replyMessage.count - 1))
}
}
}
}

View file

@ -17,9 +17,8 @@ import 'Chat.js' as Logic
Rectangle{
id: replyPreviewBlock
property ChatRoomModel chatRoomModel
Layout.preferredHeight: visible ? Math.min(messageContentsList.height + replyPreviewHeaderArea.implicitHeight + 15, parent.maxHeight) : 0
property int maxHeight : parent.maxHeight
Layout.preferredHeight: visible ? Math.min(messageContentsList.height + replyPreviewHeaderArea.implicitHeight + 15, replyPreviewBlock.maxHeight) : 0
property int leftMargin: textArea.textLeftMargin
property int rightMargin: textArea.textRightMargin
@ -70,9 +69,10 @@ Rectangle{
color: ChatStyle.replyPreview.headerTextColor
}
}
Flickable {
id: replyPreviewTextArea
ScrollBar.vertical: ForceScrollBar {visible: replyPreviewTextArea.height < messageContentsList.implicitHeight}
ScrollBar.vertical: ForceScrollBar {visible: replyPreviewTextArea.height < messageContentsList.height}
boundsBehavior: Flickable.StopAtBounds
clip: true
contentHeight: messageContentsList.height
@ -93,13 +93,20 @@ Rectangle{
delegate: ChatContent{
contentModel: modelData
textFont.pointSize: Units.dp * (SettingsModel.textMessageFont.pointSize - 2)
Rectangle{
anchors.left: parent.left
anchors.right: parent.right
color: ChatStyle.entry.separator.color
height: visible ? ChatStyle.entry.separator.width : 0
visible: (index !== (messageContentsList.count - 1))
}
}
}
}
}
ActionButton{
anchors.right:parent.right
anchors.rightMargin: 14
anchors.rightMargin: ChatStyle.rightButtonMargin
anchors.verticalCenter: parent.verticalCenter
height: ChatStyle.replyPreview.closeButton.iconSize
isCustom: true

View file

@ -23,7 +23,7 @@ TextEdit {
property ContentModel contentModel
property string lastTextSelected : ''
property font customFont : SettingsModel.textMessageFont
property int fitHeight: visible ? contentHeight + padding + 6 : 0
property int fitHeight: visible ? contentHeight + padding + 8 : 0
property int fitWidth: visible ? implicitWidth + 2: 0 // add 2 because there is a bug on border that lead to not fit text exactly
signal rightClicked()

View file

@ -90,11 +90,11 @@ Item {
}
onGoToMessage: container.goToMessage(message)
}
ListView {
id: messageContentsList
anchors.left: parent.left
anchors.right: parent.right
visible: count > 0
spacing: 0
model: ContentProxyModel{
chatMessageModel: $chatEntry
@ -102,14 +102,22 @@ Item {
height: contentHeight
boundsBehavior: Flickable.StopAtBounds
interactive: false
delegate: ChatContent{
contentModel: modelData
onFitWidthChanged:{
rectangle.updateWidth()
delegate:
ChatContent{
contentModel: modelData
onFitWidthChanged:{
rectangle.updateWidth()
}
onLastTextSelectedChanged: container.lastTextSelected= lastTextSelected
onRightClicked: chatMenu.open()
Rectangle{
anchors.left: parent.left
anchors.right: parent.right
color: ChatStyle.entry.separator.color
height: visible ? ChatStyle.entry.separator.width : 0
visible: (index !== (messageContentsList.count - 1))
}
}
onLastTextSelectedChanged: container.lastTextSelected= lastTextSelected
onRightClicked: chatMenu.open()
}
}
}
Row{

View file

@ -0,0 +1,135 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Common 1.0
import Linphone 1.0
import LinphoneUtils 1.0
import LinphoneEnums 1.0
import Linphone.Styles 1.0
import Utils 1.0
import Units 1.0
import ColorsList 1.0
// =============================================================================
Item {
id: mainItem
property string thumbnail
property string name
property bool active: true
property real animationScale : ChatStyle.entry.message.file.animation.to
property alias imageScale: thumbnailProvider.scale
signal clickOnFile()
// ---------------------------------------------------------------------
// Thumbnail or extension.
// ---------------------------------------------------------------------
Component {
id: thumbnailImage
Image {
id: thumbnailImageSource
mipmap: SettingsModel.mipmapEnabled
source: mainItem.thumbnail
fillMode: Image.PreserveAspectFit
}
}
Component {
id: extension
Rectangle {
color: ChatStyle.entry.message.file.extension.background.color
Text {
anchors.fill: parent
color: ChatStyle.entry.message.file.extension.text.color
font.bold: true
elide: Text.ElideRight
text: Utils.getExtension(mainItem.name).toUpperCase()
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
Loader {
id: thumbnailProvider
anchors.fill: parent
//Layout.fillHeight: true
//Layout.preferredWidth: parent.height
sourceComponent: (mainItem.active ? (mainItem.thumbnail ? thumbnailImage : extension ): undefined)
ScaleAnimator {
id: thumbnailProviderAnimator
target: mainItem
duration: ChatStyle.entry.message.file.animation.duration
easing.type: Easing.InOutQuad
from: 1.0
}
states: State {
name: 'hovered'
}
transitions: [
Transition {
from: ''
to: 'hovered'
ScriptAction {
script: {
if (thumbnailProviderAnimator.running) {
thumbnailProviderAnimator.running = false
}
mainItem.z = 999//Constants.zPopup
thumbnailProviderAnimator.to = mainItem.animationScale
thumbnailProviderAnimator.running = true
}
}
},
Transition {
from: 'hovered'
to: ''
ScriptAction {
script: {
if (thumbnailProviderAnimator.running) {
thumbnailProviderAnimator.running = false
}
thumbnailProviderAnimator.to = 1.0
thumbnailProviderAnimator.running = true
mainItem.z = 0
}
}
}
]
}
MouseArea {
function handleMouseMove (mouse) {
thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse)
? 'hovered'
: ''
}
anchors.fill: parent
onClicked: {
clickOnFile()
thumbnailProvider.state = ''
}
onExited: thumbnailProvider.state = ''
onMouseXChanged: handleMouseMove.call(this, mouse)
onMouseYChanged: handleMouseMove.call(this, mouse)
}
}

View file

@ -8,54 +8,54 @@ import ColorsList 1.0
QtObject {
property string sectionName : 'ChatAudioMessage'
property int minWidth: 400
property int emptySpace: 100
property int minWidth: 500
property int emptySpace: 10
property color color: ColorsList.add(sectionName, 'q').color
property color backgroundColor: ColorsList.add(sectionName+'_bg', 'a').color
property QtObject pauseAction: QtObject {
property int iconSize: 30
property int iconSize: 25
property string name : 'pause'
property string icon : 'chat_audio_pause_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'q').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'q').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'q').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject playAction: QtObject {
property int iconSize: 30
property int iconSize: 25
property string name : 'play'
property string icon : 'chat_audio_play_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'q').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'q').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'q').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject progressionWave: QtObject{
property int iconSize: 30
property int iconHeight: 40
property int iconWidth: 250
property int iconSize: 60
property int iconHeight: 60
property int iconWidth: 60
property string name : 'progression_soundwave'
property string icon : 'chat_audio_soundwave_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'a_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'a_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'a_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'a_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'a_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'a_p_b_fg').color
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'w_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'w_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'w_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'w_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'w_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'w_p_b_fg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_n_b_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_h_b_bg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_h_b_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_n_b_bg').color
property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'l_p_b_bg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_n_b_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_h_b_fg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_h_b_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_n_b_fg').color
property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'l_p_b_fg').color
}

View file

@ -9,6 +9,8 @@ import ColorsList 1.0
QtObject {
property string sectionName : 'ChatAudioPreview'
property color color: ColorsList.add(sectionName, 'q').color
property int height: 70
property QtObject header: QtObject{
property color color: ColorsList.add(sectionName+'_header', 'h').color
property int pointSizeOffset: -3
@ -31,51 +33,51 @@ QtObject {
property int iconSize: 40
property string name : 'delete'
property string icon : 'delete_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_h_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_n_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject stopAction: QtObject {
property int iconSize: 30
property string name : 'stop'
property string icon : 'chat_audio_stop_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property string icon : 'chat_audio_preview_stop_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_h_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_n_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject pauseAction: QtObject {
property int iconSize: 30
property string name : 'pause'
property string icon : 'chat_audio_pause_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property string icon : 'chat_audio_preview_pause_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_h_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_n_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject playAction: QtObject {
property int iconSize: 30
property string name : 'play'
property string icon : 'chat_audio_play_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property string icon : 'chat_audio_preview_play_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_h_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_n_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject progressionWave: QtObject{
property int iconSize: 30
property int iconHeight: 40
property int iconWidth: 250
property int iconSize: 60
property int iconHeight: 60
property int iconWidth: 60
property string name : 'progression_soundwave'
property string icon : 'chat_audio_soundwave_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'a_n_b_bg').color
@ -85,35 +87,37 @@ QtObject {
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'a_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'a_p_b_fg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_n_b_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_h_b_bg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_h_b_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_n_b_bg').color
property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'l_p_b_bg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_n_b_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_h_b_fg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_h_b_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_n_b_fg').color
property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'l_p_b_fg').color
}
property QtObject recordingProgressionWave: QtObject{
property int iconSize: 30
property int iconHeight: 40
property int iconWidth: 250
property int iconSize: 60
property int iconHeight: 60
property int iconWidth: 60
property string name : 'recording_progression_soundwave'
property string icon : 'chat_audio_soundwave_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'r_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'r_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'r_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'r_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'r_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'r_p_b_fg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_n_b_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_h_b_bg').color
property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'l_p_b_bg').color
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'l_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'l_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'l_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'l_h_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'l_n_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'l_p_b_fg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_n_b_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_h_b_fg').color
property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'l_p_b_fg').color
// Old color: l_n_b_bg
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'ai').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'ai').color
property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'ai').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'ai').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'ai').color
property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'ai').color
}
property int padding: 8

View file

@ -0,0 +1,49 @@
pragma Singleton
import QtQml 2.2
import Units 1.0
import ColorsList 1.0
// =============================================================================
QtObject {
property string sectionName : 'ChatFilePreview'
property int height: 160
property QtObject filePreview: QtObject{
id: filePreviewObject
property int heightMargins: 60
property real format: 16/9
property string name: 'filePreview'
property string icon: 'menu_reply_custom'
property color backgroundColor: ColorsList.add(sectionName+'_'+name+'_bg', 'e').color
property color headerTextColor: ColorsList.add(sectionName+'_'+name+'_header_fg', 'i').color
property color iconColor: ColorsList.add(sectionName+'_'+name+'_header_fg', 'i').color
property color textColor: ColorsList.add(sectionName+'_'+name+'_fg', 'd').color
property int pointSize: Units.dp * 9
property int headerPointSize: Units.dp * 9
property QtObject removeButton: QtObject{
property int iconSize: 30
property string name : 'remove'
property string icon : 'close_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_b_n', icon, 's_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_b_h', icon, 's_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_b_p', icon, 's_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_f_n', icon, 's_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_f_h', icon, 's_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_f_p', icon, 's_p_b_fg').color
}
property QtObject closeButton: QtObject{
property int iconSize: 30
property string name : 'close'
property string icon : 'close_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_b_n', icon, 'l_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_b_h', icon, 'l_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_b_p', icon, 'l_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_f_n', icon, 'l_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_f_h', icon, 'l_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+filePreviewObject.name+'_'+name+'_f_p', icon, 'l_p_b_fg').color
}
}
}

View file

@ -10,6 +10,10 @@ QtObject {
property string sectionName : 'Chat'
property color color: ColorsList.add(sectionName, 'q').color
property string copyTextIcon : 'copy_custom'
property int rightButtonMargin: 15
property int rightButtonSize: 30
property int rightButtonLMargin: 10
property int separatorHeight: 2
property QtObject sectionHeading: QtObject {
property int padding: 5
@ -45,6 +49,10 @@ QtObject {
property color color: ColorsList.add(sectionName+'_send_border', 'f').color
property int width: 1
}
property QtObject backgroundBorder: QtObject {
property color color: ColorsList.add(sectionName+'_send_background_border', 'ag').color
property int width: 2
}
}
property QtObject composingText: QtObject {
@ -64,7 +72,7 @@ QtObject {
property int pointSize: Units.dp * 9
property int headerPointSize: Units.dp * 9
property QtObject closeButton: QtObject{
property int iconSize: 30
property int iconSize: rightButtonSize
property string name : 'close'
property string icon : 'close_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+replyPreviewObject.name+'_'+name+'_b_n', icon, 'l_n_b_bg').color
@ -94,6 +102,11 @@ QtObject {
property int lineHeight: 30
property int metaWidth: 40
property QtObject separator: QtObject {
property color color: ColorsList.add(sectionName+'_separator_border', 'g10').color
property int width: 2
}
property QtObject menu: QtObject {
property int iconSize: 22
property string name : 'menu'
@ -164,7 +177,7 @@ QtObject {
}
property QtObject message: QtObject {
property int padding: 10
property int padding: 8
property int radius: 4
property QtObject extraContent: QtObject {

View file

@ -12,6 +12,7 @@ singleton RequestBlockStyle 1.0 Blocks/RequestBlockStyle.qml
singleton ChatStyle 1.0 Chat/ChatStyle.qml
singleton ChatAudioMessageStyle 1.0 Chat/ChatAudioMessageStyle.qml
singleton ChatAudioPreviewStyle 1.0 Chat/ChatAudioPreviewStyle.qml
singleton ChatFilePreviewStyle 1.0 Chat/ChatFilePreviewStyle.qml
singleton ChatForwardMessageStyle 1.0 Chat/ChatForwardMessageStyle.qml
singleton ChatReplyMessageStyle 1.0 Chat/ChatReplyMessageStyle.qml

View file

@ -22,7 +22,7 @@ Rectangle {
property string _selectedSipAddress
property bool showHistoryButton : true
property bool updateSelectionModels : true
property bool isFilterVisible: searchView.visible || filterView.visible
property bool isFilterVisible: searchView.visible || showFilterView
// ---------------------------------------------------------------------------
@ -32,7 +32,7 @@ Rectangle {
signal showHistoryRequest()
// ---------------------------------------------------------------------------
property bool showFilterView : false
color: TimelineStyle.color
ColumnLayout {
@ -68,7 +68,7 @@ Rectangle {
id:showHistory
anchors.fill:parent
onClicked: {
filterView.visible = !filterView.visible
timeline.showFilterView = !timeline.showFilterView
}
}
RowLayout{
@ -99,7 +99,7 @@ Rectangle {
MouseArea{
anchors.fill:parent
onClicked:{
filterView.visible = !filterView.visible
timeline.showFilterView = !timeline.showFilterView
}
}
}
@ -146,13 +146,13 @@ Rectangle {
// Filter.
// -------------------------------------------------------------------------
Rectangle{
id:filterView
id:exhaustiveFilterView
Layout.fillWidth: true
Layout.preferredHeight: filterChoices.height
Layout.alignment: Qt.AlignCenter
border.color: TimelineStyle.filterField.borderColor
border.width: 2
visible:false
visible: timeline.showFilterView && !SettingsModel.useMinimalTimelineFilter
ColumnLayout{
id:filterChoices
@ -234,6 +234,75 @@ Rectangle {
}
}
}
Rectangle{
id:minimalFilterView
Layout.fillWidth: true
Layout.preferredHeight: minimalFilterChoices.height
Layout.alignment: Qt.AlignCenter
border.color: TimelineStyle.filterField.borderColor
border.width: 2
visible: timeline.showFilterView && SettingsModel.useMinimalTimelineFilter
ColumnLayout{
id:minimalFilterChoices
anchors.leftMargin: 20
anchors.left:parent.left
anchors.right:parent.right
spacing:-4
function getFilterFlags(){
return securedCheckBox.getValue() | groupCheckBox.getValue() | conferenceCheckBox.getValue();
}
CheckBoxText {
id: securedCheckBox
Layout.fillWidth: true
visible: SettingsModel.secureChatEnabled && SettingsModel.standardChatEnabled
//: 'Secure rooms' : Filter item. Selecting it will show all secure rooms.
text: qsTr('timelineFilterSecureRooms')
onClicked: {
timeline.model.filterFlags = minimalFilterChoices.getFilterFlags()
}
function getValue(){
if( checked)
return TimelineProxyModel.SecureChatRoom
else
return 0
}
}
CheckBoxText {
id: groupCheckBox
Layout.fillWidth: true
visible: SettingsModel.secureChatEnabled || SettingsModel.standardChatEnabled
//: 'Chat groups' : Filter item. Selecting it will show all chat groups (with more than one participant).
text: qsTr('timelineFilterChatGroups')
onClicked: {
timeline.model.filterFlags = minimalFilterChoices.getFilterFlags()
}
function getValue(){
if( checked)
return TimelineProxyModel.GroupChatRoom
else
return 0
}
}
CheckBoxText {
id: conferenceCheckBox
Layout.fillWidth: true
visible: false
//: 'Conferences' : Filter item. Selecting it will show all conferences.
text: qsTr('timelineFilterConferences')
onClicked: {
timeline.model.filterFlags = minimalFilterChoices.getFilterFlags()
}
function getValue(){
return 0
}
}
}
}
// -------------------------------------------------------------------------
// Search.
// -------------------------------------------------------------------------

View file

@ -30,6 +30,8 @@ ContactDescription 1.0 Contact/ContactDescription.qml
SipAddressDialog 1.0 Dialog/SipAddressDialog.qml
FileView 1.0 File/FileView.qml
History 1.0 History/History.qml
SipAddressesMenu 1.0 Menus/SipAddressesMenu.qml

View file

@ -204,11 +204,9 @@ Rectangle {
}
}
TooltipArea {
text: !incall.call.recording
tooltipText: !incall.call.recording
? qsTr('startRecordingLabel')
: qsTr('stopRecordingLabel')
}
}
ActionButton {

View file

@ -266,11 +266,9 @@ Window {
? call.startRecording()
: call.stopRecording()
TooltipArea {
text: !recordingSwitch.recording
tooltipText: !recordingSwitch.recording
? qsTr('startRecordingLabel')
: qsTr('stopRecordingLabel')
}
}
ActionButton {
@ -373,7 +371,7 @@ Window {
ActionButton {
isCustom: true
backgroundRadius: 90
colorSet: CallFullscreenStyle.buttons.cameraOn
colorSet: call && call.videoEnabled ? CallStyle.buttons.cameraOn : CallStyle.buttons.cameraOff
updating: call && call.updating
iconSize: CallFullscreenStyle.actionArea.iconSize

View file

@ -69,25 +69,25 @@ ColumnLayout {
text: qsTr('homeDescription')
}
CheckBoxText{
id: cguCheckBox
Layout.topMargin: 10
Layout.maximumWidth: infoItem.width
Layout.alignment: Qt.AlignHCenter
visible: applicationVendor != '' && ConstantsCpp.CguUrl != '' && ConstantsCpp.PrivatePolicyUrl != ''
checked: SettingsModel.cguAccepted
onCheckedChanged: SettingsModel.cguAccepted = checked
//: 'I accept %1's %2terms of use%3 and %4privacy policy%5' : where %1 is the vendor name and other %n are internal keywords that encapsulate links.
text: qsTr('homeCgu').arg(applicationVendor).arg('< a href="'+ConstantsCpp.CguUrl+'">').arg('</a>').arg('<a href="'+ConstantsCpp.PrivatePolicyUrl+'">').arg('</a>')
}
}
}
// ---------------------------------------------------------------------------
// Buttons.
// ---------------------------------------------------------------------------
CheckBoxText{
id: cguCheckBox
Layout.bottomMargin: 10
Layout.maximumWidth: infoItem.width
Layout.alignment: Qt.AlignHCenter
visible: applicationVendor != '' && ConstantsCpp.CguUrl != '' && ConstantsCpp.PrivatePolicyUrl != ''
checked: SettingsModel.cguAccepted
onCheckedChanged: SettingsModel.cguAccepted = checked
//: 'I accept %1's %2terms of use%3 and %4privacy policy%5' : where %1 is the vendor name and other %n are internal keywords that encapsulate links.
text: qsTr('homeCgu').arg(applicationVendor).arg('< a href="'+ConstantsCpp.CguUrl+'">').arg('</a>').arg('<a href="'+ConstantsCpp.PrivatePolicyUrl+'">').arg('</a>')
}
GridView {
id: buttons

View file

@ -492,7 +492,7 @@ ColumnLayout {
anchors.leftMargin: 50
anchors.topMargin: 10
anchors.bottomMargin: 10
visible: false
visible: true
TextField {
id:searchBar
@ -503,14 +503,13 @@ ColumnLayout {
width: parent.width-14
icon: 'close_custom'
overwriteColor: ConversationStyle.filters.iconColor
persistentIcon: true
showWhenEmpty: false
//: 'Search in messages' : this is a placeholder when searching something in the timeline list
placeholderText: qsTr('searchMessagesPlaceholder')
onTextChanged: searchDelay.restart()
onIconClicked: {
searchView.visible = false
chatRoomProxyModel.filterText = ''
searchView.text = ''
}
font.pointSize: ConversationStyle.filters.pointSize

View file

@ -222,6 +222,20 @@ TabContainer {
}
}
}
FormGroup {
//: 'Minimal Timeline filter'
label: qsTr('minimalTimelineFilterLabel')
Switch {
checked: SettingsModel.useMinimalTimelineFilter
onClicked: SettingsModel.useMinimalTimelineFilter = !checked
TooltipArea{
//: 'Show a minimal version of what to display in timeline.' :
text: qsTr('minimalTimelineFilterTooltip')
}
}
}
}
FormLine {
maxItemWidth: parent.width

View file

@ -113,6 +113,15 @@ QtObject {
property color foregroundDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_d', icon, 'me_d_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'me_n_b_inv_bg').color
property color backgroundHiddenPartDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_d', icon, 'me_d_b_inv_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'me_h_b_inv_bg').color
property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'me_p_b_inv_bg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'me_n_b_inv_fg').color
property color foregroundHiddenPartDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_d', icon, 'me_d_b_inv_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'me_h_b_inv_fg').color
property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'me_p_b_inv_fg').color
}
property QtObject telKeyad: QtObject {
property int iconSize: 16

View file

@ -113,6 +113,15 @@ QtObject {
property color foregroundDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_d', icon, 'me_d_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'me_n_b_inv_bg').color
property color backgroundHiddenPartDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_d', icon, 'me_d_b_inv_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'me_h_b_inv_bg').color
property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'me_p_b_inv_bg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'me_n_b_inv_fg').color
property color foregroundHiddenPartDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_d', icon, 'me_d_b_inv_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'me_h_b_inv_fg').color
property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'me_p_b_inv_fg').color
}
property QtObject telKeyad: QtObject {
property int iconSize: 16

@ -1 +1 @@
Subproject commit b2a3ebaffde438fb23c7b7d91327a35fa3a0f23d
Subproject commit 18cd2436eda9cb72023c4727691e4a2b764966d3