~nicohman/signal-rs

1e1259e97821e82b2b5bb4150ee717bf74ddff5d — nicohman 7 months ago 323c129
Add EditGroup
M qml/ChatHistory.qml => qml/ChatHistory.qml +8 -1
@@ 14,7 14,14 @@ Kirigami.ScrollablePage {
        id: editContactAction
        icon.name: "document-edit"
        text: "Edit Chat"
        onTriggered: signalState.openView("editContact");
        onTriggered: {
            if (signalState.currentChat.is_group) {
                signalState.editGroup(signalState.currentChat.tel);
                signalState.openView("editGroup");
            } else {
                signalState.openView("editContact");
            }
        }
    }
    function sendMessage() {
        console.log("Sending "+ msgTextInput.text +" to "+signalState.current);

M qml/ChatList.qml => qml/ChatList.qml +1 -0
@@ 25,6 25,7 @@ Kirigami.ScrollablePage {
            height: 40
            onClicked: {
                console.log("ChatList clicked");
                console.log(tel);
                signalState.currentChat = model;
                signalState.setCurrent(tel);
                signal.show_chat(tel);

A qml/EditGroup.qml => qml/EditGroup.qml +49 -0
@@ 0,0 1,49 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.13 as Kirigami

Kirigami.Page {
	id: editGroup
	title: "Edit Group"
	property var currentGroup: signalState.currentGroup
	ColumnLayout {
		anchors.fill: parent
		Label {
			font.pixelSize: Qt.application.font.pixelSize * 1.6
			text: currentGroup.name
		}
		ListView {
			Layout.fillHeight: true
			Layout.fillWidth: true
			model: signalState.editGroupMembersModel
			delegate: ItemDelegate {
				height: egRow.height
				Row {
					Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter	
					id: egRow
					height: egLabel.height + egLabelId.height
					Column {
						Label {
							height: 20
							id: egLabel
							text: c_name
						}
						Label {
							height: 20
							id: egLabelId
							text: model.id
						}				
					}
					Button {
						id: editGroupRemoveButton
						icon.name: "delete"
						onClicked: {
							console.log("Deleting from group not supported");
						}
					}
				}
			}
		}
	}
}
\ No newline at end of file

M qml/MessageUI.qml => qml/MessageUI.qml +30 -19
@@ 5,7 5,7 @@ import org.kde.kirigami 2.13 as Kirigami
ItemDelegate {
    id: message
    x: 5
    height: rectangle.height + 10
    height: rectangle.height + Kirigami.Units.gridUnit + emojiListView.height //+ emojiListView.anchors.topMargin
    property string messagetext: "Lorem ipsum"
    property bool outgoing: true
    property var message: null


@@ 14,7 14,6 @@ ItemDelegate {
    property bool oneLine: oneLineWidth <= maxDelegateWidth
    readonly property int maxDelegateWidth: Math.min(27 * Kirigami.Units.gridUnit, chatHistory.width - (Kirigami.Units.gridUnit ) * 2)
    width: parent ? parent.width : 0
    //Layout.fillWidth: true
    property ListModel attachmentModel: ListModel {
    }
    property ListModel reactionModel : ListModel {


@@ 23,19 22,18 @@ ItemDelegate {
        if (message.message.attachment && message.message.attachment !== "[]") {
            var attachments  = JSON.parse(new String(message.message.attachment));
            if (attachments && attachments.length) {
            attachments.forEach(function(at) {
                message.attachmentModel.append(at);
            });
        }
                attachments.forEach(function(at) {
                    message.attachmentModel.append(at);
                });
            }
        }
        if (message.message.reactions && message.message.reactions !== "[]") {
            var reactions = JSON.parse(new String(message.message.reactions));
                        if (reactions && reactions.length) {

            reactions.forEach(function(r) {
                message.reactionModel.append(r);
            });
        }
            if (reactions && reactions.length) {
                reactions.forEach(function(r) {
                    message.reactionModel.append(r);
                });
            }
        }
    }
    Row {


@@ 72,7 70,7 @@ ItemDelegate {
                           textLabel.width,
                           dateLabel.implicitWidth, attachmentList.implicitWidth, dynName.implicitWidth) + 10
            height: textLabel.implicitHeight + textLabel.anchors.topMargin + 15
                    + dateLabel.implicitHeight + atRow.implicitHeight + dynName.implicitHeight + dynName.anchors.topMargin + attachmentList.anchors.topMargin + dateLabel.anchors.topMargin
                    + dateLabel.implicitHeight + atRow.implicitHeight + dynName.implicitHeight + dynName.anchors.topMargin + attachmentList.anchors.topMargin + dateLabel.anchors.topMargin 
            color:  Kirigami.Theme.alternateBackgroundColor
            radius: 10
            anchors.top: parent.top


@@ 168,17 166,30 @@ ItemDelegate {
                    }
                }
                Row {
                    Layout.topMargin: -10
                    leftPadding: 5
                    height: emojiListView.implicitHeight
                    ListView {
                        id: emojiListView
                        orientation: ListView.Horizontal
                        model: message.reactionModel
                        delegate: Rectangle {
                            radius: 50

                            Label {
                                text: emoji
                        delegate: ItemDelegate {
                            height:emojLab.implicitWidth
                            width: emojLab.implicitHeight
                             Rectangle {
                                anchors.fill: parent
                                Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
                                Kirigami.Theme.inherit: false
                                radius: 50
                                color: Kirigami.Theme.alternateBackgroundColor
                                Label { 
                                    anchors.verticalCenter: parent.verticalCenter
                                    anchors.horizontalCenter: parent.horizontalCenter
                                    id: emojLab
                                    text: emoji
                                }
                            }
                    }
                }
                    }
                }
            }

M qml/Settings.qml => qml/Settings.qml +1 -1
@@ 4,9 4,9 @@ import QtQuick.Layouts 1.15
import org.kde.kirigami 2.13 as Kirigami

Kirigami.Page {
	anchors.fill: stackView
    title: qsTr("Settings")
    ColumnLayout {
        anchors.fill: parent
    	Layout.fillWidth: true
    	Row {
    		Layout.leftMargin: 10

M qml/SignalState.qml => qml/SignalState.qml +25 -2
@@ 9,6 9,8 @@ Item {
    property string currentView: ""
    property var currentChat : {}
    property var headerItems: []
    property var editGroupMembersModel: ListModel {}
    property var currentGroup
    property bool desktopView: false
    function openView(viewName) {
        signalState.currentView = viewName;


@@ 21,7 23,7 @@ Item {
                window.pageStack.layers.push(Qt.resolvedUrl("CreateContact.qml"));
                break;
            case "settings":
                window.pageStack.push(Qt.resolvedUrl("Settings.qml"));
                window.pageStack.layers.push(Qt.resolvedUrl("Settings.qml"));
                break;
            case "chatHistory":
                if (!signalState.desktopView) {


@@ 31,14 33,35 @@ Item {
            case "editContact":
                window.pageStack.layers.push(Qt.resolvedUrl("EditContact.qml"));
                break;
            case "editGroup":
                window.pageStack.layers.push(editGroupStack);
                break;
            case "createChat":
                window.pageStack.push(createChatStack);
                window.pageStack.layers.push(createChatStack);
                break;
        }
    }
    function editGroup(groupId) {
        signalState.editGroupMembersModel.clear();
        let group = signalState.group(groupId);
        signalState.currentGroup = group;
        console.log(group.members);
        group.members.forEach((i) => {
            console.log(i);
            let name = signal.resolve_name(i);
            console.log(name);
            signalState.editGroupMembersModel.append({
                c_name: name.toString(),
                id: i
            });
        });
    }
    function contact(tel) {
        return JSON.parse(signal.contact(tel));
    }
    function group(group_id) {
        return JSON.parse(signal.group(group_id));
    }
    function setCurrent(cur) {
        signalState.current = cur;
        for (var i=0; i < signal.chats.rowCount(); i++) {

M qml/main.qml => qml/main.qml +4 -0
@@ 217,6 217,10 @@ Kirigami.ApplicationWindow {
        id: createChatStack
        CreateChat {}
    }
    Component {
        id: editGroupStack
        EditGroup {}
    }
}



M src/main.rs => src/main.rs +27 -8
@@ 91,12 91,32 @@ pub struct SignalUI {
    contact: qt_method!(fn(&self, tel: String) -> String),
    del_message: qt_method!(fn(&self, chat_id: String, sent_at: i64)),
    ready: qt_signal!(),
    group: qt_method!(fn(&self, chat_id: String) -> String),
    resolve_name: qt_method!(fn(&self, source: String) -> String),
    avatar: qt_method!(fn(&self, id: String) -> QString),
    set_avatar: qt_method!(fn(&self, id: String, path: String) -> String),
    send_group_message: qt_method!(fn(&self, chat_id: String, message: String, attachment: String)),
    remove_from_group: qt_method!(fn(&self, group_id: String, phone: String)),
}
impl SignalUI {
    fn remove_from_group(&self, group_id: String, phone: String) {
        self.send_req(SignalRequest::RemoveFromGroup {
            group_id,
            contact: phone,
        });
    }
    fn group(&self, chat_id: String) -> String {
        serde_json::to_string(
            &self
                .state
                .as_ref()
                .unwrap()
                .sbackend
                .group(&chat_id)
                .unwrap(),
        )
        .unwrap()
    }
    fn send_res(&self, resp: SignalResponse) {
        self.state
            .as_ref()


@@ 117,13 137,6 @@ impl SignalUI {
    }
    fn del_message(&self, chat_id: String, sent_at: i64) {
        self.send_res(SignalResponse::RmMessage { chat_id, sent_at });
        /*self.state
        .as_ref()
        .unwrap()
        .sbackend
        .res_sender
        .send()
        .expect("Couldn't send RmMessage");*/
    }
    fn avatar(&self, id: String) -> QString {
        QString::from(


@@ 454,9 467,11 @@ impl SignalState {
            .collect();
        for (i, (gid, group)) in self.sbackend.groups().into_iter().enumerate() {
            if let Some(msgs) = self.sbackend.messages_id(&gid).ok() {
                //println!("mgsgsgs{:?}", msgs);
                if msgs.len() > 0 {
                    let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap();
                    constructed.push(gid.clone());

                    chats.push(Chat {
                        ID: (chats.len() + i) as i32,
                        name: group.name,


@@ 479,6 494,9 @@ impl SignalState {
        {
            if msgs.len() > 0 {
                let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap();
                if latest.message.as_str() == "🧝\u{200d}♀\u{fe0f}" {
                    println!("{:?} - {}", latest, id);
                }
                chats.push(Chat {
                    ID: (chats.len() + i) as i32,
                    name: self.resolve_name(id.clone()),


@@ 506,14 524,15 @@ impl SignalState {
            .map(|(_k, v)| v)
            .collect();
        contacts.sort_by_key(|x| x.name.clone());
        println!("Updated contacts");
        self.res_sender
            .send(SignalResponse::ModelContactList(contacts))
            .expect("Couldn't send ContactList");
    }
    /// Shows a given chat by tel
    pub fn show_chat(&self, chat_id: String) {
        println!("{}", chat_id);
        if let Some(map) = self.sbackend.messages_id(&chat_id).ok() {
            println!("{:?}", map);
            self.res_sender
                .send(SignalResponse::HistoryMessage {
                    phone: chat_id,

M src/qrc.rs => src/qrc.rs +2 -1
@@ 13,7 13,8 @@ qrc!(qml_resources,
        "qml/Avatar.qml",
        "qml/CreateChat.qml",
        "qml/PictureOverlay.qml",
        "qml/MessageImage.qml"
        "qml/MessageImage.qml",
        "qml/EditGroup.qml"
    },
);


M src/scli.rs => src/scli.rs +38 -3
@@ 55,6 55,19 @@ impl SCLISession {
            )
            .expect("Couldn't merge chatids");
    }
    pub fn delete_all(&self, all: String) {
        let ids: Vec<u64> = self
            .msg_store
            .search(format!("chat_id:{}", all).as_str())
            .unwrap()
            .hits
            .into_iter()
            .map(|x| {
                return x.doc.id;
            })
            .collect();
        self.msg_store.delete_multi(&ids);
    }
    pub fn fetch_contacts_scli(username: &str) -> Vec<Contact> {
        let output = Command::new("signal-cli")
            .arg("--username")


@@ 91,9 104,16 @@ impl SCLISession {
            let mut child = cmd.spawn().expect("Couldn't run signal-cli");
            let stdout = child.stdout.take().unwrap();
            let mut reader = std::io::BufReader::new(stdout).lines();
            let mut log_file = fs::OpenOptions::new()
                .read(true)
                .append(true)
                .open("/home/nicohman/scli.log")
                .unwrap();
            loop {
                let msg_line = reader.next().unwrap().unwrap();
                println!("{}", msg_line);
                write!(log_file, "{}\n", msg_line).unwrap();
                log_file.flush();
                if let Some(parsed) = serde_json::from_str::<SignalCLIContainer>(&msg_line).ok() {
                    line_sender
                        .send(parsed)


@@ 198,6 218,7 @@ impl SCLISession {
                                chat_id,
                                reactions: vec![]
                            };
                            println!("{:?}",con_msg );
                            res_sender.send(SignalResponse::IDLessMessage(con_msg)).expect("Couldn't send IDLessMessage");
                        }
                    }


@@ 223,7 244,10 @@ impl SCLISession {
                                        members: group_members,
                                        id: parsed_id
                                    });

                                }
                                println!("{:?}", groups);
                                println!("Got groups");
                                res_sender.send(SignalResponse::Groups(groups)).expect("Couldn't send Groups");
                            }
                            SignalRequest::SendGroupMessage {chat_id, message, attachment} => {


@@ 283,8 307,13 @@ impl SCLISession {
                                })).expect("Couldnt send IDLessMessage");
                            },
                            SignalRequest::GetContactName { chat_id } => {
                                let (name,): (String,) = proxy.method_call("org.asamk.Signal", "getContactName", (chat_id.clone(), )).await.unwrap();
                                res_sender.send(SignalResponse::ContactList(vec![Contact {name, tel: chat_id}])).expect("Couldn't send ContactList");
                                println!("{}", chat_id);
                                let result = proxy.method_call("org.asamk.Signal", "getContactName", (chat_id.clone(), )).await;
                                if result.is_ok() {
                                    let (name,): (String,) = result.unwrap();
                                    res_sender.send(SignalResponse::ContactList(vec![Contact {name, tel: chat_id}])).expect("Couldn't send ContactList");

                                }
                            },
                            SignalRequest::AddContact{ name, phone} => {
                                res_sender.send(SignalResponse::ContactList(vec![Contact {name, tel: phone}])).expect("Couldn't send ContactList");


@@ 443,6 472,7 @@ impl SCLISession {
        contact_store
            .index_all()
            .expect("Couldn't index contact store");
        println!("{:?}", msg_store.all());
        let proxy = nonblock::Proxy::new(
            "org.asamk.Signal",
            "/org/asamk/Signal",


@@ 459,12 489,13 @@ impl SCLISession {
            resolved_names: HashMap::new(),
            req_sender,
        };
        session.delete_all("EUSVmOeIH96m3CNb47OIx1rRp0jWpkHYeTciI28cuN0=".to_string());
        session
    }
    pub fn messages_id<'a>(&'a self, chat_id: &str) -> Result<BTreeMap<i64, SignalMessage>> {
        let messages = self
            .msg_store
            .search(format!("chat_id:{}", chat_id).as_str())?
            .search(format!("+chat_id:\"{}\"", chat_id).as_str())?
            .hits;
        Ok(messages
            .into_iter()


@@ 492,6 523,7 @@ impl SCLISession {
                })
                .expect("Couldn't send GetContactName");
            self.resolved_names.insert(chat_id.to_string(), None);
            println!("{}", chat_id);
            return Ok(chat_id.to_string());
        }
    }


@@ 538,6 570,9 @@ impl SCLISession {
    pub fn groups<'a>(&'a self) -> HashMap<String, Group> {
        self.groups.clone()
    }
    pub fn group(&self, chat_id: &str) -> Option<Group> {
        self.groups().get(chat_id).cloned()
    }
    pub fn contacts<'a>(&'a self) -> HashMap<String, Contact> {
        let map: HashMap<String, Contact> = self
            .contact_store

M src/signal.rs => src/signal.rs +19 -2
@@ 26,6 26,19 @@ pub mod signal_cli {
        pub is_delivery: Option<bool>,
        pub data_message: Option<DataMessage>,
        pub sync_message: Option<SyncMessage>,
        pub typing_message: Option<TypingMessage>,
    }
    #[serde(rename_all = "UPPERCASE")]
    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub enum TypingType {
        Started,
        Stopped,
    }
    #[serde(rename_all = "camelCase")]
    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct TypingMessage {
        pub action: TypingType,
        pub timestamp: i64,
    }
    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct SignalCLIContactInfo {


@@ 147,6 160,10 @@ pub enum SignalRequest {
        id: String,
    },
    GetGroups,
    RemoveFromGroup {
        group_id: String,
        contact: String,
    },
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Reaction {


@@ 237,13 254,13 @@ where
    Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or(vec![]))
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Group {
    pub name: String,
    pub members: Vec<String>,
    pub id: String,
}

impl QMetaType for Group {}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GroupInfo {