~nicohman/signal-rs

5dbe83b96bf14f9172599c62133e58f9c73d866d — nicohman 7 months ago 470875a
Add avatar editing
M Cargo.lock => Cargo.lock +12 -63
@@ 21,7 21,7 @@ version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
dependencies = [
 "memchr 2.3.4",
 "memchr",
]

[[package]]


@@ 30,7 30,7 @@ version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
 "memchr 2.3.4",
 "memchr",
]

[[package]]


@@ 227,7 227,7 @@ version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4369b5e4c0cddf64ad8981c0111e7df4f7078f4d6ba98fb31f2e17c4c57b7e"
dependencies = [
 "memchr 2.3.4",
 "memchr",
]

[[package]]


@@ 271,15 271,6 @@ checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"

[[package]]
name = "cpp"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d1cd8699ffa1b18fd388183f7762e0545eddbd5c6ec95e9e3b42a4a71a507ff"
dependencies = [
 "cpp_macros 0.4.0",
]

[[package]]
name = "cpp"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4875a08600be48dcc9cb6ee07f104a3e0752e95184dede6a30044d6480bf50e8"


@@ 289,20 280,6 @@ dependencies = [

[[package]]
name = "cpp_build"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c47531e7e09532ad4827098729794f5e1a5b1c2ccbb5e295498d2e7ab451c445"
dependencies = [
 "cc",
 "cpp_common 0.4.0",
 "cpp_syn",
 "cpp_synmap",
 "cpp_synom",
 "lazy_static",
]

[[package]]
name = "cpp_build"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "762705b71f4a8c5b65148de0e76bf18770c724ca2759f04ca29be9d508e1230d"


@@ 340,15 317,6 @@ dependencies = [
]

[[package]]
name = "cpp_core"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ebd6ba9742a158232afe2d07ec5d9d5d80d058baf700c5f9aa0e014fe3f24ad"
dependencies = [
 "libc",
]

[[package]]
name = "cpp_macros"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 391,17 359,6 @@ dependencies = [
]

[[package]]
name = "cpp_synmap"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897e4f9cdbe2874edd3ffe53718ee5d8b89e2a970057b2c93d3214104f2e90b6"
dependencies = [
 "cpp_syn",
 "cpp_synom",
 "memchr 1.0.2",
]

[[package]]
name = "cpp_synom"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 882,7 839,7 @@ dependencies = [
 "futures-macro",
 "futures-sink",
 "futures-task",
 "memchr 2.3.4",
 "memchr",
 "pin-project-lite 0.2.4",
 "pin-utils",
 "proc-macro-hack",


@@ 1343,15 1300,6 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"

[[package]]
name = "memchr"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
dependencies = [
 "libc",
]

[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"


@@ 1950,8 1898,8 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eab8585ec73d20ad37b118e613806c15008172152d291939c0959ede82a866"
dependencies = [
 "cpp 0.5.6",
 "cpp_build 0.5.6",
 "cpp",
 "cpp_build",
 "lazy_static",
 "qmetaobject_impl",
]


@@ 2158,7 2106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [
 "aho-corasick 0.7.15",
 "memchr 2.3.4",
 "memchr",
 "regex-syntax 0.6.22",
 "thread_local",
]


@@ 2408,9 2356,10 @@ dependencies = [
 "anyhow",
 "base64 0.13.0",
 "confy",
 "cpp 0.4.0",
 "cpp_build 0.4.0",
 "cpp_core",
 "cpp",
 "cpp_build",
 "cpp_macros 0.4.0",
 "cpp_macros 0.5.6",
 "crossbeam-channel 0.4.4",
 "cstr",
 "dbus",


@@ 2745,7 2694,7 @@ dependencies = [
 "iovec",
 "lazy_static",
 "libc",
 "memchr 2.3.4",
 "memchr",
 "mio 0.6.23",
 "mio-named-pipes",
 "mio-uds",

M Cargo.toml => Cargo.toml +4 -3
@@ 9,7 9,7 @@ build = "src/build.rs"
qmetaobject = "0.1.4"
gettext-rs = "0.4"
cstr = "0.1.0"
cpp = "0.4"
cpp = "0.5"
tungstenite = "0.11.0"
url = "2.1.0"
notify-rust = "4.2.2"


@@ 34,9 34,10 @@ home = "0.5.3"
pallet = "0.6.1"
anyhow  = "1.0"
tempfile = "3.1.0"
cpp_core = "0.6.0"
base64 = "0.13.0"
cpp_macros = "0.4"
[patch.crates-io]
pallet = { path = "./pallet" }
[build-dependencies]
cpp_build = "0.4"
cpp_build = "0.5"
cpp_macros = "0.5"

M qml/Avatar.qml => qml/Avatar.qml +32 -1
@@ 1,10 1,35 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtGraphicalEffects 1.15
import QtQuick.Dialogs 1.3
Item {
	id: avatarItem
	property string tel:""
	property bool rounded: true
	property bool editable: false
	FileDialog {
		id: fd
		title: "Pick a new avatar"
		selectExisting: true
		selectFolder: false
		selectMultiple: false
		onAccepted: {
			console.log(fd.fileUrl);
			var regex = new RegExp("file://");
			var path = fd.fileUrl.toString().replace("file://", "");
			console.log(path);
			console.log(avatarItem.tel);
			var newAvatar = signal.set_avatar(avatarItem.tel, path);
			console.log(newAvatar);
			//avatarImg.cache = false;
			avatarImg.source = "";
			avatarImg.source = `file://${newAvatar}`;	
			avatarImg.sourceChanged(avatarImg.source);
			//avatarImg.cache = true;
		}
	}
	Image {
		cache: false
		anchors.fill: parent
		id: avatarImg
		Component.onCompleted: {


@@ 30,6 55,12 @@ Item {
				}
			}
		}
		MouseArea {
			anchors.fill: parent
		onClicked: {
			fd.visible = true;
		}
	}
	}
	Rectangle {
		id:avatarRect


@@ 39,7 70,7 @@ Item {
	    visible:false
	    Label {
	        color: theme.text
	        text: new String(model.name)[0]
	        //text: new String(model.name)[0]
	        anchors.verticalCenter: parent.verticalCenter
	        anchors.horizontalCenter: parent.horizontalCenter
	    }

M qml/ChatList.qml => qml/ChatList.qml +5 -2
@@ 1,6 1,8 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.15

Page {
    anchors.fill: stackView
    title: qsTr("Chats")


@@ 9,11 11,11 @@ Page {
    background:Rectangle {
        color: theme.background
    }

    ListView {
        id: chatView
        x: 0
        y: 0
        anchors.fill: parent
        anchors.topMargin: 15
        delegate: ItemDelegate {
            x: 5
            width: chatView.width


@@ 45,4 47,5 @@ Page {
        }
        model: signal.chats
    }

}

M qml/CreateContact.qml => qml/CreateContact.qml +42 -28
@@ 7,39 7,53 @@ Page {
    background: Rectangle {
        color: theme.background
    }
    title: "Create New Contact"
    title: "Create a New Contact"

    ColumnLayout {
        anchors.leftMargin: 10
        anchors.fill: parent
        Row {
            Label {
                font.pixelSize: Qt.application.font.pixelSize * 1.3
                text: "Create a new contact"
                height: 20
            }
        x: 20
        y: 10
        //anchors.fill: parent
        anchors.right: parent.right
        anchors.left: parent.left
        spacing: 10
        Label {
            Layout.alignment: Qt.AlignHCenter | Qt.AlignTop 

            font.pixelSize: Qt.application.font.pixelSize * 1.3
            text: "Contact Name"
            height: 20
        }
        TextField {
            Layout.alignment: Qt.AlignHCenter | Qt.AlignTop 
        	id: contactNameInput
        	placeholderText: "Contact Name"
        }
        Row {
            TextField {
            	id: contactNameInput
            	placeholderText: "Contact Name"
            }

        Label {
            Layout.alignment: Qt.AlignHCenter | Qt.AlignTop  
            font.pixelSize: Qt.application.font.pixelSize * 1.3
            text: "Contact Phone"
        }
        Row {
            TextField {
            	id:contactPhoneInput
            	placeholderText: "Contact Phone"
            }
        Label {
            Layout.alignment: Qt.AlignHCenter | Qt.AlignTop  
            font.pixelSize: Qt.application.font.pixelSize
            text: "(Must include + and country code)"
        }
        Row {
            Button {
            	id: addContactButton
            	text: "Add Contact"
            	onClicked: {
            		signal.add_contact(contactNameInput.text, contactPhoneInput.text);
            		stackView.pop();
            	}
            }
        TextField {
            Layout.alignment: Qt.AlignHCenter | Qt.AlignTop 
        	id:contactPhoneInput
        	placeholderText: "+1234567890"
        }

        Button {
            Layout.alignment: Qt.AlignHCenter | Qt.AlignTop 
        	id: addContactButton
        	text: "Add Contact"
        	onClicked: {
        		signal.add_contact(contactNameInput.text, contactPhoneInput.text);
        		stackView.pop();
        	}
        }
    
    }
}
\ No newline at end of file

M qml/EditContact.qml => qml/EditContact.qml +3 -1
@@ 18,7 18,8 @@ Page {
        editContact.contactName = signalState.currentName;
	}
	ColumnLayout {
		anchors.fill: parent
		anchors.left: parent.left
		anchors.right: parent.right
		Layout.fillWidth: true
		Layout.fillHeight: true
		Row {


@@ 27,6 28,7 @@ Page {
				width: 100
				height: 100
				tel: signalState.current
				editable: true
			}
		}
		Row {

M qml/Settings.qml => qml/Settings.qml +1 -1
@@ 29,7 29,7 @@ Page {
		    	onActivated: {
		    		theme.setTheme(textAt(index));
		    	}
		    	model: ['signal', 'system']
		    	model: ['Signal', 'System']
		    }
    	}
    }

M qml/SignalState.qml => qml/SignalState.qml +1 -1
@@ 15,7 15,7 @@ Item {
        switch (viewName) {
            case "chats":
                stackView.push(chatViewStack);
                signal.show_view("chats");
                //signal.show_view("chats");
                break;
            case "createContact":
                stackView.push("CreateContact.qml");

M qml/Theme.qml => qml/Theme.qml +3 -3
@@ 11,19 11,19 @@ Item {
	property color text:systemPalette.text
	property color button :systemPalette.button
	property color highlightedText: systemPalette.highlightedText
	property string current: 'signal'
	property string current: 'Signal'
	Component.onCompleted: {
		setTheme(current);
	}
	function setTheme(themeName) {
	switch(themeName) {
		case 'signal':
		case 'Signal':
			theme.background = "black";
			theme.message = "#2e2e2e";
			theme.highlightedText = "white";
			theme.outgoing = "#2c6bed";
			break;
		case 'system':
		case 'System':
			theme.background = systemPalette.window;
			theme.message = systemPalette.highlight;
			theme.outgoing = systemPalette.highlight;

M qml/main.qml => qml/main.qml +1 -1
@@ 46,8 46,8 @@ ApplicationWindow {
            font.pixelSize: Qt.application.font.pixelSize * 1.6
            onClicked: {
                if (stackView.depth > 1) {
                    signal.show_view(stackView.currentItem.objectName);
                    stackView.pop()
                    signal.show_view(stackView.currentItem.objectName);
                    signalState.currentView = stackView.currentItem.objectName;
                } else {
                    drawer.open()

M src/config.rs => src/config.rs +2 -2
@@ 20,12 20,12 @@ impl Default for Backend {
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Theme {
    Base,
    System,
    Signal,
}
impl Default for Theme {
    fn default() -> Self {
        Theme::Base
        Theme::System
    }
}
#[derive(Serialize, Deserialize, Clone, Debug)]

M src/main.rs => src/main.rs +21 -6
@@ 35,7 35,9 @@ extern crate pallet;
extern crate dbus_tokio;
extern crate home;
extern crate serde_json;

extern crate tempfile;
use std::io::Read;
extern crate tokio;
use std::ffi::CStr;
extern crate tungstenite;


@@ 72,6 74,7 @@ pub trait SignalBackend {
    fn new(res_sender: glib::Sender<SignalResponse>, config: Config) -> Self;
    fn groups(&self) -> HashMap<String, Group>;
    fn avatar(&self, id: &str) -> Option<String>;
    fn set_avatar(&self, id: String, data: &[u8]) -> String;
}
#[derive(QObject, Default)]
pub struct SignalUI {


@@ 98,6 101,7 @@ pub struct SignalUI {
    ready: qt_signal!(),
    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)),
}
impl SignalUI {


@@ 111,6 115,7 @@ impl SignalUI {
            .expect("Couldn't send RmMessage");
    }
    fn avatar(&self, id: String) -> QString {
        println!("{}", id);
        QString::from(
            self.state
                .as_ref()


@@ 120,6 125,14 @@ impl SignalUI {
                .unwrap_or_default(),
        )
    }
    fn set_avatar(&self, id: String, path: String) -> String {
        let mut data: Vec<u8> = vec![];
        std::fs::File::open(path)
            .unwrap()
            .read_to_end(&mut data)
            .unwrap();
        self.state.as_ref().unwrap().sbackend.set_avatar(id, &data)
    }
    fn contact(&self, tel: String) -> String {
        let fetched = self
            .state


@@ 193,7 206,7 @@ impl SignalUI {
        let config = Config {
            backend: Backend::SignalCLI,
            number: Some("+14254920949".to_string()),
            theme: Theme::Base,
            theme: Theme::System,
            scli: Some(SignalCLIConfig {
                username: "+14254920949".to_string(),
                data_dir: "data".to_string(),


@@ 474,16 487,18 @@ mod qrc;
#[tokio::main]
async fn main() -> Result<(), std::boxed::Box<dyn std::error::Error>> {
    init_gettext();
    /*unsafe {
    unsafe {
        cpp! { {
            #include <QtCore/QCoreApplication>
            #include <QtCore/QString>
        }}
        cpp!{[]{
        cpp! ([] {
            QCoreApplication::setApplicationName(QStringLiteral("signal-rs-ut.nicohman"));
        }}
    }*/
    QQuickStyle::set_style("Suru");
            QCoreApplication::setOrganizationDomain(QStringLiteral("nicohman.com"));
            QCoreApplication::setOrganizationName(QStringLiteral("signal-rs-ut.nicohman"));
        });
    }
    //QQuickStyle::set_style("Suru");
    qrc::load();
    qml_register_type::<SignalUI>(
        CStr::from_bytes_with_nul(b"SignalUI\0").unwrap(),

M src/scli.rs => src/scli.rs +15 -3
@@ 8,6 8,7 @@ use pallet::Document;
use pallet::Store;
use std::fs;
use std::io::BufRead;
use std::io::Write;
use std::path::*;
use std::process::Stdio;
use std::time::Duration;


@@ 18,6 19,12 @@ lazy_static! {
        .into_os_string()
        .into_string()
        .unwrap();
    static ref SCLI_AVATAR_DIRECTORY: String = home::home_dir()
        .unwrap()
        .join(".local/share/signal-cli/avatars/")
        .into_os_string()
        .into_string()
        .unwrap();
}
pub struct SCLISession {
    pub msg_store: Store<SignalMessage>,


@@ 353,17 360,22 @@ impl SignalBackend for SCLISession {
            "contact".to_string(),
            "group".to_string(),
        ];
        let av_dir = home::home_dir()
            .unwrap()
            .join(".local/share/signal-cli/avatars");
        let av_dir = PathBuf::from(SCLI_AVATAR_DIRECTORY.to_owned());
        for opt in opts {
            let now_path = av_dir.join(format!("{}-{}", opt, id));
            println!("{:?}", av_dir);
            if now_path.exists() {
                return Some(now_path.to_str().unwrap().to_string());
            }
        }
        None
    }
    fn set_avatar(&self, id: String, data: &[u8]) -> String {
        let path = PathBuf::from(SCLI_AVATAR_DIRECTORY.to_owned()).join(format!("contact-{}", id));
        let mut fd = fs::File::create(&path).expect("Couldn't create avatar file");
        fd.write_all(data).expect("Couldn't write to avatar file");
        return path.into_os_string().into_string().unwrap();
    }
    fn new(res_sender: glib::Sender<SignalResponse>, config: Config) -> SCLISession {
        let scli_config = config.scli.expect("No configured SCLI settings");
        let data_dir = scli_config.data_dir.clone().replace(