~nicohman/signal-rs

8d72ad30e0e3ebb149982a17f8b6f45fbbfd6325 — nicohman 7 months ago 0dda6ee
Add theme support and general improvements
A qml/Avatar.qml => qml/Avatar.qml +47 -0
@@ 0,0 1,47 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtGraphicalEffects 1.15
Item {
	property string tel:""
	property bool rounded: true
	Image {
		anchors.fill: parent
		id: avatarImg
		Component.onCompleted: {
	        var avatar = signal.avatar(parent.tel);
	        console.log("av"+avatar)
	        if (avatar !== "") {
	        	console.log("set avatar");
	        	avatarImg.source = `file://${avatar}`;
	        } else {
	        	avatarRect.visible = true;
	        }
		}
		layer.enabled: parent.rounded
		layer.effect: OpacityMask {
			maskSource: Item {
				width: avatarImg.width
				height: avatarImg.height
				Rectangle {
					anchors.centerIn: parent
					width: avatarImg.width
					height: avatarImg.height
					radius: avatarImg.width
				}
			}
		}
	}
	Rectangle {
		id:avatarRect
		anchors.fill: parent
	    color: "grey"
	    radius: 50
	    visible:false
	    Label {
	        color: theme.text
	        text: new String(model.name)[0]
	        anchors.verticalCenter: parent.verticalCenter
	        anchors.horizontalCenter: parent.horizontalCenter
	    }
	}
}
\ No newline at end of file

M qml/ChatHistory.qml => qml/ChatHistory.qml +6 -2
@@ 6,11 6,13 @@ Page {
    anchors.fill: stackView
    id: chatHistory
    objectName: "chatHistory"
    background:Rectangle {
        color: theme.background
    }
    title: signalState.currentName
    function sendMessage() {
        console.log("Sending "+ msgTextInput.text +" to "+signalState.current);
        if (signalState.currentChat.is_group) {
            console.log("is group");
            signal.send_group_message(signalState.current, msgTextInput.text);
        } else {
            signal.send_message(signalState.current, msgTextInput.text);


@@ 20,6 22,7 @@ Page {
    ColumnLayout {
        anchors.fill: parent
        ListView {
            Layout.topMargin: 10
            highlight: Rectangle {
                color: "transparent"
            }


@@ 29,8 32,9 @@ Page {
            id: chatView
            x: 0
            y: 0
            anchors.left: parent.left
            //anchors.left: parent.left
            Layout.fillWidth: true
            Layout.maximumHeight:window.height - window.header.height - sendButton.height - msgTextInput.height
            Layout.fillHeight: true
            Layout.rightMargin: 5
            delegate:

M qml/ChatList.qml => qml/ChatList.qml +7 -23
@@ 6,24 6,22 @@ Page {
    title: qsTr("Chats")
    objectName: "chats"
    property string avatar: ""
    background:Rectangle {
        color: theme.background
    }
    ListView {
        id: chatView
        x: 0
        y: 0
        anchors.fill: parent
        delegate: ItemDelegate {
            Component.onCompleted:{
                var avatar = signal.avatar(tel);
                if (avatar != "") {
                    Qt.createQmlObject(`import QtQuick 2.15; Image { width: 40\nheight:40\nsource:"file://${avatar}"}`, imgPlace, "chatAvatar");
                }
            }
            x: 5
            width: chatView.width
            height: 40
            onClicked: {
                console.log(model.tel);
                console.log(model);
                signalState.currentChat = model;
                console.log(tel);
                signalState.setCurrent(tel);
                signal.show_chat(tel);
                signalState.openView("chatHistory");


@@ 32,24 30,10 @@ Page {
                id: row1
                spacing: 10
                bottomPadding: 20
                Item {
                    id: imgPlace
                    width: 40
                    height: 40
                    visible: signal.avatar(tel) != ""
                }
                Rectangle {
                Avatar {
                    tel: model.tel
                    width: 40
                    height: 40
                    color: "grey"
                    radius: 50
                    visible:signal.avatar(tel) == ""
                    Label {
                        color: theme.text
                        text: new String(model.name)[0]
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.horizontalCenter: parent.horizontalCenter
                    }
                }
                Column {
                    Label {

M qml/EditContact.qml => qml/EditContact.qml +15 -0
@@ 9,19 9,34 @@ Page {
	property string contactName: ""
	property string contactTel: ""
	property string currentTel: ""
	background: Rectangle {
		color: theme.background
	}
	Component.onCompleted: {
		editContact.contactTel = signalState.current;
        editContact.currentTel = signalState.current;
        editContact.contactName = signalState.currentName;
	}
	ColumnLayout {
		Layout.fillWidth: true
		Layout.fillHeight: true
		Layout.alignment: Qt.AlignHCenter
		Row {
			Avatar {
				width: 100
				height: 100
				tel: signalState.current
			}
		}
		Row {
			Layout.alignment: Qt.AlignHCenter
			TextField {
				id: editContactName
				text: contactName
			}	
		}
		Row {
			Layout.alignment: Qt.AlignHCenter
			TextField {
				id: editContactTel
				text: contactTel

M qml/MessageUI.qml => qml/MessageUI.qml +35 -26
@@ 5,7 5,7 @@ import QtQuick.Layouts 1.15
ItemDelegate {
    id: message
    x: 5
    height: rectangle.height + 5
    height: rectangle.height + 10
    property string messagetext: "Lorem ipsum"
    property bool outgoing: true
    property var message: null


@@ 13,7 13,7 @@ ItemDelegate {
    property int oneLineWidth: textLabel.implicitWidth
    property bool oneLine: oneLineWidth <= maxDelegateWidth
    readonly property int maxDelegateWidth: 27 * 8
    width: parent.width
    width: parent ? parent.width : 0
    property ListModel attachmentModel: ListModel {
    }
    Component.onCompleted : {


@@ 28,10 28,9 @@ ItemDelegate {
        id: row
        x: 0
        y: 0
        Layout.topMargin: 0
        anchors.fill: parent
        anchors.bottomMargin: 0
        anchors.topMargin: 0
        anchors.horizontalCenterOffset: 0
        layoutDirection: message.message.outgoing ? Qt.RightToLeft : Qt.LeftToRight
        Layout.rightMargin: 5
        Rectangle {


@@ 47,9 46,7 @@ ItemDelegate {
                    id: msgRightClick
                    MenuItem {
                        onClicked: {
                            console.log(signalState.currentChat.tel);
                            signal.del_message(signalState.currentChat.tel, message.message.sent_at);
                            console.log("Delete")
                        }
                        text: "Delete"
                    }


@@ 60,9 57,9 @@ ItemDelegate {
                       message.maxDelegateWidth, Math.max(
                           message.oneLine ? message.oneLineWidth : textLabel.implicitWidth,
                           dateLabel.implicitWidth, attachmentList.implicitWidth, dynName.implicitWidth)) + 10
            height: textLabel.implicitHeight + textLabel.anchors.topMargin + 5
                    + dateLabel.implicitHeight + attachmentList.implicitHeight + dynName.implicitHeight + dynName.anchors.topMargin +attachmentList.anchors.topMargin
            color: theme.highlight//"#6393e0"
            height: textLabel.implicitHeight + textLabel.anchors.topMargin + 15
                    + dateLabel.implicitHeight + atRow.implicitHeight + dynName.implicitHeight + dynName.anchors.topMargin + attachmentList.anchors.topMargin + dateLabel.anchors.topMargin
            color: message.message.outgoing ? theme.outgoing : theme.message
            radius: 10
            anchors.top: parent.top
            anchors.topMargin: 10


@@ 74,7 71,6 @@ ItemDelegate {
                    Label {
                    	color: theme.highlightedText
                        font.pixelSize: Qt.application.font.pixelSize
                        //minimumPointSize: 10
                        font.bold: false
                        id: textLabel
                        width: message.oneLine ? textLabel.implicitWidth : message.maxDelegateWidth


@@ 82,21 78,34 @@ ItemDelegate {
                        wrapMode: Text.Wrap
                    }
                }
                ListView {
                	id:attachmentList
                    model: message.attachmentModel
                    delegate: Row {
                        id: atDel
                        Component.onCompleted: {
                            switch (CType) {
                                case 2:
                                    var obj = Qt.createQmlObject(`import QtQuick 2.15; Image { width: 350\nfillMode: Image.PreserveAspectFit\nsource: "file://${File}"}`,atDel,  "imageDel");
                                    rectangle.height += obj.implicitHeight;
                                    break;
                                default:
                                    var obj = Qt.createQmlObject('import QtQuick 2.15; Text { text: "Can\'t display"}', atDel, "defaultDel");
                                    rectangle.height += obj.implicitHeight;
                                    break;
                Row {
                    topPadding: {
                       return message.attachmentModel.count === 0 ? 0 : 5;
                    }
                    Layout.leftMargin: {
                       return message.attachmentModel.count === 0 ? 0 : 5;
                    }
                    id: atRow
                    ListView {
                    	id:attachmentList
                        model: message.attachmentModel
                        delegate: ItemDelegate {
                            id: atDel
                            Component.onCompleted: {
                                switch (CType) {
                                    case 2:
                                        var obj = Qt.createQmlObject(`import QtQuick 2.15; Image { width: 350\nfillMode: Image.PreserveAspectFit\nsource: "file://${File}"}`,atRow,  "imageDel");
                                        //atRow.Layout.minimumHeight += obj.implicitHeight;
                                        //atRow.forceLayout();
                                        if (obj.width > rectangle.width) {
                                            rectangle.width = obj.width + 10;
                                        }
                                        break;
                                    default:
                                        var obj = Qt.createQmlObject('import QtQuick 2.15; import QtQuick.Controls 2.5; Label { text: "Can\'t display attachment"}', atDel, "defaultDel");
                                        attachmentList.height += obj.implicitHeight;
                                        break;
                                }
                            }
                        }
                    }


@@ 107,7 116,7 @@ ItemDelegate {
                	Label {
                		color: theme.highlightedText
                		text: signalState.contact(message.message.source).Name || message.message.source
                		visible: signalState.currentChat.is_group
                		visible: signalState.currentChat && signalState.currentChat.is_group
                	}
                }
                Row {

A qml/Settings.qml => qml/Settings.qml +36 -0
@@ 0,0 1,36 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.15

Page {
	anchors.fill: stackView
    title: qsTr("Settings")
    background:Rectangle {
    	color: theme.background
    }
    ColumnLayout {
    	Layout.fillWidth: true
    	Row {
    		Layout.leftMargin: 10
    		Layout.topMargin: 20
    		Layout.alignment: Qt.AlignTop
    		Label {
    			font.pixelSize: Qt.application.font.pixelSize * 1.3
    			text: "Theme Selection"
    		}
    	}
    	Row {
    		Layout.leftMargin: 15
    		Layout.alignment: Qt.AlignTop
		    ComboBox {
		    	Component.onCompleted: {
		    		currentIndex = indexOfValue(theme.current);
		    	}
		    	onActivated: {
		    		theme.setTheme(textAt(index));
		    	}
		    	model: ['signal', 'system']
		    }
    	}
    }
}

D qml/Settings.ui.qml => qml/Settings.ui.qml +0 -13
@@ 1,13 0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.5

Page {
	anchors.fill: stackView

    title: qsTr("Signal Settings")

    Label {
        text: qsTr("Settings")
        anchors.centerIn: parent
    }
}

M qml/SignalState.qml => qml/SignalState.qml +5 -14
@@ 3,12 3,7 @@ import QtQuick.Controls 2.5

Item {
    id: signalState
    property var chats: {
        return {};
    }
    property ListModel chatModel: ListModel {}
    property var messages: ({})
    property ListModel messagesModel : ListModel {}
    property string current: ""
    property string currentName: ""
    property string currentView: ""


@@ 26,7 21,7 @@ Item {
                stackView.push("CreateContact.qml");
                break;
            case "settings":
                stackView.push("Settings.ui.qml");
                stackView.push("Settings.qml");
                break;
            case "chatHistory":
                stackView.push(messagesViewStack);


@@ 41,17 36,13 @@ Item {
    }
    function setCurrent(cur) {
        signalState.current = cur;
        console.log(signal.chats.rowCount());
        for (var i=0; i < signal.chats.rowCount(); i++) {
            if (chatModel[i] && chatModel[i].tel === cur) {
                signalState.currentName = signal.chats[i].name;
            var idx = signal.chats.index(i, 0);
            var str = signal.chats.data(idx, 2+256).toString();
            if (str == cur.toString()) {
                signalState.currentName = signal.chats.data(idx, 1+256).toString();
                break;
            }
        }
    }
    Connections {
        target: signal
        function onSignalResponse(response) {
        }
    }
}

M qml/Theme.qml => qml/Theme.qml +19 -3
@@ 2,18 2,34 @@ import QtQuick 2.12
import QtQuick.Controls 2.5
Item {
	id: theme
	property color window: systemPalette.window
	property color background: systemPalette.window
	property color message: systemPalette.highlight
	property color outgoing: systemPalette.highlight
	property color messageText: systemPalette.highlightedText
	property color highlight:systemPalette.highlight
	property color base:systemPalette.base
	property color text:systemPalette.text
	property color button :systemPalette.button
	property color highlightedText: systemPalette.highlightedText
	property string current: 'signal'
	Component.onCompleted: {
		setTheme(current);
	}
	function setTheme(themeName) {
	switch(themeName) {
		case 'signal':
			theme.window = "black";
			theme.highlight = "blue";
			theme.background = "black";
			theme.message = "#2e2e2e";
			theme.highlightedText = "white";
			theme.outgoing = "#2c6bed";
			break;
		case 'system':
			theme.background = systemPalette.window;
			theme.message = systemPalette.highlight;
			theme.outgoing = systemPalette.highlight;
			theme.messageText = systemPalette.highlightedText;
			theme.text =  systemPalette.text;
			theme.highlightedText = systemPalette.highlightedText;
	}
	}
}
\ No newline at end of file

M qml/loading.qml => qml/loading.qml +1 -1
@@ 6,7 6,7 @@ Page {
    objectName: "loadingpage"
    title: qsTr("Loading")
    background:Rectangle {
    	color: theme.window
    	color: theme.background
    }
    BusyIndicator {
    	running: true

M qml/main.qml => qml/main.qml +9 -4
@@ 9,7 9,7 @@ ApplicationWindow {
    height: 471
    visible: true
    title: signal.name
    color: theme.base
    color: theme.background
    Theme {
        id: theme
    }


@@ 24,21 24,22 @@ ApplicationWindow {
        id: signal
        objectName: "signal"
        Component.onCompleted: {
            theme.setTheme("signal");
            signal.init_state();
        }
        name: "signal"
        onSignalResponse: {
        }
        onReady: {
            stackView.replace(null, chatViewStack);
            toolButton.enabled = true;
        }
    }
    header: ToolBar {
        background: Rectangle {
            color: theme.base
            color: theme.background
        }
        contentHeight: toolButton.implicitHeight
        ToolButton {
            enabled: false
            id: toolButton
            text: stackView.depth > 1 ? "\u25C0" : "\u2630"
            font.pixelSize: Qt.application.font.pixelSize * 1.6


@@ 56,6 57,7 @@ ApplicationWindow {
            text: stackView.currentItem.title
            anchors.centerIn: parent
            font.pixelSize: Qt.application.font.pixelSize * 1.3
            color: theme.highlightedText
        }
        Item {
            anchors.right: parent.right


@@ 79,6 81,9 @@ ApplicationWindow {
        id: drawer
        width: window.width * 0.66
        height: window.height
        background: Rectangle {
            color: theme.background
        }
        Column {
            anchors.fill: parent
            ItemDelegate {

M src/main.rs => src/main.rs +10 -9
@@ 53,16 53,12 @@ use signal::*;
use std::collections::*;

mod config;
/// Contains the custom widgets
//mod widgets;
use config::*;
use std::cell::*;
use std::process::Command;
use std::rc::*;
//mod axolotl;
//use axolotl::*;
use std::env;
use std::path::PathBuf;
use std::process::Command;
use std::rc::*;
use std::result::Result;

use gettextrs::{bindtextdomain, textdomain};


@@ 85,7 81,8 @@ pub trait SignalBackend {
pub struct SignalUI {
    base: qt_base_class!(trait QObject),
    state: Option<SignalState<scli::SCLISession>>,
    name: qt_property!(QString),
    name: qt_property!(QString; NOTIFY name_changed),
    name_changed: qt_signal!(),
    init_state: qt_method!(fn(&mut self)),
    show_view: qt_method!(fn(&mut self, view: String)),
    signalResponse: qt_signal!(response: String),


@@ 206,7 203,10 @@ impl SignalUI {
                let ref mut slf = self_.borrow_mut();
                slf.state.as_mut().unwrap().process_response(res.clone());
                if let SignalResponse::ChatList(ref chats) = res {
                    slf.chats.replace(chats.into_iter().collect());
                    /*slf.chats.replace_with(|cur| {
                        chats.into_iter().collect()
                    });*/
                    slf.chats.borrow_mut().reset_data(chats.to_vec());
                    slf.chats_changed();
                }
                if let SignalResponse::AddHist(ref msg, new) = res {


@@ 430,7 430,7 @@ where
            .messages()
            .unwrap()
            .into_iter()
            .filter(|(id, msgs)| !constructed.contains(&id))
            .filter(|(id, _msgs)| !constructed.contains(&id))
            .enumerate()
        {
            if msgs.len() > 0 {


@@ 447,6 447,7 @@ where
            }
        }
        chats.sort_by_key(|c| -1 * c.timestamp);
        println!("{:?}", chats);
        self.res_sender
            .send(SignalResponse::ChatList(chats))
            .expect("Couldn't send ChatList");

M src/qrc.rs => src/qrc.rs +3 -2
@@ 5,11 5,12 @@ qrc!(qml_resources,
        "qml/ChatHistory.qml",
        "qml/ChatList.qml",
        "qml/loading.qml",
        "qml/Settings.ui.qml",
        "qml/Settings.qml",
        "qml/SignalState.qml",
        "qml/CreateContact.qml",
        "qml/EditContact.qml",
        "qml/Theme.qml"
        "qml/Theme.qml",
        "qml/Avatar.qml"
    },
);


M src/scli.rs => src/scli.rs +12 -6
@@ 84,12 84,14 @@ impl SCLISession {
            let stdout = child.stdout.take().unwrap();
            let mut reader = std::io::BufReader::new(stdout).lines();
            loop {
                let msg_line = reader.next();
                let parsed: SignalCLIContainer =
                    serde_json::from_str(&msg_line.unwrap().unwrap()).unwrap();
                line_sender
                    .send(parsed)
                    .expect("Couldn't send parsed line from scli daemon");
                let msg_line = reader.next().unwrap().unwrap();
                if let Some(parsed) = serde_json::from_str::<SignalCLIContainer>(&msg_line).ok() {
                    line_sender
                        .send(parsed)
                        .expect("Couldn't send parsed line from scli daemon");
                } else {
                    println!("Could not parsed SCLI message {}", msg_line);
                }
            }
        });
        let proxy = nonblock::Proxy::new(


@@ 143,8 145,12 @@ impl SCLISession {
                                let mut source = parsed.envelope.source.clone();
                                if parsed.envelope.source == config.username {
                                    outgoing = true;
                                    if let Some(ref group_info) = sm.group_info {
                                        //source = group_info.group_id.clone();
                                    } else {
                                    source = sm.destination.clone().unwrap();
                                }
                                }
                                let mut attachment = vec![];
                                if let Some(ats) = &sm.attachments {
                                    attachment = parse_attachments(ats);

M src/signal.rs => src/signal.rs +11 -4
@@ 131,7 131,8 @@ pub struct Chat {
    pub last: String,
    pub timestamp: i64,
    // pub when: String,
    //pub c_type: i32,
    // pub c_type: i32,
    // pub(crate) to prevent this field from being used by SimpleListItem
    #[serde(deserialize_with = "parse_messages")]
    pub(crate) messages: MessageVec,
    /*#[serde(deserialize_with = "parse_messages")]


@@ 203,6 204,7 @@ pub struct Group {
pub struct SentMessage {
    pub timestamp: u64,
    pub message: Option<String>,
    pub reaction: Option<SCLIReaction>,
    pub expires_in_seconds: u64,
    pub attachments: Option<Vec<SignalCLIAttachment>>,
    pub group_info: Option<GroupInfo>,


@@ 210,10 212,16 @@ pub struct SentMessage {
}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SCLIReaction {
    pub emoji: String,
    pub targetAuthor: String,
    pub isRemove: bool,
    pub targetSentTimestamp: i64,
}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GroupInfo {
    pub group_id: String,
    //pub members:
    //pub name:
}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]


@@ 315,7 323,6 @@ where
    D: Deserializer<'de>,
{
    let vec: Option<String> = Deserialize::deserialize(d).ok();
    println!("{:?}", vec);
    if let Some(vec) = vec {
        if let Some(vec) = serde_json::from_str::<Vec<Attachment>>(&vec).ok() {
            Ok(Some(vec))