Merge refactor branch (#173)
|
@ -2,6 +2,5 @@ _ignore
|
|||
build
|
||||
build.sh
|
||||
build-po.sh
|
||||
install.sh
|
||||
uninstall.sh
|
||||
*~
|
||||
|
|
32
README.md
|
@ -1,14 +1,17 @@
|
|||
![Tootle](https://user-images.githubusercontent.com/37731582/39933812-45d8149a-5544-11e8-9bf4-6d78b1fdb29c.png)
|
||||
Simple [Mastodon](https://github.com/tootsuite/mastodon) client designed for elementary OS.
|
||||
![Tootle](https://raw.githubusercontent.com/bleakgrey/tootle/master/data/icons/color.svg)
|
||||
Simple [Mastodon](https://github.com/tootsuite/mastodon) client for Linux
|
||||
|
||||
![Tootle Screenshot](https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot.png)
|
||||
![Screenshot](https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot.png)
|
||||
|
||||
## Building and Installation
|
||||
## Installation
|
||||
This project is undergoing a major rewrite and will be published in the near future.
|
||||
|
||||
To help the project, please build it manually and help test it.
|
||||
|
||||
[![Get it on AppCenter](https://appcenter.elementary.io/badge.svg)](https://appcenter.elementary.io/com.github.bleakgrey.tootle)
|
||||
<a href='https://flathub.org/apps/details/com.github.bleakgrey.tootle'><img height='51' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
|
||||
|
||||
First of all you'll need some dependencies to build and run the app:
|
||||
## Building
|
||||
To build the app, make sure you have these dependencies:
|
||||
* meson
|
||||
* valac
|
||||
* libgtk-3-dev
|
||||
|
@ -16,26 +19,19 @@ First of all you'll need some dependencies to build and run the app:
|
|||
* libgranite-dev
|
||||
* libjson-glib-dev
|
||||
|
||||
Then run these commands to build and install it:
|
||||
|
||||
meson build --prefix=/usr
|
||||
cd build
|
||||
sudo ninja install
|
||||
com.github.bleakgrey.tootle
|
||||
Then run 'install.sh' in the project directory to install the app.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you feel like contributing, you're always welcome to help the project in many ways:
|
||||
* Reporting any issues
|
||||
* Suggesting ideas and functionality
|
||||
You're always welcome to help the project in many ways:
|
||||
* Donating with [LiberaPay](https://liberapay.com/bleakgrey/) to keep the developer happy and motivated
|
||||
* Reporting issues and bugs
|
||||
* Submitting pull requests
|
||||
* Donating with [LiberaPay](https://liberapay.com/bleakgrey/) to help project development and keeping the developer happy
|
||||
|
||||
<a href="https://liberapay.com/bleakgrey/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||||
|
||||
## Credits
|
||||
* Tootle Logo by [@CallMeFib3r](https://github.com/CallMeFib3r)
|
||||
* Medel typeface by Ozan Karakoc
|
||||
* Icon design by [Tobias Bernard](https://github.com/bertob)
|
||||
* French translation by [@Larnicone](https://github.com/Larnicone)
|
||||
* Polish translation by [@m4sk1n](https://github.com/m4sk1n)
|
||||
* German translation by [@koyuawsmbrtn](https://github.com/koyuawsmbrtn)
|
||||
|
|
37
data/app.css
|
@ -1,35 +1,12 @@
|
|||
.titlebar.compact {
|
||||
padding: 0 6px;
|
||||
.avatar {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mode .toggle{
|
||||
border-radius:0px;
|
||||
border-top:none;
|
||||
border-bottom:none;
|
||||
padding:10px;
|
||||
margin:0px;
|
||||
.attachment {
|
||||
border-radius: 4px;
|
||||
background: rgba (150, 150, 150, 0.2);
|
||||
}
|
||||
|
||||
.button_avatar{
|
||||
padding:0;
|
||||
border:0;
|
||||
box-shadow:none;
|
||||
background:none;
|
||||
}
|
||||
|
||||
.toot-text, .toot-text text{
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.header{
|
||||
background-size: cover;
|
||||
background-position: 50%;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.relationship {
|
||||
background: rgba (0,0,0,.5);
|
||||
padding: 6px;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
.highlight {
|
||||
background: @theme_base_color;
|
||||
}
|
||||
|
|
|
@ -62,15 +62,6 @@
|
|||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot2.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot3.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot4.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/com/github/bleakgrey/tootle/">
|
||||
<file alias="app.css">app.css</file>
|
||||
<file alias="light.css">light.css</file>
|
||||
<file alias="dark.css">dark.css</file>
|
||||
<file alias="logo128">logo128.png</file>
|
||||
<file alias="empty_state">empty_state.png</file>
|
||||
</gresource>
|
||||
</gresources>
|
|
@ -16,16 +16,6 @@
|
|||
<summary>Always monitor new notifications</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key name="cache" type="b">
|
||||
<default>false</default>
|
||||
<summary>Cache images to reduce network load</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key name="cache-size" type="i">
|
||||
<default>64</default>
|
||||
<summary>Cache size</summary>
|
||||
<description>Sets the maximum size of cached content</description>
|
||||
</key>
|
||||
<key name="live-updates" type="b">
|
||||
<default>true</default>
|
||||
<summary>Real-time timelines</summary>
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
@define-color colorAccent #c92e34;
|
||||
@define-color colorPrimary #35393c;
|
||||
|
||||
.header-counters{
|
||||
background: rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
.attachment{
|
||||
background: rgba (255,255,255,.15);
|
||||
}
|
||||
|
||||
.card{
|
||||
background: rgba (255,255,255,.15);
|
||||
}
|
Before Width: | Height: | Size: 5.6 KiB |
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/com/github/bleakgrey/tootle/">
|
||||
<file alias="app.css">app.css</file>
|
||||
<file preprocess="xml-stripblanks">ui/views/new_account.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/views/base.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/views/profile_header.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/widgets/status.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/widgets/accounts_button.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/widgets/accounts_button_item.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/dialogs/compose.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/dialogs/main.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
|
@ -1,188 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg4379"
|
||||
sodipodi:docname="128x128.svg"
|
||||
inkscape:version="0.91 r13725"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="128px"
|
||||
height="128px"
|
||||
viewBox="0 0 128 128"
|
||||
enable-background="new 0 0 128 128"
|
||||
xml:space="preserve"><metadata
|
||||
id="metadata59"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs57" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="705"
|
||||
id="namedview55"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.84375"
|
||||
inkscape:cx="54.508475"
|
||||
inkscape:cy="64"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4379" /><radialGradient
|
||||
id="path3041_1_"
|
||||
cx="-292.7208"
|
||||
cy="1871.6008"
|
||||
r="35.3381"
|
||||
gradientTransform="matrix(1.5564 0 0 -0.1698 519.5884 435.7747)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#000000"
|
||||
id="stop4" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0"
|
||||
id="stop6" /></radialGradient><path
|
||||
id="path3041"
|
||||
opacity="0.2"
|
||||
fill="url(#path3041_1_)"
|
||||
enable-background="new "
|
||||
d="M119,118 c-0.006,3.314-24.635,5.999-55.01,5.999C33.622,123.998,9.006,121.313,9,118c-0.006-3.314,24.614-6.001,54.99-6.001 c30.376-0.001,55.005,2.685,55.01,5.999C119,117.999,119,117.999,119,118z" /><g
|
||||
id="g2036"
|
||||
transform="matrix(2.6999989,0,0,0.55555607,-0.8000019,94.888882)"><g
|
||||
id="g3712"
|
||||
transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)"
|
||||
opacity="0.4"><radialGradient
|
||||
id="rect2801_1_"
|
||||
cx="-136.6434"
|
||||
cy="5007.957"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(5.695 0 0 -1 816.1725 5051.457)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop12" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop14" /></radialGradient><rect
|
||||
id="rect2801"
|
||||
x="38"
|
||||
y="40"
|
||||
fill="url(#rect2801_1_)"
|
||||
width="5"
|
||||
height="7" /><radialGradient
|
||||
id="rect3696_1_"
|
||||
cx="-165.4541"
|
||||
cy="6006.0449"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(-5.695 0 0 1 -932.2481 -5962.5449)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop18" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop20" /></radialGradient><rect
|
||||
id="rect3696"
|
||||
x="5"
|
||||
y="40"
|
||||
fill="url(#rect3696_1_)"
|
||||
width="5"
|
||||
height="7" /><linearGradient
|
||||
id="rect3700_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-115.496"
|
||||
y1="4803.4004"
|
||||
x2="-115.496"
|
||||
y2="4810.4287"
|
||||
gradientTransform="matrix(2.8421 0 0 -0.7143 352.2516 3478.0276)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop24" /><stop
|
||||
offset="0.5"
|
||||
style="stop-color:#181818"
|
||||
id="stop26" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop28" /></linearGradient><rect
|
||||
id="rect3700"
|
||||
x="10"
|
||||
y="40"
|
||||
fill="url(#rect3700_1_)"
|
||||
width="28"
|
||||
height="7" /></g></g><radialGradient
|
||||
id="SVGID_1_"
|
||||
cx="-42.2709"
|
||||
cy="69.1949"
|
||||
r="322.7202"
|
||||
fx="-55.7745"
|
||||
fy="69.1949"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#9BAEC8"
|
||||
id="stop32" /><stop
|
||||
offset="0.4576"
|
||||
style="stop-color:#8093AA"
|
||||
id="stop34" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#273445"
|
||||
id="stop36" /></radialGradient><path
|
||||
fill="url(#SVGID_1_)"
|
||||
d="M115.5,21.55v90.9c0,3.34-2.71,6.05-6.05,6.05h-90.9c-3.34,0-6.05-2.71-6.05-6.05v-90.9 c0-3.34,2.71-6.05,6.05-6.05h90.9C112.79,15.5,115.5,18.21,115.5,21.55z"
|
||||
id="path38" /><linearGradient
|
||||
id="rect6741-7_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-329.085"
|
||||
y1="3929.1445"
|
||||
x2="-329.085"
|
||||
y2="3891.1445"
|
||||
gradientTransform="matrix(2.7297 0 0 -2.7297 962.313 10740.0137)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop41" /><stop
|
||||
offset="0.0632"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
||||
id="stop43" /><stop
|
||||
offset="0.9506"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
||||
id="stop45" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
||||
id="stop47" /></linearGradient><path
|
||||
id="rect6741-7"
|
||||
opacity="0.3"
|
||||
fill="none"
|
||||
stroke="url(#rect6741-7_1_)"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M18.5,15.875h91c2.761,0,5,2.239,5,5v91c0,2.761-2.239,5-5,5h-91c-2.761,0-5-2.239-5-5v-91C13.5,18.114,15.739,15.875,18.5,15.875z" /><path
|
||||
id="rect5505-21-6"
|
||||
opacity="0.5"
|
||||
fill="none"
|
||||
stroke="#0E141F"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M18.5,15.5h91c3.314,0,6,2.686,6,6v91c0,3.314-2.686,6-6,6h-91c-3.314,0-6-2.686-6-6v-91C12.5,18.186,15.186,15.5,18.5,15.5z" /><path
|
||||
d="M115.5,50.65v61.8c0,3.34-2.71,6.05-6.05,6.05H51.31L24.13,92.02l-0.08-0.08v-0.01 c-0.02-0.02-0.03-0.04-0.05-0.05l-5.5-10.54c-0.12-0.31,0.01-0.66,0.31-0.82c0.14-0.07,0.29-0.09,0.42-0.06c0.02,0,0.03,0,0.04,0.01 l5.02,1.09l1.18-0.62l0.89-0.46l12.63-6.6c0.08-0.04,0.17-0.09,0.25-0.13l6.18-3.22l-1-1.91c-0.41-0.79-0.29-1.72,0.23-2.38 l-3.46-3.34c-0.1-0.08-0.19-0.17-0.28-0.27c-0.17-0.19-0.31-0.39-0.43-0.62c-0.77-1.47-0.2-3.27,1.27-4.04l16.81-8.77 c1.11-0.58,2.42-0.39,3.32,0.38l0.26,0.25l8.04,7.77l4.36-2.27c0.08-0.05,0.17-0.09,0.25-0.14l12.83-6.7l6.47-15.51 c0.01-0.03,0.02-0.06,0.04-0.08c0.12-0.28,0.34-0.52,0.63-0.68c0.5-0.26,1.08-0.2,1.51,0.11L115.5,50.65z"
|
||||
id="path51"
|
||||
fill="#273445"
|
||||
opacity="0.5"
|
||||
style="opacity:0.15" /><path
|
||||
fill="#FFFFFF"
|
||||
d="M108.571,55.606l-11.964-22.92c-0.018-0.027-0.034-0.051-0.055-0.075c-0.082-0.106-0.178-0.199-0.284-0.275 c-0.426-0.311-1.007-0.375-1.507-0.114c-0.293,0.155-0.511,0.396-0.636,0.678c-0.018,0.025-0.026,0.054-0.037,0.083l-6.467,15.511 l-12.831,6.699c-0.084,0.044-0.171,0.088-0.251,0.134l-4.363,2.278l-3.801,1.984l-0.994-1.905c-0.564-1.079-1.89-1.496-2.969-0.932 c-1.077,0.562-1.496,1.89-0.932,2.969l0.994,1.905l-3.672,1.917l-3.172-6.075l5.693-2.971c1.461-0.765,2.029-2.571,1.266-4.032 c-0.123-0.238-0.274-0.45-0.449-0.638l-0.259-0.251c-0.899-0.769-2.213-0.958-3.323-0.378l-16.812,8.775 c-1.463,0.765-2.031,2.571-1.268,4.034c0.12,0.229,0.265,0.437,0.433,0.62c0.087,0.099,0.182,0.192,0.282,0.273 c0.899,0.764,2.208,0.951,3.318,0.371l5.691-2.969l3.17,6.075l-4.054,2.116l-0.994-1.905c-0.564-1.077-1.894-1.494-2.969-0.932 c-0.284,0.148-0.521,0.349-0.709,0.585c-0.52,0.66-0.639,1.589-0.225,2.384l0.994,1.905l-6.172,3.223 c-0.084,0.042-0.169,0.086-0.253,0.13l-14.7,7.674l-5.016-1.089c-0.014-0.004-0.027-0.006-0.038-0.008 c-0.139-0.027-0.289-0.006-0.424,0.063c-0.3,0.157-0.428,0.511-0.309,0.818l5.501,10.535c0.014,0.02,0.028,0.04,0.043,0.058 l0.007,0.009l0.08,0.075c0.195,0.162,0.476,0.198,0.718,0.073c0.135-0.072,0.234-0.181,0.293-0.312 c0.007-0.011,0.011-0.023,0.015-0.036l2.014-4.828l5.884-3.073c0.023,1.672,0.427,3.363,1.253,4.946 c2.823,5.406,9.497,7.502,14.906,4.679c0.084-0.044,0.169-0.088,0.251-0.134l6.172-3.223l1.154,2.211 c0.562,1.077,1.894,1.494,2.969,0.932c1.077-0.562,1.496-1.892,0.934-2.969l-1.154-2.209l4.054-2.118l3.367,6.448 c0.784,1.501,2.632,2.082,4.133,1.298c0.42-0.217,0.768-0.521,1.029-0.875c0.688-0.914,0.831-2.177,0.267-3.258l-3.364-6.448 l3.672-1.917l1.154,2.209c0.562,1.077,1.892,1.496,2.969,0.934c1.079-0.564,1.494-1.894,0.932-2.969l-1.154-2.211l8.517-4.446 c0.588-0.314,1.135-0.675,1.642-1.075c4.085-3.225,5.435-8.995,2.94-13.776c-0.825-1.581-1.982-2.879-3.34-3.855l4.014-2.095 l16.518,3.58c0.031,0.008,0.059,0.017,0.088,0.019c0.305,0.058,0.627,0.019,0.92-0.136C108.55,57.044,108.829,56.275,108.571,55.606 z M46.51,88.011c-0.038,0.023-0.073,0.042-0.113,0.062c-2.431,1.269-5.427,0.329-6.697-2.102c-1.267-2.429-0.329-5.425,2.098-6.694 l6.432-3.357l4.592,8.797L46.51,88.011z M56.724,82.679l-4.592-8.797l0.269-0.139l3.786-1.977l4.592,8.797l-0.864,0.451 L56.724,82.679z M66.209,77.729l-4.592-8.797l3.672-1.917l4.592,8.797L66.209,77.729z M81.444,69.766 c-0.053,0.034-0.109,0.067-0.164,0.095l-7.498,3.915l-4.592-8.797l5.591-2.92l1.792-0.934c0.095-0.049,0.191-0.095,0.289-0.139 c2.374-1.056,5.189-0.094,6.408,2.241c1.075,2.056,0.567,4.516-1.086,5.995C81.958,69.421,81.712,69.605,81.444,69.766z"
|
||||
id="path53" /></svg>
|
Before Width: | Height: | Size: 9.5 KiB |
|
@ -1,125 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg4372"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="16x16.svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="16px"
|
||||
height="16px"
|
||||
viewBox="0 0 16 16"
|
||||
enable-background="new 0 0 16 16"
|
||||
xml:space="preserve"><metadata
|
||||
id="metadata34"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs32" /><sodipodi:namedview
|
||||
inkscape:zoom="32"
|
||||
objecttolerance="10"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
showgrid="true"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:cx="5.6537797"
|
||||
inkscape:cy="7.7070694"
|
||||
id="namedview23"
|
||||
inkscape:current-layer="svg4372"
|
||||
inkscape:window-height="705"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:pageshadow="2"><inkscape:grid
|
||||
type="xygrid"
|
||||
empspacing="4"
|
||||
id="grid4151" /><inkscape:grid
|
||||
type="xygrid"
|
||||
color="#ff00ff"
|
||||
opacity="0.2627451"
|
||||
spacingy="0.5"
|
||||
empopacity="0"
|
||||
empcolor="#ff3fff"
|
||||
empspacing="4"
|
||||
spacingx="0.5"
|
||||
id="grid4153" /></sodipodi:namedview><radialGradient
|
||||
id="SVGID_1_"
|
||||
cx="-5.4128"
|
||||
cy="8.277"
|
||||
r="40.7317"
|
||||
fx="-7.1172"
|
||||
fy="8.277"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#95A3AB"
|
||||
id="stop7" /><stop
|
||||
offset="0.2624"
|
||||
style="stop-color:#9BAEC8"
|
||||
id="stop9" /><stop
|
||||
offset="0.705"
|
||||
style="stop-color:#8093AA"
|
||||
id="stop11" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#273445"
|
||||
id="stop13" /></radialGradient><path
|
||||
fill="url(#SVGID_1_)"
|
||||
d="M14.5,2.5v11c0,0.52-0.4,0.94-0.9,0.99c-0.03,0.01-0.07,0.01-0.1,0.01h-11c-0.55,0-1-0.45-1-1v-11 c0-0.55,0.45-1,1-1h11C14.05,1.5,14.5,1.95,14.5,2.5z"
|
||||
id="path15" /><linearGradient
|
||||
id="rect6741-0-3-5_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="502.1291"
|
||||
y1="2616.1348"
|
||||
x2="502.1291"
|
||||
y2="2578.1348"
|
||||
gradientTransform="matrix(0.2973 0 0 -0.2973 -141.2816 780.1212)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop18" /><stop
|
||||
offset="0.0632"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
||||
id="stop20" /><stop
|
||||
offset="0.9506"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
||||
id="stop22" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
||||
id="stop24" /></linearGradient><rect
|
||||
id="rect6741-0-3-5"
|
||||
x="2.5"
|
||||
y="2.5"
|
||||
opacity="0.3"
|
||||
fill="none"
|
||||
stroke="url(#rect6741-0-3-5_1_)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
width="11"
|
||||
height="11" /><path
|
||||
id="rect5505-21-2-8"
|
||||
opacity="0.5"
|
||||
fill="none"
|
||||
stroke="#0E141F"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M2.5,1.5h11c0.552,0,1,0.448,1,1v11c0,0.552-0.448,1-1,1h-11c-0.552,0-1-0.448-1-1v-11C1.5,1.948,1.948,1.5,2.5,1.5z" /><path
|
||||
opacity="0.5"
|
||||
fill="#273445"
|
||||
d="M14.5,6.56v6.94c0,0.52-0.4,0.94-0.9,0.99H6.77l-3.23-3.14v-0.01c-0.01,0-0.01,0-0.01,0 l-0.3-0.58l-0.35-0.68c-0.01-0.03,0-0.07,0.04-0.09c0.02-0.01,0.03-0.01,0.05-0.01l0.26,0.06l0.33,0.07l0.15-0.07l0.11-0.06 l1.49-0.77c0.01-0.02,0.02-0.02,0.03-0.02l0.73-0.38L5.95,8.58C5.9,8.48,5.92,8.37,5.98,8.29L5.57,7.9L5.54,7.87 C5.52,7.85,5.5,7.82,5.48,7.79C5.4,7.62,5.46,7.41,5.63,7.32l2-1.04c0.13-0.07,0.29-0.05,0.4,0.05l0.02,0.02l0.96,0.92l0.51-0.26 C9.53,7,9.54,7,9.55,6.99l1.52-0.8l0.77-1.84c0.01-0.04,0.05-0.06,0.07-0.09c0.06-0.03,0.14-0.02,0.19,0.02L14.5,6.56z"
|
||||
id="path28"
|
||||
style="opacity:0.15" /><path
|
||||
fill="#FFFFFF"
|
||||
d="M13.555,7.038l-1.418-2.716c-0.002-0.003-0.004-0.006-0.007-0.009c-0.01-0.013-0.021-0.024-0.034-0.033 c-0.05-0.037-0.119-0.044-0.179-0.014c-0.035,0.018-0.061,0.047-0.075,0.08c-0.002,0.003-0.003,0.006-0.004,0.01l-0.766,1.838 L9.551,6.989c-0.01,0.005-0.02,0.01-0.03,0.016l-0.517,0.27L8.554,7.51L8.436,7.284c-0.067-0.128-0.224-0.177-0.352-0.11 C7.957,7.24,7.907,7.397,7.974,7.525l0.118,0.226L7.656,7.978l-0.376-0.72l0.675-0.352c0.173-0.091,0.24-0.305,0.15-0.478 C8.091,6.4,8.073,6.375,8.052,6.353l-0.031-0.03C7.915,6.232,7.759,6.21,7.627,6.278l-1.992,1.04 c-0.173,0.091-0.241,0.305-0.15,0.478C5.499,7.823,5.516,7.848,5.536,7.87c0.01,0.012,0.022,0.023,0.033,0.032 c0.107,0.091,0.262,0.113,0.393,0.044l0.674-0.352l0.376,0.72l-0.48,0.251L6.415,8.339C6.348,8.212,6.19,8.162,6.063,8.229 C6.029,8.246,6.001,8.27,5.979,8.298C5.917,8.376,5.903,8.486,5.952,8.581L6.07,8.806L5.338,9.188c-0.01,0.005-0.02,0.01-0.03,0.015 l-1.742,0.909L2.972,9.984C2.97,9.984,2.968,9.983,2.967,9.983C2.951,9.98,2.933,9.982,2.917,9.991 c-0.036,0.019-0.051,0.061-0.037,0.097l0.652,1.249c0.002,0.002,0.003,0.005,0.005,0.007l0.001,0.001l0.009,0.009 c0.023,0.019,0.056,0.023,0.085,0.009c0.016-0.008,0.028-0.021,0.035-0.037c0.001-0.001,0.001-0.003,0.002-0.004l0.239-0.572 l0.697-0.364c0.003,0.198,0.051,0.399,0.148,0.586c0.335,0.641,1.126,0.889,1.767,0.555c0.01-0.005,0.02-0.01,0.03-0.016 l0.731-0.382l0.137,0.262c0.067,0.128,0.225,0.177,0.352,0.11c0.128-0.067,0.177-0.224,0.111-0.352l-0.137-0.262l0.48-0.251 l0.399,0.764c0.093,0.178,0.312,0.247,0.49,0.154c0.05-0.026,0.091-0.062,0.122-0.104c0.082-0.108,0.098-0.258,0.032-0.386 l-0.399-0.764l0.435-0.227l0.137,0.262c0.067,0.128,0.224,0.177,0.352,0.111c0.128-0.067,0.177-0.225,0.11-0.352L9.765,9.83 l1.009-0.527c0.07-0.037,0.135-0.08,0.195-0.127c0.484-0.382,0.644-1.066,0.348-1.633c-0.098-0.187-0.235-0.341-0.396-0.457 l0.476-0.248l1.958,0.424c0.004,0.001,0.007,0.002,0.01,0.002c0.036,0.007,0.074,0.002,0.109-0.016 C13.552,7.208,13.585,7.117,13.555,7.038z M6.2,10.878c-0.004,0.003-0.009,0.005-0.013,0.007c-0.288,0.15-0.643,0.039-0.794-0.249 c-0.15-0.288-0.039-0.643,0.249-0.793l0.762-0.398l0.544,1.043L6.2,10.878z M7.41,10.246L6.866,9.204l0.032-0.016l0.449-0.234 l0.544,1.043l-0.102,0.053L7.41,10.246z M8.534,9.66L7.99,8.617L8.425,8.39l0.544,1.043L8.534,9.66z M10.34,8.716 c-0.006,0.004-0.013,0.008-0.019,0.011L9.432,9.191L8.888,8.148L9.55,7.802l0.212-0.111C9.774,7.686,9.785,7.68,9.797,7.675 c0.281-0.125,0.615-0.011,0.759,0.266c0.127,0.244,0.067,0.535-0.129,0.71C10.401,8.675,10.372,8.697,10.34,8.716z"
|
||||
id="path30" /></svg>
|
Before Width: | Height: | Size: 6.9 KiB |
|
@ -1,189 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg4157"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="24x24.svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="24px"
|
||||
height="24px"
|
||||
viewBox="0 0 24 24"
|
||||
enable-background="new 0 0 24 24"
|
||||
xml:space="preserve"><metadata
|
||||
id="metadata56"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs54" /><sodipodi:namedview
|
||||
inkscape:zoom="16"
|
||||
objecttolerance="10"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
showgrid="true"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:cx="12.702408"
|
||||
inkscape:cy="9.3530973"
|
||||
id="namedview46"
|
||||
inkscape:current-layer="svg4157"
|
||||
inkscape:window-height="705"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:pageshadow="2"><inkscape:grid
|
||||
type="xygrid"
|
||||
empspacing="4"
|
||||
id="grid4179" /><inkscape:grid
|
||||
type="xygrid"
|
||||
color="#ff3fff"
|
||||
opacity="0.1254902"
|
||||
spacingy="0.5"
|
||||
empopacity="0.11764706"
|
||||
empcolor="#3ff9ff"
|
||||
empspacing="2"
|
||||
spacingx="0.5"
|
||||
id="grid4181" /></sodipodi:namedview><g
|
||||
id="g2036-4"
|
||||
transform="matrix(0.55,0,0,0.3333336,-1.2000011,7.33333)"><g
|
||||
id="g3712-8"
|
||||
transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)"
|
||||
opacity="0.4"><radialGradient
|
||||
id="rect2801-6_1_"
|
||||
cx="-410.7503"
|
||||
cy="7665.8701"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(1.1601 0 0 -0.6 514.4986 4643.021)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop9" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop11" /></radialGradient><rect
|
||||
id="rect2801-6"
|
||||
x="38"
|
||||
y="40"
|
||||
fill="url(#rect2801-6_1_)"
|
||||
width="5"
|
||||
height="7" /><radialGradient
|
||||
id="rect3696-20_1_"
|
||||
cx="-1259.9097"
|
||||
cy="10842.7783"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(-1.1601 0 0 0.6 -1451.5964 -6462.1655)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop15" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop17" /></radialGradient><rect
|
||||
id="rect3696-20"
|
||||
x="5"
|
||||
y="40"
|
||||
fill="url(#rect3696-20_1_)"
|
||||
width="5"
|
||||
height="7" /><linearGradient
|
||||
id="rect3700-5_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="48.2663"
|
||||
y1="7022.2568"
|
||||
x2="48.2663"
|
||||
y2="7029.2852"
|
||||
gradientTransform="matrix(0.5789 0 0 -0.4286 -3.9436 3056.5657)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop21" /><stop
|
||||
offset="0.5"
|
||||
style="stop-color:#181818"
|
||||
id="stop23" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop25" /></linearGradient><rect
|
||||
id="rect3700-5"
|
||||
x="10"
|
||||
y="40"
|
||||
fill="url(#rect3700-5_1_)"
|
||||
width="28"
|
||||
height="7" /></g></g><radialGradient
|
||||
id="SVGID_1_"
|
||||
cx="-7.6034"
|
||||
cy="12.4049"
|
||||
r="59.5309"
|
||||
fx="-10.0943"
|
||||
fy="12.4049"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#95A3AB"
|
||||
id="stop29" /><stop
|
||||
offset="0.2624"
|
||||
style="stop-color:#9BAEC8"
|
||||
id="stop31" /><stop
|
||||
offset="0.705"
|
||||
style="stop-color:#8093AA"
|
||||
id="stop33" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#273445"
|
||||
id="stop35" /></radialGradient><path
|
||||
fill="url(#SVGID_1_)"
|
||||
d="M21.5,3.5v17c0,0.52-0.4,0.94-0.9,0.99c-0.03,0.01-0.07,0.01-0.1,0.01h-17c-0.55,0-1-0.45-1-1v-17 c0-0.55,0.45-1,1-1h17C21.05,2.5,21.5,2.95,21.5,3.5z"
|
||||
id="path37" /><path
|
||||
id="rect5505-21-8-1"
|
||||
opacity="0.5"
|
||||
fill="none"
|
||||
stroke="#0E141F"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M3.5,2.5h17c0.552,0,1,0.448,1,1v17c0,0.552-0.448,1-1,1h-17c-0.552,0-1-0.448-1-1v-17C2.5,2.948,2.948,2.5,3.5,2.5z" /><linearGradient
|
||||
id="rect6741-9_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="154.1423"
|
||||
y1="3116.9497"
|
||||
x2="154.1423"
|
||||
y2="3082.2695"
|
||||
gradientTransform="matrix(0.4595 0 0 -0.4595 -58.8209 1436.0929)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop41" /><stop
|
||||
offset="0.0165"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
||||
id="stop43" /><stop
|
||||
offset="0.98"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
||||
id="stop45" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
||||
id="stop47" /></linearGradient><rect
|
||||
id="rect6741-9"
|
||||
x="3.501"
|
||||
y="3.499"
|
||||
opacity="0.3"
|
||||
fill="none"
|
||||
stroke="url(#rect6741-9_1_)"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
width="17"
|
||||
height="17" /><path
|
||||
opacity="0.5"
|
||||
fill="#273445"
|
||||
d="M21.5,9.79V20.5c0,0.52-0.4,0.94-0.9,0.99H10.03l-4.77-4.64H5.25v-0.01 c-0.01-0.01-0.01-0.01-0.01-0.01l-0.45-0.85l-0.51-1c-0.02-0.05,0-0.11,0.05-0.14c0.03-0.02,0.05-0.02,0.08-0.02c0,0,0,0,0,0.01 l0.38,0.08l0.5,0.11l0.21-0.11l0.16-0.08l2.21-1.15c0.01-0.02,0.03-0.02,0.05-0.02L9,13.09l-0.18-0.34 c-0.07-0.14-0.05-0.3,0.04-0.42l-0.61-0.58c-0.01-0.01-0.03-0.03-0.04-0.05c-0.03-0.03-0.06-0.07-0.08-0.11 c-0.13-0.25-0.04-0.57,0.22-0.7l2.95-1.54c0.19-0.1,0.43-0.07,0.59,0.07l0.04,0.04l1.41,1.36l0.76-0.39 c0.02-0.02,0.03-0.02,0.05-0.03l2.24-1.18l1.14-2.72c0,0,0,0,0-0.01c0.02-0.05,0.07-0.09,0.11-0.12c0.09-0.05,0.2-0.04,0.27,0.02 L21.5,9.79z"
|
||||
id="path50"
|
||||
style="opacity:0.15" /><path
|
||||
fill="#FFFFFF"
|
||||
d="M20.068,10.472l-2.097-4.018c-0.003-0.005-0.006-0.009-0.01-0.013c-0.014-0.019-0.031-0.035-0.05-0.048 c-0.075-0.055-0.177-0.066-0.264-0.02c-0.051,0.027-0.09,0.069-0.112,0.119c-0.003,0.004-0.005,0.009-0.006,0.015l-1.134,2.719 l-2.25,1.174c-0.015,0.008-0.03,0.015-0.044,0.023l-0.765,0.399l-0.666,0.348l-0.174-0.334c-0.099-0.189-0.331-0.262-0.521-0.163 c-0.189,0.099-0.262,0.331-0.163,0.521l0.174,0.334l-0.644,0.336l-0.556-1.065l0.998-0.521c0.256-0.134,0.356-0.451,0.222-0.707 c-0.021-0.042-0.048-0.079-0.079-0.112l-0.045-0.044C11.725,9.28,11.495,9.247,11.3,9.349l-2.947,1.538 c-0.257,0.134-0.356,0.451-0.222,0.707c0.021,0.04,0.046,0.077,0.076,0.109c0.015,0.017,0.032,0.034,0.049,0.048 c0.158,0.134,0.387,0.167,0.582,0.065l0.998-0.521l0.556,1.065L9.68,12.731l-0.174-0.334c-0.099-0.189-0.332-0.262-0.521-0.163 c-0.05,0.026-0.091,0.061-0.124,0.103c-0.091,0.116-0.112,0.279-0.039,0.418l0.174,0.334l-1.082,0.565 c-0.015,0.007-0.03,0.015-0.044,0.023l-2.577,1.345l-0.879-0.191C4.41,14.83,4.408,14.83,4.406,14.829 c-0.024-0.005-0.051-0.001-0.074,0.011c-0.053,0.028-0.075,0.09-0.054,0.143l0.964,1.847c0.002,0.003,0.005,0.007,0.008,0.01 l0.001,0.002l0.014,0.013c0.034,0.028,0.083,0.035,0.126,0.013c0.024-0.013,0.041-0.032,0.051-0.055 c0.001-0.002,0.002-0.004,0.003-0.006l0.353-0.846l1.032-0.539c0.004,0.293,0.075,0.59,0.22,0.867 c0.495,0.948,1.665,1.315,2.613,0.82c0.015-0.008,0.03-0.015,0.044-0.023l1.082-0.565l0.202,0.388 c0.099,0.189,0.332,0.262,0.521,0.163c0.189-0.099,0.262-0.332,0.164-0.521l-0.202-0.387l0.711-0.371l0.59,1.13 c0.137,0.263,0.461,0.365,0.725,0.228c0.074-0.038,0.135-0.091,0.18-0.153c0.121-0.16,0.146-0.382,0.047-0.571l-0.59-1.13 l0.644-0.336l0.202,0.387c0.099,0.189,0.332,0.262,0.521,0.164c0.189-0.099,0.262-0.332,0.163-0.521l-0.202-0.388l1.493-0.78 c0.103-0.055,0.199-0.118,0.288-0.188c0.716-0.565,0.953-1.577,0.515-2.415c-0.145-0.277-0.348-0.505-0.586-0.676l0.704-0.367 l2.896,0.628c0.006,0.001,0.01,0.003,0.015,0.003c0.054,0.01,0.11,0.003,0.161-0.024C20.065,10.724,20.114,10.589,20.068,10.472z M9.188,16.153c-0.007,0.004-0.013,0.007-0.02,0.011c-0.426,0.223-0.952,0.058-1.174-0.369c-0.222-0.426-0.058-0.951,0.368-1.174 l1.128-0.589l0.805,1.542L9.188,16.153z M10.978,15.219l-0.805-1.542l0.047-0.024l0.664-0.347l0.805,1.542l-0.151,0.079 L10.978,15.219z M12.641,14.351l-0.805-1.542l0.644-0.336l0.805,1.542L12.641,14.351z M15.312,12.955 c-0.009,0.006-0.019,0.012-0.029,0.017l-1.315,0.686l-0.805-1.542l0.98-0.512l0.314-0.164c0.017-0.009,0.033-0.017,0.051-0.024 c0.416-0.185,0.91-0.017,1.123,0.393c0.188,0.36,0.099,0.792-0.19,1.051C15.402,12.894,15.359,12.926,15.312,12.955z"
|
||||
id="path52" /></svg>
|
Before Width: | Height: | Size: 9.1 KiB |
|
@ -1,185 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg4588"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="32x32.svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="32px"
|
||||
height="32px"
|
||||
viewBox="0 0 32 32"
|
||||
enable-background="new 0 0 32 32"
|
||||
xml:space="preserve"><metadata
|
||||
id="metadata56"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs54" /><sodipodi:namedview
|
||||
inkscape:zoom="16"
|
||||
objecttolerance="10"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
showgrid="true"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:cx="13.84291"
|
||||
inkscape:cy="15.588816"
|
||||
id="namedview46"
|
||||
inkscape:current-layer="svg4588"
|
||||
inkscape:window-height="705"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:pageshadow="2"><inkscape:grid
|
||||
type="xygrid"
|
||||
empspacing="4"
|
||||
id="grid4203" /><inkscape:grid
|
||||
type="xygrid"
|
||||
color="#ff3fff"
|
||||
opacity="0.37254902"
|
||||
spacingy="0.5"
|
||||
empopacity="0.1254902"
|
||||
empcolor="#3fffff"
|
||||
empspacing="2"
|
||||
spacingx="0.5"
|
||||
id="grid4205" /></sodipodi:namedview><g
|
||||
id="g2036-2"
|
||||
transform="matrix(0.6999997,0,0,0.3333336,-0.8000002,15.33333)"><g
|
||||
id="g3712-3"
|
||||
transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)"
|
||||
opacity="0.4"><radialGradient
|
||||
id="rect2801-0_1_"
|
||||
cx="-392.7515"
|
||||
cy="7672.0923"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(1.4765 0 0 -0.6 617.8796 4646.7544)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop9" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop11" /></radialGradient><rect
|
||||
id="rect2801-0"
|
||||
x="38"
|
||||
y="40"
|
||||
fill="url(#rect2801-0_1_)"
|
||||
width="5"
|
||||
height="7" /><radialGradient
|
||||
id="rect3696-2_1_"
|
||||
cx="-909.6242"
|
||||
cy="10817.8896"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(-1.4765 0 0 0.6 -1333.028 -6447.2324)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop15" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop17" /></radialGradient><rect
|
||||
id="rect3696-2"
|
||||
x="5"
|
||||
y="40"
|
||||
fill="url(#rect3696-2_1_)"
|
||||
width="5"
|
||||
height="7" /><linearGradient
|
||||
id="rect3700-1_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-107.542"
|
||||
y1="7034.7012"
|
||||
x2="-107.542"
|
||||
y2="7041.7295"
|
||||
gradientTransform="matrix(0.7368 0 0 -0.4286 103.2414 3061.8992)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop21" /><stop
|
||||
offset="0.5"
|
||||
style="stop-color:#181818"
|
||||
id="stop23" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop25" /></linearGradient><rect
|
||||
id="rect3700-1"
|
||||
x="10"
|
||||
y="40"
|
||||
fill="url(#rect3700-1_1_)"
|
||||
width="28"
|
||||
height="7" /></g></g><radialGradient
|
||||
id="SVGID_1_"
|
||||
cx="-11.8574"
|
||||
cy="16.5754"
|
||||
r="84.5966"
|
||||
fx="-15.3972"
|
||||
fy="16.5754"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#95A3AB"
|
||||
id="stop29" /><stop
|
||||
offset="0.2624"
|
||||
style="stop-color:#9BAEC8"
|
||||
id="stop31" /><stop
|
||||
offset="0.705"
|
||||
style="stop-color:#8093AA"
|
||||
id="stop33" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#273445"
|
||||
id="stop35" /></radialGradient><path
|
||||
fill="url(#SVGID_1_)"
|
||||
d="M29.5,4.66v22.68c0,1.06-0.78,1.95-1.79,2.13c-0.12,0.02-0.24,0.03-0.37,0.03H4.66 c-1.19,0-2.16-0.97-2.16-2.16V4.66c0-1.19,0.97-2.16,2.16-2.16h22.68C28.53,2.5,29.5,3.47,29.5,4.66z"
|
||||
id="path37" /><linearGradient
|
||||
id="rect6741-7-4_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-46.3832"
|
||||
y1="3411.5254"
|
||||
x2="-46.3832"
|
||||
y2="3376.2197"
|
||||
gradientTransform="matrix(0.6757 0 0 -0.6757 47.34 2309.1753)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop40" /><stop
|
||||
offset="0.038"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
||||
id="stop42" /><stop
|
||||
offset="0.962"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
||||
id="stop44" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
||||
id="stop46" /></linearGradient><path
|
||||
id="rect6741-7-4"
|
||||
opacity="0.3"
|
||||
fill="none"
|
||||
stroke="url(#rect6741-7-4_1_)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M4.587,3.5h22.826c0.6,0,1.087,0.487,1.087,1.087v22.826c0,0.6-0.487,1.087-1.087,1.087H4.587c-0.6,0-1.087-0.487-1.087-1.087V4.587 C3.5,3.987,3.987,3.5,4.587,3.5z" /><path
|
||||
id="rect5505-6"
|
||||
opacity="0.5"
|
||||
fill="none"
|
||||
stroke="#0E141F"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M4.66,2.5h22.68c1.193,0,2.16,0.967,2.16,2.16v22.68c0,1.193-0.967,2.16-2.16,2.16H4.66c-1.193,0-2.16-0.967-2.16-2.16V4.66 C2.5,3.467,3.467,2.5,4.66,2.5z" /><path
|
||||
opacity="0.5"
|
||||
fill="#273445"
|
||||
d="M29.5,12.53v14.81c0,1.06-0.78,1.95-1.79,2.13H12.87l-6.9-6.72l-0.02-0.01v-0.01 c-0.01-0.01-0.01-0.01-0.01-0.01l-0.65-1.24l-0.75-1.44c-0.03-0.08,0.01-0.16,0.08-0.21c0.04-0.02,0.07-0.02,0.11-0.02 c0,0,0,0,0,0.01l0.56,0.12l0.72,0.16l0.3-0.16l0.23-0.12l3.2-1.67c0.02-0.02,0.05-0.03,0.07-0.03l1.57-0.82l-0.26-0.49 c-0.1-0.2-0.07-0.44,0.06-0.61l-0.88-0.84c-0.02-0.02-0.05-0.04-0.07-0.07c-0.04-0.05-0.08-0.1-0.11-0.16 c-0.19-0.37-0.05-0.83,0.32-1.02l4.27-2.23c0.28-0.15,0.62-0.1,0.85,0.1l0.06,0.06l2.04,1.97l1.11-0.57 c0.02-0.02,0.04-0.03,0.07-0.04l3.25-1.7l1.65-3.94c0-0.01,0-0.01,0-0.02c0.03-0.07,0.09-0.13,0.16-0.17 c0.13-0.07,0.28-0.06,0.39,0.02L29.5,12.53z"
|
||||
id="path50"
|
||||
style="opacity:0.15" /><path
|
||||
fill="#FFFFFF"
|
||||
d="M27.412,13.506l-3.038-5.82c-0.005-0.007-0.009-0.013-0.014-0.019c-0.021-0.027-0.045-0.05-0.072-0.07 c-0.108-0.079-0.256-0.095-0.383-0.029c-0.074,0.039-0.13,0.101-0.161,0.172c-0.004,0.006-0.007,0.014-0.009,0.021L22.092,11.7 l-3.258,1.701c-0.021,0.011-0.043,0.022-0.064,0.034l-1.108,0.578l-0.965,0.504l-0.252-0.484c-0.143-0.274-0.48-0.38-0.754-0.237 c-0.273,0.143-0.38,0.48-0.237,0.754l0.252,0.484l-0.932,0.487l-0.806-1.542l1.445-0.754c0.371-0.194,0.515-0.653,0.321-1.024 c-0.031-0.06-0.07-0.114-0.114-0.162l-0.066-0.064c-0.228-0.195-0.562-0.243-0.844-0.096l-4.269,2.228 c-0.372,0.194-0.516,0.653-0.322,1.024c0.031,0.058,0.067,0.111,0.11,0.157c0.022,0.025,0.046,0.049,0.072,0.069 c0.228,0.194,0.561,0.242,0.843,0.094l1.445-0.754l0.805,1.542l-1.029,0.537l-0.252-0.484c-0.143-0.273-0.481-0.379-0.754-0.237 c-0.072,0.038-0.132,0.089-0.18,0.149c-0.132,0.168-0.162,0.404-0.057,0.605l0.252,0.484l-1.567,0.818 c-0.021,0.011-0.043,0.022-0.064,0.033l-3.733,1.949l-1.274-0.276c-0.003-0.001-0.007-0.002-0.01-0.002 c-0.035-0.007-0.073-0.002-0.108,0.016c-0.076,0.04-0.109,0.13-0.078,0.208l1.397,2.675c0.003,0.005,0.007,0.01,0.011,0.015 l0.002,0.002l0.02,0.019c0.05,0.041,0.121,0.05,0.182,0.019c0.034-0.018,0.06-0.046,0.074-0.079 c0.002-0.003,0.003-0.006,0.004-0.009l0.511-1.226l1.494-0.78c0.006,0.425,0.108,0.854,0.318,1.256 c0.717,1.373,2.412,1.905,3.785,1.188c0.021-0.011,0.043-0.022,0.064-0.034l1.567-0.818l0.293,0.562 c0.143,0.273,0.481,0.379,0.754,0.237c0.273-0.143,0.38-0.48,0.237-0.754l-0.293-0.561l1.029-0.538l0.855,1.637 c0.199,0.381,0.668,0.529,1.049,0.33c0.107-0.055,0.195-0.132,0.261-0.222c0.175-0.232,0.211-0.553,0.068-0.827l-0.854-1.637 l0.932-0.487l0.293,0.561c0.143,0.273,0.48,0.38,0.754,0.237c0.274-0.143,0.379-0.481,0.237-0.754l-0.293-0.562l2.163-1.129 c0.149-0.08,0.288-0.171,0.417-0.273c1.037-0.819,1.38-2.284,0.746-3.498c-0.21-0.401-0.503-0.731-0.848-0.979l1.019-0.532 l4.194,0.909c0.008,0.002,0.015,0.004,0.022,0.005c0.078,0.015,0.159,0.005,0.234-0.035C27.406,13.871,27.477,13.676,27.412,13.506z M11.653,21.735c-0.01,0.006-0.019,0.011-0.029,0.016c-0.617,0.322-1.378,0.084-1.7-0.534c-0.322-0.617-0.084-1.378,0.533-1.7 l1.633-0.853l1.166,2.234L11.653,21.735z M14.246,20.381l-1.166-2.234l0.068-0.035l0.961-0.502l1.166,2.234l-0.219,0.114 L14.246,20.381z M16.655,19.124l-1.166-2.234l0.932-0.487l1.166,2.234L16.655,19.124z M20.523,17.102 c-0.014,0.009-0.028,0.017-0.042,0.024l-1.904,0.994l-1.166-2.234l1.42-0.742l0.455-0.237c0.024-0.012,0.048-0.024,0.073-0.035 c0.603-0.268,1.318-0.024,1.627,0.569c0.273,0.522,0.144,1.147-0.276,1.522C20.654,17.014,20.592,17.061,20.523,17.102z"
|
||||
id="path52" /></svg>
|
Before Width: | Height: | Size: 9.2 KiB |
|
@ -1,184 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg4405"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="48x48.svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="48px"
|
||||
height="48px"
|
||||
viewBox="0 0 48 48"
|
||||
enable-background="new 0 0 48 48"
|
||||
xml:space="preserve"><metadata
|
||||
id="metadata57"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs55" /><sodipodi:namedview
|
||||
inkscape:zoom="8"
|
||||
objecttolerance="10"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
showgrid="true"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:cx="18.590413"
|
||||
inkscape:cy="19.492052"
|
||||
id="namedview45"
|
||||
inkscape:current-layer="g6"
|
||||
inkscape:window-height="705"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:pageshadow="2"><inkscape:grid
|
||||
type="xygrid"
|
||||
empspacing="4"
|
||||
id="grid4182" /><inkscape:grid
|
||||
type="xygrid"
|
||||
color="#ff3fff"
|
||||
opacity="0.31372549"
|
||||
spacingy="0.5"
|
||||
empopacity="0.11764706"
|
||||
empcolor="#3fffff"
|
||||
empspacing="2"
|
||||
spacingx="0.5"
|
||||
id="grid4184" /></sodipodi:namedview><g
|
||||
id="g6"><g
|
||||
id="g3712-0"
|
||||
transform="matrix(1.1578952,0,0,0.57142859,-3.789476,19.142856)"
|
||||
opacity="0.4"><radialGradient
|
||||
id="rect2801-4_1_"
|
||||
cx="-303.9869"
|
||||
cy="6065.9893"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(2.3202 0 0 -0.8 743.2948 4896.2915)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop10" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop12" /></radialGradient><rect
|
||||
id="rect2801-4"
|
||||
x="38"
|
||||
y="40"
|
||||
fill="url(#rect2801-4_1_)"
|
||||
width="5"
|
||||
height="7" /><radialGradient
|
||||
id="rect3696-8_1_"
|
||||
cx="-507.3432"
|
||||
cy="7800.5005"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(-2.3202 0 0 0.8 -1167.1165 -6196.9004)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop16" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop18" /></radialGradient><rect
|
||||
id="rect3696-8"
|
||||
x="5"
|
||||
y="40"
|
||||
fill="url(#rect3696-8_1_)"
|
||||
width="5"
|
||||
height="7" /><linearGradient
|
||||
id="rect3700-7_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-185.5088"
|
||||
y1="5712.9136"
|
||||
x2="-185.5088"
|
||||
y2="5719.9419"
|
||||
gradientTransform="matrix(1.1579 0 0 -0.5714 238.7997 3311.5498)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop22" /><stop
|
||||
offset="0.5"
|
||||
style="stop-color:#181818"
|
||||
id="stop24" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop26" /></linearGradient><rect
|
||||
id="rect3700-7"
|
||||
x="10"
|
||||
y="40"
|
||||
fill="url(#rect3700-7_1_)"
|
||||
width="28"
|
||||
height="7" /></g><radialGradient
|
||||
id="SVGID_1_"
|
||||
cx="-16.2385"
|
||||
cy="25.8311"
|
||||
r="122.195"
|
||||
fx="-21.3515"
|
||||
fy="25.8311"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#95A3AB"
|
||||
id="stop30" /><stop
|
||||
offset="0.2624"
|
||||
style="stop-color:#9BAEC8"
|
||||
id="stop32" /><stop
|
||||
offset="0.705"
|
||||
style="stop-color:#8093AA"
|
||||
id="stop34" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#273445"
|
||||
id="stop36" /></radialGradient><path
|
||||
fill="url(#SVGID_1_)"
|
||||
d="M43.5,7.5v35c0,0.96-0.69,1.77-1.6,1.96c-0.13,0.03-0.26,0.04-0.4,0.04h-35c-1.1,0-2-0.9-2-2v-35 c0-1.1,0.9-2,2-2h35C42.6,5.5,43.5,6.4,43.5,7.5z"
|
||||
id="path38" /><linearGradient
|
||||
id="rect6741-1_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-181.34"
|
||||
y1="3621.929"
|
||||
x2="-181.34"
|
||||
y2="3586.1492"
|
||||
gradientTransform="matrix(1 0 0 -1 205.34 3629.04)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop41" /><stop
|
||||
offset="0.021"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
||||
id="stop43" /><stop
|
||||
offset="0.977"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
||||
id="stop45" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
||||
id="stop47" /></linearGradient><path
|
||||
id="rect6741-1"
|
||||
opacity="0.3"
|
||||
fill="none"
|
||||
stroke="url(#rect6741-1_1_)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M6.5,6.5h35c0.552,0,1,0.448,1,1v35c0,0.552-0.448,1-1,1h-35c-0.552,0-1-0.448-1-1v-35C5.5,6.948,5.948,6.5,6.5,6.5z" /><path
|
||||
id="rect5505-21-6"
|
||||
opacity="0.5"
|
||||
fill="none"
|
||||
stroke="#0E141F"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M6.5,5.5h35c1.105,0,2,0.895,2,2v35c0,1.105-0.895,2-2,2h-35c-1.105,0-2-0.895-2-2v-35C4.5,6.395,5.395,5.5,6.5,5.5z" /><path
|
||||
opacity="0.5"
|
||||
fill="#273445"
|
||||
d="M43.5,19.234v23.477c0,0.857-0.616,1.579-1.428,1.749H19.773L9.689,34.644l-0.027-0.027 v-0.009c-0.009-0.009-0.009-0.018-0.018-0.018l-0.946-1.811l-1.098-2.097c-0.045-0.116,0.009-0.241,0.116-0.303 c0.054-0.027,0.107-0.036,0.161-0.027c0,0,0.009,0,0.009,0.009l0.812,0.178l1.053,0.223l0.437-0.232l0.33-0.17l4.685-2.445 c0.027-0.018,0.062-0.036,0.089-0.044l2.293-1.196l-0.375-0.714c-0.152-0.286-0.107-0.634,0.089-0.883l-1.285-1.232 c-0.036-0.036-0.071-0.062-0.098-0.107c-0.062-0.063-0.116-0.143-0.161-0.223c-0.285-0.544-0.08-1.214,0.464-1.499l6.237-3.257 c0.41-0.214,0.901-0.143,1.231,0.143l0.098,0.098l2.98,2.873l1.615-0.839c0.027-0.018,0.062-0.036,0.098-0.054l4.756-2.48 l2.4-5.756c0-0.009,0-0.018,0.009-0.027c0.045-0.107,0.125-0.197,0.232-0.25c0.187-0.098,0.402-0.08,0.562,0.036L43.5,19.234z"
|
||||
id="path51"
|
||||
style="opacity:0.15" /><path
|
||||
fill="#FFFFFF"
|
||||
d="M41.005,21.139l-4.436-8.499c-0.007-0.01-0.013-0.019-0.02-0.028c-0.031-0.039-0.066-0.074-0.105-0.102 c-0.158-0.115-0.373-0.139-0.559-0.042c-0.109,0.057-0.189,0.147-0.236,0.251c-0.007,0.009-0.01,0.02-0.014,0.031l-2.398,5.752 l-4.758,2.484c-0.031,0.016-0.063,0.033-0.093,0.05l-1.618,0.845l-1.41,0.736l-0.369-0.706c-0.209-0.4-0.701-0.555-1.101-0.346 c-0.399,0.208-0.555,0.701-0.346,1.101l0.369,0.706l-1.362,0.711l-1.176-2.253l2.111-1.102c0.542-0.284,0.752-0.953,0.469-1.495 c-0.045-0.088-0.102-0.167-0.167-0.236l-0.096-0.093c-0.334-0.285-0.82-0.355-1.232-0.14l-6.234,3.254 c-0.543,0.284-0.753,0.953-0.47,1.496c0.045,0.085,0.098,0.162,0.161,0.23c0.032,0.037,0.068,0.071,0.105,0.101 c0.334,0.283,0.819,0.353,1.23,0.138l2.11-1.101l1.176,2.253l-1.503,0.785l-0.369-0.706c-0.209-0.399-0.703-0.554-1.101-0.346 c-0.105,0.055-0.193,0.129-0.263,0.217c-0.193,0.245-0.237,0.589-0.083,0.884l0.369,0.706l-2.289,1.195 c-0.031,0.015-0.063,0.032-0.094,0.048L9.75,30.763l-1.86-0.404c-0.005-0.002-0.01-0.002-0.014-0.003 c-0.052-0.01-0.107-0.002-0.157,0.023c-0.111,0.058-0.159,0.189-0.115,0.303l2.04,3.907c0.005,0.007,0.01,0.015,0.016,0.021 l0.003,0.003l0.03,0.028c0.072,0.06,0.176,0.073,0.266,0.027c0.05-0.027,0.087-0.067,0.109-0.116 c0.002-0.004,0.004-0.008,0.006-0.013l0.747-1.79l2.182-1.14c0.009,0.62,0.158,1.247,0.464,1.834 c1.047,2.005,3.522,2.782,5.528,1.735c0.031-0.016,0.063-0.033,0.093-0.05l2.289-1.195l0.428,0.82 c0.208,0.399,0.703,0.554,1.101,0.346c0.399-0.208,0.555-0.702,0.346-1.101l-0.428-0.819l1.503-0.785l1.248,2.391 c0.291,0.557,0.976,0.772,1.533,0.481c0.156-0.081,0.285-0.193,0.382-0.324c0.255-0.339,0.308-0.807,0.099-1.208l-1.248-2.391 l1.362-0.711l0.428,0.819c0.208,0.399,0.702,0.555,1.101,0.346c0.4-0.209,0.554-0.703,0.346-1.101l-0.428-0.82l3.158-1.649 c0.218-0.117,0.421-0.25,0.609-0.399c1.515-1.196,2.015-3.336,1.09-5.108c-0.306-0.586-0.735-1.067-1.238-1.43l1.489-0.777 l6.125,1.327c0.012,0.003,0.022,0.006,0.033,0.007c0.113,0.021,0.233,0.007,0.341-0.05C40.997,21.673,41.1,21.388,41.005,21.139z M17.991,33.156c-0.014,0.009-0.027,0.015-0.042,0.023c-0.902,0.471-2.013,0.122-2.483-0.78c-0.47-0.901-0.122-2.012,0.778-2.482 l2.385-1.245l1.703,3.262L17.991,33.156z M21.778,31.179l-1.703-3.262l0.1-0.051l1.404-0.733l1.703,3.262l-0.32,0.167 L21.778,31.179z M25.296,29.343l-1.703-3.262l1.362-0.711l1.703,3.262L25.296,29.343z M30.945,26.39 c-0.02,0.013-0.04,0.025-0.061,0.035l-2.78,1.452l-1.703-3.262l2.073-1.083l0.664-0.346c0.035-0.018,0.071-0.035,0.107-0.052 c0.88-0.392,1.924-0.035,2.376,0.831c0.399,0.762,0.21,1.675-0.403,2.223C31.136,26.263,31.045,26.331,30.945,26.39z"
|
||||
id="path53" /></g></svg>
|
Before Width: | Height: | Size: 9.3 KiB |
|
@ -1,184 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg4332"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="64x64.svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="64px"
|
||||
height="64px"
|
||||
viewBox="0 0 64 64"
|
||||
enable-background="new 0 0 64 64"
|
||||
xml:space="preserve"><metadata
|
||||
id="metadata54"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs52" /><sodipodi:namedview
|
||||
inkscape:zoom="8"
|
||||
objecttolerance="10"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
showgrid="true"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:cx="16.97465"
|
||||
inkscape:cy="32.785763"
|
||||
id="namedview46"
|
||||
inkscape:current-layer="svg4332"
|
||||
inkscape:window-height="705"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:pageshadow="2"><inkscape:grid
|
||||
type="xygrid"
|
||||
empspacing="4"
|
||||
id="grid4203" /><inkscape:grid
|
||||
type="xygrid"
|
||||
color="#ff3fff"
|
||||
opacity="0.29803922"
|
||||
spacingy="0.5"
|
||||
empopacity="0.1372549"
|
||||
empcolor="#3fffff"
|
||||
empspacing="2"
|
||||
spacingx="0.5"
|
||||
id="grid4187" /></sodipodi:namedview><g
|
||||
id="g3712"
|
||||
transform="matrix(1.5789502,0,0,0.7142857,-5.8947511,28.428574)"
|
||||
opacity="0.4"><radialGradient
|
||||
id="rect2801_1_"
|
||||
cx="-238.0086"
|
||||
cy="5000.9541"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(3.1639 0 0 -1 791.0224 5044.4541)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop8" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop10" /></radialGradient><rect
|
||||
id="rect2801"
|
||||
x="38"
|
||||
y="40"
|
||||
fill="url(#rect2801_1_)"
|
||||
width="5"
|
||||
height="7" /><radialGradient
|
||||
id="rect3696_1_"
|
||||
cx="-344.166"
|
||||
cy="6088.6416"
|
||||
r="2.4994"
|
||||
gradientTransform="matrix(-3.1639 0 0 1 -1078.8911 -6045.1416)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818"
|
||||
id="stop14" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop16" /></radialGradient><rect
|
||||
id="rect3696"
|
||||
x="5"
|
||||
y="40"
|
||||
fill="url(#rect3696_1_)"
|
||||
width="5"
|
||||
height="7" /><linearGradient
|
||||
id="rect3700_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-172.6913"
|
||||
y1="4778.4775"
|
||||
x2="-172.6913"
|
||||
y2="4785.5059"
|
||||
gradientTransform="matrix(1.579 0 0 -0.7143 296.6709 3460.2258)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop20" /><stop
|
||||
offset="0.5"
|
||||
style="stop-color:#181818"
|
||||
id="stop22" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop24" /></linearGradient><rect
|
||||
id="rect3700"
|
||||
x="10"
|
||||
y="40"
|
||||
fill="url(#rect3700_1_)"
|
||||
width="28"
|
||||
height="7" /></g><radialGradient
|
||||
id="rect5505-21-3-8-5-2-9_1_"
|
||||
cx="-434.767"
|
||||
cy="4038.6685"
|
||||
r="12.6719"
|
||||
fx="-435.2973"
|
||||
fy="4038.6685"
|
||||
gradientTransform="matrix(0 13.394 16.4435 0 -66376.8047 5798.5156)"
|
||||
gradientUnits="userSpaceOnUse"><stop
|
||||
offset="0"
|
||||
style="stop-color:#9BAEC8"
|
||||
id="stop28" /><stop
|
||||
offset="0.2624"
|
||||
style="stop-color:#9BAEC8"
|
||||
id="stop30" /><stop
|
||||
offset="0.705"
|
||||
style="stop-color:#8093AA"
|
||||
id="stop32" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#273445"
|
||||
id="stop34" /></radialGradient><path
|
||||
id="rect5505-21-3-8-5-2-9"
|
||||
fill="url(#rect5505-21-3-8-5-2-9_1_)"
|
||||
d="M7.5,4.5h49c1.657,0,3,1.343,3,3v49c0,1.657-1.343,3-3,3 h-49c-1.657,0-3-1.343-3-3v-49C4.5,5.843,5.843,4.5,7.5,4.5z" /><linearGradient
|
||||
id="rect6741_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-261.3883"
|
||||
y1="3756.1287"
|
||||
x2="-261.3883"
|
||||
y2="3719.9463"
|
||||
gradientTransform="matrix(1.4324 0 0 -1.4363 406.4211 5400.8813)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop38" /><stop
|
||||
offset="0.0349"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
||||
id="stop40" /><stop
|
||||
offset="0.9622"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
||||
id="stop42" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
||||
id="stop44" /></linearGradient><path
|
||||
id="rect6741"
|
||||
opacity="0.3"
|
||||
fill="none"
|
||||
stroke="url(#rect6741_1_)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M7.5,5.429h49c1.105,0,2,0.895,2,2v49.142c0,1.105-0.895,2-2,2h-49c-1.105,0-2-0.895-2-2V7.429C5.5,6.324,6.395,5.429,7.5,5.429z" /><path
|
||||
id="rect5505-21-9"
|
||||
opacity="0.5"
|
||||
fill="none"
|
||||
stroke="#0E141F"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
enable-background="new "
|
||||
d=" M7.5,4.5h49c1.657,0,3,1.343,3,3v49c0,1.657-1.343,3-3,3h-49c-1.657,0-3-1.343-3-3v-49C4.5,5.843,5.843,4.5,7.5,4.5z" /><path
|
||||
opacity="0.5"
|
||||
fill="#273445"
|
||||
d="M59.5,23.606v32.74c0,1.77-1.436,3.205-3.205,3.205H25.493L11.094,45.523l-0.042-0.042v-0.005 c-0.011-0.011-0.016-0.021-0.026-0.027l-2.914-5.584c-0.064-0.164,0.005-0.35,0.164-0.434c0.074-0.037,0.154-0.048,0.222-0.032 c0.011,0,0.016,0,0.021,0.005l2.659,0.578l0.625-0.329l0.472-0.244l6.691-3.497c0.042-0.021,0.09-0.048,0.132-0.069l3.274-1.706 l-0.53-1.012c-0.217-0.419-0.154-0.911,0.122-1.261l-1.833-1.77c-0.053-0.042-0.101-0.09-0.148-0.143 c-0.09-0.101-0.164-0.207-0.228-0.328c-0.408-0.779-0.106-1.732,0.673-2.14l8.906-4.646c0.588-0.307,1.282-0.207,1.759,0.201 l0.138,0.132l4.259,4.116l2.31-1.203c0.042-0.027,0.09-0.048,0.132-0.074l6.797-3.55l3.428-8.217 c0.005-0.016,0.011-0.032,0.021-0.042c0.064-0.148,0.18-0.275,0.334-0.36c0.265-0.138,0.572-0.106,0.8,0.058L59.5,23.606z"
|
||||
id="path48"
|
||||
style="opacity:0.15" /><path
|
||||
fill="#FFFFFF"
|
||||
d="M55.829,26.231l-6.338-12.142c-0.01-0.014-0.018-0.027-0.029-0.04c-0.044-0.056-0.094-0.105-0.151-0.146 c-0.226-0.165-0.534-0.199-0.798-0.06c-0.155,0.082-0.271,0.21-0.337,0.359c-0.009,0.013-0.014,0.029-0.019,0.044l-3.426,8.217 l-6.798,3.549c-0.045,0.023-0.091,0.047-0.133,0.071l-2.311,1.207l-2.014,1.051l-0.527-1.009c-0.299-0.572-1.001-0.793-1.573-0.494 c-0.57,0.298-0.793,1.001-0.494,1.573l0.527,1.009l-1.946,1.016l-1.681-3.218l3.016-1.574c0.774-0.405,1.075-1.362,0.671-2.136 c-0.065-0.126-0.145-0.238-0.238-0.338l-0.137-0.133c-0.477-0.407-1.172-0.507-1.76-0.2l-8.907,4.649 c-0.775,0.405-1.076,1.362-0.672,2.137c0.064,0.121,0.14,0.231,0.23,0.328c0.046,0.053,0.097,0.102,0.149,0.145 c0.476,0.405,1.17,0.504,1.758,0.196l3.015-1.573l1.679,3.218l-2.148,1.121l-0.527-1.009c-0.299-0.57-1.004-0.791-1.573-0.494 c-0.151,0.078-0.276,0.185-0.376,0.31c-0.275,0.35-0.338,0.842-0.119,1.263l0.527,1.009l-3.27,1.708 c-0.045,0.022-0.089,0.045-0.134,0.069l-7.788,4.066L8.52,39.402C8.513,39.4,8.506,39.399,8.5,39.398 c-0.074-0.014-0.153-0.003-0.225,0.033c-0.159,0.083-0.227,0.271-0.164,0.433l2.914,5.581c0.007,0.011,0.015,0.021,0.023,0.03 l0.004,0.005l0.042,0.04c0.103,0.086,0.252,0.105,0.38,0.039c0.072-0.038,0.124-0.096,0.155-0.165 c0.003-0.006,0.006-0.012,0.008-0.019l1.067-2.558l3.117-1.628c0.012,0.886,0.226,1.782,0.664,2.62 c1.496,2.864,5.032,3.975,7.897,2.479c0.045-0.023,0.089-0.047,0.133-0.071l3.27-1.708l0.611,1.172 c0.298,0.57,1.004,0.791,1.573,0.494c0.57-0.298,0.793-1.002,0.495-1.573l-0.611-1.17l2.148-1.122l1.784,3.416 c0.415,0.795,1.394,1.103,2.189,0.688c0.222-0.115,0.407-0.276,0.545-0.463c0.365-0.484,0.44-1.153,0.141-1.726l-1.782-3.416 l1.946-1.016l0.611,1.17c0.298,0.57,1.002,0.793,1.573,0.495c0.572-0.299,0.791-1.004,0.494-1.573l-0.611-1.172l4.512-2.356 c0.312-0.166,0.602-0.358,0.87-0.57c2.164-1.708,2.879-4.766,1.557-7.298c-0.437-0.837-1.05-1.525-1.769-2.042l2.127-1.11 l8.751,1.897c0.017,0.004,0.031,0.009,0.046,0.01c0.162,0.031,0.332,0.01,0.488-0.072C55.818,26.993,55.966,26.586,55.829,26.231z M22.951,43.399c-0.02,0.012-0.039,0.022-0.06,0.033c-1.288,0.673-2.875,0.174-3.548-1.114c-0.671-1.287-0.174-2.874,1.111-3.547 l3.408-1.779l2.433,4.66L22.951,43.399z M28.361,40.574l-2.433-4.66l0.142-0.074l2.006-1.047l2.433,4.66l-0.458,0.239L28.361,40.574 z M33.386,37.952l-2.433-4.66l1.946-1.016l2.433,4.66L33.386,37.952z M41.458,33.733c-0.028,0.018-0.058,0.035-0.087,0.05 l-3.972,2.074l-2.433-4.66l2.962-1.547l0.949-0.495c0.051-0.026,0.101-0.05,0.153-0.074c1.258-0.559,2.749-0.05,3.395,1.187 c0.57,1.089,0.3,2.393-0.575,3.176C41.73,33.55,41.6,33.648,41.458,33.733z"
|
||||
id="path50" /></svg>
|
Before Width: | Height: | Size: 9.1 KiB |
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 CallMeFib3r
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 39 KiB |
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
|
||||
<defs>
|
||||
<radialGradient id="radial0" gradientUnits="userSpaceOnUse" cx="26.10651" cy="259.029419" fx="26.10651" fy="259.029419" r="16.961348" gradientTransform="matrix(1.293566,0.0000000199752,-0.0000000199752,1.293566,-279.770477,-257.071594)">
|
||||
<stop offset="0" style="stop-color:rgb(96.078432%,76.078433%,6.666667%);stop-opacity:1;"/>
|
||||
<stop offset="0.4572" style="stop-color:rgb(96.078432%,76.078433%,6.666667%);stop-opacity:1;"/>
|
||||
<stop offset="0.685319" style="stop-color:rgb(97.647059%,85.882354%,57.647061%);stop-opacity:1;"/>
|
||||
<stop offset="0.822382" style="stop-color:rgb(96.078432%,74.901962%,17.647059%);stop-opacity:1;"/>
|
||||
<stop offset="1" style="stop-color:rgb(89.803922%,64.705884%,3.921569%);stop-opacity:1;"/>
|
||||
<stop offset="1" style="stop-color:rgb(89.803922%,64.705884%,3.921569%);stop-opacity:1;"/>
|
||||
</radialGradient>
|
||||
<image id="image1393" width="192" height="152" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAACYCAYAAACyAeadAAAABmJLR0QA/wD/AP+gvaeTAAAMNElEQVR4nO3dy3MbV3bH8e+53Q2A4Et8gHrLiETJNlWWa8IxPXEqJWaTVSo75d+R/G94l8rO3E5VtvQqKy1SiZUal8pRObIVh7YlkiJBAN3nlwUIiXIcj+OhSIo4nwWLJYGNbvKc+74XEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghHBI77hsIx0/ajwMDAx3z7RypSIARJmF8jHF7NT3Y2EhLfF5xBzcbnSSIBBhREsYa6VGrXUyXqZ5VvWxri84lHvdGKQnScd9AOHpiEPxPxi7UW5VNF93qQqbiUpHXJ2m1C0aoYIwEGDEC49NB8Bf17EzVVbuX7FYfv5YKTX+3VS9YuxMJEE4pYbTaxVSqTdXK7IqSvW/i/UR2vkDNfKqb0doYmQTIj/sGwtHRPRL/tFg8h8kKv4TpFuK3mE0JtsxSzk7f7n+9PTIJEDXAiJAwlm7mX+91JspUXRC8h/ShxDvJmJNUYDKmYfnG5Eh0gCESYCRIGPeXc1o7zelGdjbJlgQfgt0EzkmMDV5oYvN47/WoRRPolJOw+58s51dnnjbz8bTgpHcr6Xdm3BJclCkls+SiIpVON41M6Q9RA5xqEsb6arZ883kjTaR5TG87+tDgN4auAFOGFQgSKknJNZG0zvpx3/qRiRrglBoGP2w0Nnf25o38RmWsGLYMamM2bSKX6DmSkUpV+IwnrT4djUkwiBrgVBoEPxlsNJ7t7cxTpeuYVgw+wHQVmDGoa/j3N3OSl0WW/IfjvfUjFzXAKTMM/g1ajWJ3d85SuuboA5wPk3EdmAMacpIZpQwhXJ5K6LtXjZEp/SFqgFNlf3FbtrHRauTl5KxZuubOimG/M+OGjHlgDMiwF8sdZIaTqVSWXFN5JEB487wo+ZdajWJ8Yi71bRHTCqa/AN4GtWwY/AfW+gyWP8tNViozb/W+E3dGpw8QTaBT4GWbfxD8lmwR9IFLHxm8A1oAxiRyDgS/2E8AmZOpr7J0qmxkgh+iBnjj3btH4pPl/GFnsbnZa7YMbrj0oYbBbywATXg1+IdkSIarsoos+ROAj4/4IY5R1ABvMN0jPVi6mT+deTo2XfVapnzRTSsMZnlvCBZsMMv7k8FvIBcgPCX1VSavOjERFk44gUmkhyuLxRSbE3mpc4WKJZf9pWEfGfYOsGD2fwf/K5czuUhlPU9etnKNUAUQCfCmkQbr+Vlv1yZgany8dtHhlsRfIX0keBusZcYY+vng1+CCAqvAq05landy3b0bneBwAg07u9/una1nXU2NyS9KftPdVixxC+wtiVmDuvTqaM9PGWx7NBdWyagaWdcflnUWj+h5ToJIgDfAcPP6g7Wb+bmJbqM+V80Irrh4T8lWMmNJ4hIwbVAHJf74tka5wMANlfLku12geP3Pc5JEApxww83r3G4Xl58+G+9abd7wq8mzW0r6rcHbEucEkzYI318S/PsXRyRcUplZ5VU9afFyfWSaPxAJcGLtn9Vj3F/OaH1V3+3Xp7q17HyN6npl6c9dumVwFZg3aCIKEoZ+cfAPmkAyR5QSLhCfA0uv88lOlkiAE0Zg3MNYX02PNx4W07P/1dzz+mytLC9nyZdc/AZ41+AiMIPRkMgw0q/oug5mgc36GN4sEDuH/kgnWiTACTFs56/fJrUftfPp8qt6c7o+5V2dzcyvgb9n8B7YVYMFYAKshvRHO7s/xQYTYDLhoFKVu1KuB4xUBRAJcNxeBP46aXVlMbvVoe6z3Ql6ac6SLgveQbwns+ugi8CZ/TU9uX5ZZ/en33fwRTBIAJL5zg4sXR2t1aCRAMdAL9vpdv+T5Wz59vfZ+xupvjvVb9ZcM708vwhcM2nJEjdMdgU0J5gwqAkS9v9o7/+I2WD0H0BmDpQyXA0bqeCHSIAjITh40KCxvpoedh5nk9l2fv2tb+udTqNZTOtMX9m5Cv1ZgndIuo64LFnL0KRB3SDXcJTnTwhVHfxWckwlnvl4NVrLICAS4LUYBvzHH2N37wJrGPeXE/+9mb7NtvN67VExLRq1vDbe7xWzlfxs5lyR/Lph1zBdRDYPTAk1DMtBSYc4c2+YNKiLXFgpK52xPd2/P8ny8mG9y8kXCXDIXozbf7Kc7q5sJn6/mzHRzLef7RTdvFtPXjRrZT5Zyeesm84m1E6JtuCyZOcwZsEmgAZQGKTBeSWHe17nsAWE8GQqzc1VRhMo/Anu3SOxvpqejH1RG5/6ttHrWqMosjGXN/N+OZEpnbGU5vuVX8gyXXbpohkLYLOCKTOaQJ1XJ7Re0yltGuwFMFyy0pL5FqN1KBZEAhya/XU6id4P9cKz6Ur5fFGzlsGC4WcdzprsnCpaJJ+BNA1MIpoaBH2NwW6t1xz4HCj+4eUwaPJJkqi/tnc9kSIBDo9BO98otyZTPb9k0rsuezfBW5AWJM6Y2QTQRNYA1RiU9DkHg/7FTt2f6eYa/NoRoB8RDPoArxyKNTIng8Zy6MMkJh9VrVrq0NeWe3qeBn3NGaHzQhdknJWYA6YY7NKqAZm9LPGFD9rlQIVRAqVB36APlEC1/ypnEMD6NR9msf8zwvBRPRQLogY4NGZI96hYSXv9LH3foP+lzCYkO5PQlGTjDDq2w8/h8mHH9uWQPC72g19UQLX/usFLRMJIGBmDv12OkaRXmk2/qPzenweQhKPBoVgasUOxIBLgcN1Da58+LO+02tvPe/nXlUqDDHcvzawjdB5jUoOSfxi0g1IcKokS6IHtYewJdQ16mDmSychN1ARjhprCxmy//6DBiNGwD5F+STPJGOwHRl4WWe49f/HvIyMS4BAZSH+Pr336qP9Bq70197z+VVmrerm06fA16BrY/jqewRCnwDUo7XsYuxjbuJ4BT5OxCbaDVLpSslTVhU2aMYtbC9O8ZDMJpgWTwJigkSBHlh1YKvGTMb2/HMLNUr9b9l0vD8UamVogEuCQDZMAPerx4ObWsy96ZVGz7S72WOYt3OayzKYcGrgysCpL7LnbruHPXb5jZrtVZZ2Us+dV1VeW3KrK6lmeVdavJ2XNUkxbSi3JL7rsspkuSCwYzGJMyjQmDWaP95tOdrBWsEErSPu1S1nLku/tjtahWBAJ8Frsj69L9z4vz9xeff7k+Re98SJt15N90y99rJTV68qzvpOU8FJllWdFzyv6Ven9qqqVY81uWWW1Sr2GazeXWtDrdFMas7TTSUWhVCuystnr23RhmpXpvIk2ZleFLuG2YGZnQOPY/jKK4TZJcWAkVC5ZqQr36e/E34xO6Q+j1dw7FsONLfc/Wc6WL28mqt3sO5/M5ovKnhZu6id5P1PVeOb9zcL3Jhq+uDDtbE9q/bN1VpfQGnBneMHWqjG5bY/+9fusfaGTbfbrtSK3RpmyiXLP51LOuURqu2kRtzaJc7hmMSaAhqAwkQSVGU8N/s2Mf5TKf56uqif87Td7Zvhx/b6OWtQAr9lwuFG6PyxZbX4Nowv/0sJejLr8HeJj4MCJDKt//b8+ud3EOgj+4fdUS+PYndZql87j3Y2tze3G7NwPhXrfVJX+g1L/rsQVly2mRNvFBaQ5M5s0o44wwzpm2pPTU8oq5qRROhQLIgGOzIGx+v2+J7Y6/L+DQX7vZy+jYQP+xXW0bkDVWqPkzJUuXz7ffVbkW7XxaqPTa3yVWfkH0PkEbTeuAhdcNmvmDWEduf2gjB15r3z0h0Ltw3rgN0Q0gU6R4eYabq8mOo+z7/pZrarKMav1J+q5nalkrQTnjHQeaU5mmaEnZnxm/e6X/9mZ2Vq683l/VD4lHiIBTqUXibCEPeBmNjP2NG9aXu9XGlMqxou6j3tPTcuylMpqN/f09fiUNll91Bul9j9EApxqBzbiGOuDWuHbbDuv744VnbyWW+Y25r3y6XjqtDce9bmDj1LpD5EAI2O4DXNtjXSnhfHFsrEMbE+Kz9adu79uTdGbLhJgBP1oiyajGPghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhPDm+x+8b2xxUlIm4AAAAABJRU5ErkJggg=="/>
|
||||
<image id="image1419" width="192" height="152" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAACYCAYAAACyAeadAAAABmJLR0QA/wD/AP+gvaeTAAAAiElEQVR4nO3BMQEAAADCoPVP7WsIoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAPIpwABWLk4FQAAAABJRU5ErkJggg=="/>
|
||||
</defs>
|
||||
<g id="surface1370">
|
||||
<path style="fill:none;stroke-width:12;stroke-linecap:round;stroke-linejoin:miter;stroke:url(#radial0);stroke-miterlimit:4;" d="M -230 78 C -230 86.835938 -237.164062 94 -246 94 " transform="matrix(0.000000000000000061,-1,1,0.000000000000000061,-160,-172)"/>
|
||||
<use xlink:href="#image1393" transform="matrix(1,0,0,1,-168,-16)"/>
|
||||
<use xlink:href="#image1419" transform="matrix(1,0,0,1,-168,-16)"/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,12.156863%,19.215687%);fill-opacity:1;" d="M 14.476562 1 C 13.429688 3.382812 10.09375 5 8 5 L 8 7 C 10.09375 7 13.429688 8.617188 14.476562 11 L 15 11 L 15 1 Z M 14.476562 1 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,12.156863%,19.215687%);fill-opacity:1;" d="M 2 5 L 1.996094 7 L 8.257812 7.011719 L 8.265625 5.011719 Z M 2 5 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,12.156863%,19.215687%);fill-opacity:1;" d="M 7.5 5 C 5.023438 5 3 7.023438 3 9.5 C 3 11.976562 5.023438 14 7.5 14 C 9.976562 14 12 11.976562 12 9.5 C 12 7.023438 9.976562 5 7.5 5 Z M 7.5 7 C 8.902344 7 10 8.097656 10 9.5 C 10 10.902344 8.902344 12 7.5 12 C 6.097656 12 5 10.902344 5 9.5 C 5 8.097656 6.097656 7 7.5 7 Z M 7.5 7 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,12.156863%,19.215687%);fill-opacity:1;" d="M 0 4 L 1 5 L 1 7 L 0 8 Z M 0 4 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.0 KiB |
|
@ -1,14 +0,0 @@
|
|||
@define-color colorAccent #9aa7c8;
|
||||
@define-color colorPrimary #9aa7c8;
|
||||
|
||||
.header-counters{
|
||||
background: rgba(255,255,255,.4);
|
||||
}
|
||||
|
||||
.attachment{
|
||||
background: rgba (255,255,255,.8);
|
||||
}
|
||||
|
||||
.card{
|
||||
background: #fff;
|
||||
}
|
BIN
data/logo128.png
Before Width: | Height: | Size: 5.6 KiB |
|
@ -1,15 +1,17 @@
|
|||
icon_sizes = ['16', '24', '32', '48', '64', '128']
|
||||
icons_dir = join_paths(get_option('datadir'), 'icons', 'hicolor')
|
||||
scalable_dir = join_paths(icons_dir, 'scalable', 'apps')
|
||||
symbolic_dir = join_paths(icons_dir, 'symbolic', 'apps')
|
||||
|
||||
foreach i : icon_sizes
|
||||
install_data(
|
||||
join_paths('icons', i, meson.project_name() + '.svg'),
|
||||
install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', i + 'x' + i, 'apps')
|
||||
)
|
||||
install_data(
|
||||
join_paths('icons', i, meson.project_name() + '.svg'),
|
||||
install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', i + 'x' + i + '@2', 'apps')
|
||||
)
|
||||
endforeach
|
||||
install_data(
|
||||
join_paths('icons', 'color.svg'),
|
||||
install_dir: scalable_dir,
|
||||
rename: meson.project_name() + '.svg'
|
||||
)
|
||||
install_data(
|
||||
join_paths('icons', 'symbolic.svg'),
|
||||
install_dir: symbolic_dir,
|
||||
rename: meson.project_name() + '-symbolic.svg'
|
||||
)
|
||||
|
||||
install_data(
|
||||
meson.project_name() + '.gschema.xml',
|
||||
|
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 246 KiB |
Before Width: | Height: | Size: 176 KiB |
Before Width: | Height: | Size: 244 KiB |
|
@ -0,0 +1,255 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkImage" id="image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">dialog-warning-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="image2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">mail-attachment-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="image4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">face-smile-symbolic</property>
|
||||
</object>
|
||||
<template class="TootleDialogsCompose" parent="GtkWindow">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="has_subtitle">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="visibility_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="visibility_icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="image3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">pan-down-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="post_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="horizontal"/>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box">
|
||||
<property name="width_request">500</property>
|
||||
<property name="height_request">250</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkRevealer" id="cw_revealer">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="cw">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="placeholder_text" translatable="yes">Write your warning here</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="width_request">350</property>
|
||||
<property name="height_request">150</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="overlay_scrolling">False</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="content">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="pixels_below_lines">8</property>
|
||||
<property name="pixels_inside_wrap">8</property>
|
||||
<property name="wrap_mode">word-char</property>
|
||||
<property name="left_margin">8</property>
|
||||
<property name="right_margin">8</property>
|
||||
<property name="top_margin">8</property>
|
||||
<property name="bottom_margin">8</property>
|
||||
<property name="accepts_tab">False</property>
|
||||
<property name="populate_all">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="emoji_button">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="image">image4</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="attach_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="image">image2</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="cw_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="image">image1</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="counter">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">250</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<template class="TootleDialogsMainWindow" parent="GtkWindow">
|
||||
<property name="width_request">450</property>
|
||||
<property name="height_request">600</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="back_button">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">go-previous-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="compose_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-edit-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="title">
|
||||
<object class="GraniteWidgetsModeButton" id="timeline_switcher">
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="TootleWidgetsAccountsButton" id="accounts_button">
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="view_stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="transition_type">slide-left-right</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="timeline_stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="transition_type">slide-left-right</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">0</property>
|
||||
<property name="title" translatable="yes">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,155 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<template class="TootleViewsBase" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolled">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="shadow_type">none</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="states">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="vhomogeneous">False</property>
|
||||
<property name="transition_type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="status">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_top">16</property>
|
||||
<property name="margin_bottom">16</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">16</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="width_request">128</property>
|
||||
<property name="height_request">128</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="opacity">0.29999999999999999</property>
|
||||
<property name="pixel_size">128</property>
|
||||
<property name="icon_name">com.github.bleakgrey.tootle-symbolic</property>
|
||||
<property name="icon_size">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="status_stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="opacity">0.5</property>
|
||||
<property name="transition_type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="status_message_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">message</property>
|
||||
<property name="title" translatable="yes">page0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="status_spinner">
|
||||
<property name="height_request">32</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">spinner</property>
|
||||
<property name="title" translatable="yes">page1</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="status_button">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="halign">center</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">status</property>
|
||||
<property name="title" translatable="yes">page1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="content">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">content</property>
|
||||
<property name="title" translatable="yes">page0</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,224 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkGrid" id="wizard">
|
||||
<property name="width_request">350</property>
|
||||
<property name="height_request">400</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_top">12</property>
|
||||
<property name="margin_bottom">12</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="row_spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">12</property>
|
||||
<property name="margin_right">12</property>
|
||||
<property name="hexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="next">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="icon_name">go-next-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="image-button"/>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="transition_type">slide-left-right</property>
|
||||
<child>
|
||||
<object class="GtkGrid" id="step1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">12</property>
|
||||
<property name="margin_right">12</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<property name="column_spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="step1_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Which Instance?
|
||||
</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="wrap">True</property>
|
||||
<style>
|
||||
<class name="h2"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes"><a href="https://joinmastodon.org/">What's an instance?</a></property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="justify">right</property>
|
||||
<property name="track_visited_links">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="instance_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="activates_default">True</property>
|
||||
<property name="caps_lock_warning">False</property>
|
||||
<property name="placeholder_text" translatable="yes">instance.domain</property>
|
||||
<property name="input_purpose">url</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="step2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">12</property>
|
||||
<property name="margin_right">12</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<property name="column_spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="step2_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Grant Account Access
|
||||
</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="wrap">True</property>
|
||||
<style>
|
||||
<class name="h2"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="code_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="secondary_icon_name">edit-paste-symbolic</property>
|
||||
<property name="secondary_icon_tooltip_text" translatable="yes">Paste</property>
|
||||
<property name="placeholder_text" translatable="yes">Paste your authorization code here</property>
|
||||
<property name="input_purpose">url</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes"><a href="">Try another instance?</a></property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="justify">right</property>
|
||||
<property name="track_visited_links">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">page1</property>
|
||||
<property name="title" translatable="yes">page1</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="pixel_size">128</property>
|
||||
<property name="icon_name">com.github.bleakgrey.tootle</property>
|
||||
<property name="icon_size">6</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -0,0 +1,525 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkPopover" id="filter_popover">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">8</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="filter_all">
|
||||
<property name="label" translatable="yes">Posts</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="filter_replies">
|
||||
<property name="label" translatable="yes">Posts and Replies</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">filter_all</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="filter_media">
|
||||
<property name="label" translatable="yes">Media</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">filter_all</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkMenu" id="options">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">__glade_unnamed_3</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">__glade_unnamed_8</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">__glade_unnamed_9</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkGrid" id="grid">
|
||||
<property name="width_request">400</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="row_spacing">8</property>
|
||||
<property name="column_spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkListBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<property name="activate_on_single_click">False</property>
|
||||
<child>
|
||||
<object class="GtkListBoxRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="activatable">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<child>
|
||||
<object class="TootleWidgetsRichLabel" id="note">
|
||||
<property name="visible">True</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="selectable">True</property>
|
||||
<property name="width_chars">25</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkListBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<property name="activate_on_single_click">False</property>
|
||||
<child>
|
||||
<object class="GtkListBoxRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="activatable">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="height_request">40</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="spacing">32</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="posts_tab">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="posts_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">0 Posts</property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="following_tab">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">posts_tab</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="following_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">0 Follows</property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="followers_tab">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">posts_tab</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="followers_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">0 Followers</property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
<class name="horizontal"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="filter_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="popover">filter_popover</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">view-more-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkListBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<property name="activate_on_single_click">False</property>
|
||||
<child>
|
||||
<object class="GtkListBoxRow">
|
||||
<property name="width_request">100</property>
|
||||
<property name="height_request">80</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="activatable">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="TootleWidgetsAvatar" id="avatar">
|
||||
<property name="visible">True</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="size">128</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="TootleWidgetsRichLabel" id="name">
|
||||
<property name="visible">True</property>
|
||||
<property name="selectable">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="TootleWidgetsRichLabel" id="handle">
|
||||
<property name="visible">True</property>
|
||||
<property name="selectable">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBoxRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="activatable">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBoxRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="activatable">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="relationship">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="actions">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="follow_button">
|
||||
<property name="label" translatable="yes">Follow</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="options_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="popup">options</property>
|
||||
<property name="use_popover">False</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="horizontal"/>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -0,0 +1,240 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkPopover" id="popover">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hhomogeneous">False</property>
|
||||
<property name="vhomogeneous">False</property>
|
||||
<property name="transition_type">slide-left-right</property>
|
||||
<property name="interpolate_size">True</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">4</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="item_accounts">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes"> </property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="menu_name">accounts</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="item_favs">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Favorites</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="item_direct">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Conversations</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="item_watchlist">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Watchlist</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="item_refresh">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Refresh</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="item_search">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Search</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="item_prefs">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Preferences</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">menu</property>
|
||||
<property name="title" translatable="yes">page0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="width_request">400</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">4</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Accounts</property>
|
||||
<property name="menu_name">menu</property>
|
||||
<property name="inverted">True</property>
|
||||
<property name="centered">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="max_content_height">300</property>
|
||||
<property name="propagate_natural_height">True</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="account_list">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">accounts</property>
|
||||
<property name="title" translatable="yes">page1</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<template class="TootleWidgetsAccountsButton" parent="GtkMenuButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="popover">popover</property>
|
||||
<child>
|
||||
<object class="GtkOverlay">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="TootleWidgetsAvatar" id="avatar">
|
||||
<property name="visible">True</property>
|
||||
<property name="size">24</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pass_through">True</property>
|
||||
<property name="index">-1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="overlay">
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pass_through">True</property>
|
||||
<property name="index">-1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<template class="TootleWidgetsAccountsButtonItem" parent="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="row_spacing">3</property>
|
||||
<property name="column_spacing">8</property>
|
||||
<child>
|
||||
<object class="TootleWidgetsAvatar" id="avatar">
|
||||
<property name="visible">True</property>
|
||||
<property name="size">48</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="height">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="relief">none</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-delete-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="circular"/>
|
||||
<class name="image-button"/>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">3</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="height">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="name">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Name</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="single_line_mode">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="handle">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Handle</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="single_line_mode">True</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="profile">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Open profile</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="relief">none</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">avatar-default-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="circular"/>
|
||||
<class name="image-button"/>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="height">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,293 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkImage" id="image2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-favorite-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="image3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">mail-replied-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="reblog_icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">media-playlist-repeat-symbolic</property>
|
||||
</object>
|
||||
<template class="TootleWidgetsStatus" parent="GtkEventBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="grid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="column_spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="header_icon">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="margin_bottom">8</property>
|
||||
<property name="icon_name">applications-development-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="actions">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">8</property>
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="reblog_button">
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="image">reblog_icon</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="favorite_button">
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="image">image2</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reply_button">
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="image">image3</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="width">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="TootleWidgetsRichLabel" id="handle_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">Handle</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="date_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="opacity">0.5</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Yesterday</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="pin_indicator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="icon_name">view-pin-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="width">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRevealer" id="revealer">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="reveal_child">True</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="TootleWidgetsRichLabel" id="revealer_content">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="wrap_mode">word-char</property>
|
||||
<property name="width_chars">15</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="TootleWidgetsAttachmentBox" id="attachments">
|
||||
<property name="visible">True</property>
|
||||
<property name="margin_top">8</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="width">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="TootleWidgetsAvatar" id="avatar">
|
||||
<property name="width_request">48</property>
|
||||
<property name="height_request">48</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="height">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="TootleWidgetsRichLabel" id="header_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="width">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="TootleWidgetsRichLabel" id="content">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="wrap_mode">word-char</property>
|
||||
<property name="width_chars">15</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="width">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,4 @@
|
|||
meson build --prefix=/usr
|
||||
cd build
|
||||
sudo ninja install
|
||||
com.github.bleakgrey.tootle
|
50
meson.build
|
@ -3,6 +3,9 @@ project('com.github.bleakgrey.tootle', 'vala', 'c')
|
|||
gnome = import('gnome')
|
||||
i18n = import('i18n')
|
||||
|
||||
#add_project_arguments(['--disable-warnings', '-g', '-X', '-rdynamic'], language: 'vala')
|
||||
add_project_arguments(['-g', '-rdynamic', '-export-dynamic'], language: 'c')
|
||||
|
||||
add_global_arguments([
|
||||
'-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name())
|
||||
],
|
||||
|
@ -10,7 +13,7 @@ add_global_arguments([
|
|||
)
|
||||
|
||||
asresources = gnome.compile_resources(
|
||||
'as-resources', 'data/' + meson.project_name() + '.gresource.xml',
|
||||
'as-resources', 'data/gresource.xml',
|
||||
source_dir: 'data',
|
||||
c_name: 'as'
|
||||
)
|
||||
|
@ -18,42 +21,48 @@ asresources = gnome.compile_resources(
|
|||
executable(
|
||||
meson.project_name(),
|
||||
asresources,
|
||||
|
||||
'src/Stacktrace.vala', #TODO: move into a separate lib
|
||||
|
||||
'src/Build.vala',
|
||||
'src/Application.vala',
|
||||
'src/Desktop.vala',
|
||||
'src/Drawing.vala',
|
||||
'src/Html.vala',
|
||||
'src/Settings.vala',
|
||||
'src/Accounts.vala',
|
||||
'src/ImageCache.vala',
|
||||
'src/Network.vala',
|
||||
'src/Watchlist.vala',
|
||||
'src/Notificator.vala',
|
||||
'src/Utils.vala',
|
||||
'src/Request.vala',
|
||||
'src/InstanceAccount.vala',
|
||||
'src/Services/Streams.vala',
|
||||
'src/Services/Settings.vala',
|
||||
'src/Services/Accounts.vala',
|
||||
'src/Services/IAccountListener.vala',
|
||||
'src/Services/IStreamListener.vala',
|
||||
'src/Services/Cache.vala',
|
||||
'src/Services/Network.vala',
|
||||
'src/API/Account.vala',
|
||||
'src/API/Relationship.vala',
|
||||
'src/API/Mention.vala',
|
||||
'src/API/Tag.vala',
|
||||
'src/API/Status.vala',
|
||||
'src/API/StatusVisibility.vala',
|
||||
'src/API/Visibility.vala',
|
||||
'src/API/Notification.vala',
|
||||
'src/API/NotificationType.vala',
|
||||
'src/API/Attachment.vala',
|
||||
'src/Widgets/Avatar.vala',
|
||||
'src/Widgets/AccountsButton.vala',
|
||||
'src/Widgets/AlignedLabel.vala',
|
||||
'src/Widgets/RichLabel.vala',
|
||||
'src/Widgets/ImageToggleButton.vala',
|
||||
'src/Widgets/AccountsButton.vala',
|
||||
'src/Widgets/Status.vala',
|
||||
'src/Widgets/Account.vala',
|
||||
'src/Widgets/Notification.vala',
|
||||
'src/Widgets/ImageAttachment.vala',
|
||||
'src/Widgets/AttachmentGrid.vala',
|
||||
'src/Widgets/VisibilityPopover.vala',
|
||||
'src/Widgets/Attachment/Box.vala',
|
||||
'src/Widgets/Attachment/Item.vala',
|
||||
'src/Dialogs/ISavedWindow.vala',
|
||||
'src/Dialogs/MainWindow.vala',
|
||||
'src/Dialogs/NewAccount.vala',
|
||||
'src/Dialogs/Compose.vala',
|
||||
'src/Dialogs/Preferences.vala',
|
||||
'src/Dialogs/WatchlistEditor.vala',
|
||||
'src/Views/Abstract.vala',
|
||||
'src/Views/Base.vala',
|
||||
'src/Views/NewAccount.vala',
|
||||
'src/Views/Timeline.vala',
|
||||
'src/Views/Home.vala',
|
||||
'src/Views/Local.vala',
|
||||
|
@ -62,8 +71,6 @@ executable(
|
|||
'src/Views/Direct.vala',
|
||||
'src/Views/ExpandedStatus.vala',
|
||||
'src/Views/Profile.vala',
|
||||
'src/Views/Followers.vala',
|
||||
'src/Views/Following.vala',
|
||||
'src/Views/Favorites.vala',
|
||||
'src/Views/Search.vala',
|
||||
'src/Views/Hashtag.vala',
|
||||
|
@ -73,9 +80,12 @@ executable(
|
|||
dependency('gee-0.8', version: '>=0.8.5'),
|
||||
dependency('granite', version: '>=5.2.0'),
|
||||
dependency('json-glib-1.0'),
|
||||
dependency('libsoup-2.4')
|
||||
dependency('libsoup-2.4'),
|
||||
|
||||
meson.get_compiler('vala').find_library('linux', required: true), #Required by Stacktrace.vala
|
||||
],
|
||||
install: true
|
||||
install: true,
|
||||
link_args: '-export-dynamic'
|
||||
)
|
||||
|
||||
subdir('data')
|
||||
|
|
74
po/POTFILES
|
@ -1,47 +1,53 @@
|
|||
data/com.github.bleakgrey.tootle.desktop.in
|
||||
data/com.github.bleakgrey.tootle.appdata.xml.in
|
||||
src/Accounts.vala
|
||||
src/Build.vala
|
||||
src/Application.vala
|
||||
src/Desktop.vala
|
||||
src/Drawing.vala
|
||||
src/Html.vala
|
||||
src/ImageCache.vala
|
||||
src/Utils.vala
|
||||
src/Request.vala
|
||||
src/InstanceAccount.vala
|
||||
src/MainWindow.vala
|
||||
src/Network.vala
|
||||
src/Watchlist.vala
|
||||
src/Notificator.vala
|
||||
src/Settings.vala
|
||||
src/Services/Notificator.vala
|
||||
src/Services/Settings.vala
|
||||
src/Services/Accounts.vala
|
||||
src/Services/IAccountListener.vala
|
||||
src/Services/Cache.vala
|
||||
src/Services/Network.vala
|
||||
src/Services/Watchlist.vala
|
||||
src/API/Account.vala
|
||||
src/API/Attachment.vala
|
||||
src/API/Relationship.vala
|
||||
src/API/Mention.vala
|
||||
src/API/Tag.vala
|
||||
src/API/Status.vala
|
||||
src/API/Visibility.vala
|
||||
src/API/Notification.vala
|
||||
src/API/NotificationType.vala
|
||||
src/API/Relationship.vala
|
||||
src/API/Status.vala
|
||||
src/API/StatusVisibility.vala
|
||||
src/API/Attachment.vala
|
||||
src/Widgets/Avatar.vala
|
||||
src/Widgets/AccountsButton.vala
|
||||
src/Widgets/AccountWidget.vala
|
||||
src/Widgets/AlignedLabel.vala
|
||||
src/Widgets/AttachmentBox.vala
|
||||
src/Widgets/AttachmentWidget.vala
|
||||
src/Widgets/ImageToggleButton.vala
|
||||
src/Widgets/NotificationWidget.vala
|
||||
src/Widgets/RichLabel.vala
|
||||
src/Widgets/StatusWidget.vala
|
||||
src/Dialogs/NewAccountDialog.vala
|
||||
src/Dialogs/PostDialog.vala
|
||||
src/Dialogs/SettingsDialog.vala
|
||||
src/Dialogs/WatchlistDialog.vala
|
||||
src/Views/AbstractView.vala
|
||||
src/Views/AccountView.vala
|
||||
src/Views/FavoritesView.vala
|
||||
src/Views/DirectView.vala
|
||||
src/Views/FederatedView.vala
|
||||
src/Views/FollowersView.vala
|
||||
src/Views/FollowingView.vala
|
||||
src/Views/HomeView.vala
|
||||
src/Views/LocalView.vala
|
||||
src/Views/NotificationsView.vala
|
||||
src/Views/SearchView.vala
|
||||
src/Views/StatusView.vala
|
||||
src/Views/TimelineView.vala
|
||||
src/Widgets/Status.vala
|
||||
src/Widgets/Notification.vala
|
||||
src/Widgets/VisibilityPopover.vala
|
||||
src/Widgets/Attachment/Box.vala
|
||||
src/Widgets/Attachment/Item.vala
|
||||
src/Dialogs/ISavedWindow.vala
|
||||
src/Dialogs/MainWindow.vala
|
||||
src/Dialogs/Compose.vala
|
||||
src/Dialogs/Preferences.vala
|
||||
src/Dialogs/WatchlistEditor.vala
|
||||
src/Views/Base.vala
|
||||
src/Views/NewAccount.vala
|
||||
src/Views/Timeline.vala
|
||||
src/Views/Home.vala
|
||||
src/Views/Local.vala
|
||||
src/Views/Federated.vala
|
||||
src/Views/Notifications.vala
|
||||
src/Views/Direct.vala
|
||||
src/Views/ExpandedStatus.vala
|
||||
src/Views/Profile.vala
|
||||
src/Views/Favorites.vala
|
||||
src/Views/Search.vala
|
||||
src/Views/Hashtag.vala
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: com.github.bleakgrey.tootle\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-30 19:17+0300\n"
|
||||
"POT-Creation-Date: 2019-09-16 16:00+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -18,7 +18,7 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.desktop.in:4
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7 src/MainWindow.vala:68
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7
|
||||
msgid "Tootle"
|
||||
msgstr ""
|
||||
|
||||
|
@ -42,366 +42,300 @@ msgstr ""
|
|||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:11
|
||||
msgid ""
|
||||
"Tootle is a client for the world’s largest free, open-source, decentralized "
|
||||
"microblogging network with real-time notifications and multiple accounts "
|
||||
"support."
|
||||
"microblogging network with real-time notifications and support for multiple "
|
||||
"accounts."
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:14
|
||||
msgid ""
|
||||
"Mastodon is lovely crafted with power and speed in mind, resulting in a "
|
||||
"free, independent and popular alternative to the centralized social networks."
|
||||
"Mastodon is lovingly crafted with power and speed in mind, resulting in a "
|
||||
"free, independent, and popular alternative to the centralized social "
|
||||
"networks."
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:17
|
||||
msgid ""
|
||||
"Anyone can run a server of Mastodon. Each server hosts individual user "
|
||||
"accounts, the content they produce, and the content they are subscribed. "
|
||||
"Every user can follow each other and share their posts regardless of their "
|
||||
"server."
|
||||
"Anyone can run a Mastodon server. Each server hosts individual user "
|
||||
"accounts, the content they produce, and the content to which they are "
|
||||
"subscribed. Every user can follow each other and share their posts "
|
||||
"regardless of their server."
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:26
|
||||
msgid "bleak_grey"
|
||||
msgstr ""
|
||||
|
||||
#: src/Desktop.vala:10 src/API/Account.vala:123 src/API/Account.vala:142
|
||||
#: src/API/Account.vala:161 src/Dialogs/NewAccountDialog.vala:102
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:80
|
||||
msgid "Added Watchlist"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:81
|
||||
msgid "Added Redraft support"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:82
|
||||
msgid "Added Pinning support"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:83
|
||||
msgid "Added Simplified Chinese and German translations"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:84
|
||||
msgid "Added --hidden Start Flag"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:85
|
||||
msgid "Added Shortcuts and Back mouse button support"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:86
|
||||
msgid "Changed Notifications screen behavior"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:87
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:102
|
||||
msgid "Fixed minor bugs"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:94
|
||||
msgid "Added Russian, French and Polish translations"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:95
|
||||
msgid "Added Direct timeline"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:96
|
||||
msgid "Added support for custom character limit"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:97
|
||||
msgid "Added support for streaming all timelines"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:98
|
||||
msgid "Added tooltips for image attachments"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:99
|
||||
msgid "Added remove action for attachments"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:100
|
||||
msgid "Changed behavior for mentioning users"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:101
|
||||
msgid "Changed behavior for missing image attachments"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:109
|
||||
msgid "Initial release"
|
||||
msgstr ""
|
||||
|
||||
#: src/Desktop.vala:10
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: src/Desktop.vala:46
|
||||
#: src/Desktop.vala:47
|
||||
msgid "Media downloaded"
|
||||
msgstr ""
|
||||
|
||||
#: src/MainWindow.vala:48
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: src/MainWindow.vala:54 src/Dialogs/PostDialog.vala:29
|
||||
msgid "Toot"
|
||||
msgstr ""
|
||||
|
||||
#: src/Network.vala:58
|
||||
msgid "TLS Error"
|
||||
msgstr ""
|
||||
|
||||
#: src/Network.vala:58
|
||||
msgid "Can't ensure secure connection: "
|
||||
msgstr ""
|
||||
|
||||
#: src/Network.vala:66
|
||||
#: src/Services/Accounts.vala:31
|
||||
#, c-format
|
||||
msgid "Error: %s"
|
||||
msgid ""
|
||||
"This instance has invalidated this session. Please sign in again.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/NotificationType.vala:50
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> mentioned you"
|
||||
#: src/Services/Network.vala:86
|
||||
msgid "Network Error"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/NotificationType.vala:52
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted your toot"
|
||||
#: src/API/Visibility.vala:36
|
||||
msgid "Unlisted"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/NotificationType.vala:54
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> favorited your toot"
|
||||
#: src/API/Visibility.vala:38
|
||||
msgid "Followers-only"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Visibility.vala:40
|
||||
msgid "Direct"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Visibility.vala:42
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Visibility.vala:49
|
||||
msgid "Don't post to public timelines"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Visibility.vala:51
|
||||
msgid "Post to followers only"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Visibility.vala:53
|
||||
msgid "Post to mentioned users only"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Visibility.vala:55
|
||||
msgid "Post to public timelines"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/NotificationType.vala:56
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> now follows you"
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> mentioned you</span>"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/NotificationType.vala:58
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> wants to follow you"
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your status</"
|
||||
"span>"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/NotificationType.vala:60
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> posted a toot"
|
||||
msgid "<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted</span>"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:174
|
||||
msgid "Boosted!"
|
||||
#: src/API/NotificationType.vala:62
|
||||
#, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your status</"
|
||||
"span>"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:176
|
||||
msgid "Removed boost"
|
||||
#: src/API/NotificationType.vala:64
|
||||
#, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> now follows you</span>"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:189
|
||||
msgid "Favorited!"
|
||||
#: src/API/NotificationType.vala:66
|
||||
#, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> wants to follow you</"
|
||||
"span>"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:191
|
||||
msgid "Removed from favorites"
|
||||
#: src/API/NotificationType.vala:68
|
||||
#, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a status</span>"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:204
|
||||
msgid "Muted!"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:206
|
||||
msgid "Conversation unmuted"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:219
|
||||
msgid "Pinned!"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:221
|
||||
msgid "Unpinned from profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:231
|
||||
msgid "Poof!"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/StatusVisibility.vala:40
|
||||
msgid "Post to public timelines"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/StatusVisibility.vala:42
|
||||
msgid "Don't post to public timelines"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/StatusVisibility.vala:44
|
||||
msgid "Post to followers only"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/StatusVisibility.vala:46
|
||||
msgid "Post to mentioned users only"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:67
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:71
|
||||
msgid "Favorites"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:75 src/Views/DirectView.vala:12
|
||||
msgid "Direct Messages"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:79 src/Views/SearchView.vala:12
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:83
|
||||
msgid "Watchlist"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:87 src/Dialogs/SettingsDialog.vala:18
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:142
|
||||
msgid "<b>New Account</b>"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:143
|
||||
msgid "Click to add"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountWidget.vala:24 src/Widgets/AttachmentWidget.vala:130
|
||||
#: src/Widgets/StatusWidget.vala:289
|
||||
#: src/Widgets/RichLabel.vala:105 src/Widgets/Status.vala:206
|
||||
#: src/Widgets/Account.vala:28
|
||||
msgid "Open in Browser"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AccountWidget.vala:26 src/Widgets/AttachmentWidget.vala:132
|
||||
#: src/Widgets/StatusWidget.vala:291
|
||||
msgid "Copy Link"
|
||||
#: src/Widgets/AccountsButton.vala:62
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentBox.vala:41
|
||||
msgid "Select media files to add"
|
||||
#: src/Widgets/AccountsButton.vala:67
|
||||
msgid "Favorites"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentBox.vala:44
|
||||
msgid "_Cancel"
|
||||
#: src/Widgets/AccountsButton.vala:71 src/Views/Direct.vala:12
|
||||
msgid "Direct Messages"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentBox.vala:46
|
||||
msgid "_Open"
|
||||
#: src/Widgets/AccountsButton.vala:75 src/Views/Search.vala:12
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:67
|
||||
#, c-format
|
||||
msgid "Click to open %s media"
|
||||
#: src/Widgets/AccountsButton.vala:79 src/Dialogs/WatchlistEditor.vala:99
|
||||
msgid "Watchlist"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:84
|
||||
msgid "Uploading..."
|
||||
#: src/Widgets/AccountsButton.vala:83 src/Dialogs/Preferences.vala:17
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:105
|
||||
msgid "File read error"
|
||||
#: src/Widgets/Status.vala:52
|
||||
msgid "[ Show more ]"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:105
|
||||
#, c-format
|
||||
msgid "Can't read file %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:124
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:134
|
||||
msgid "Download"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:20
|
||||
msgid "Unknown Notification"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:25
|
||||
msgid "Dismiss"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:64
|
||||
msgid "Accept"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:66
|
||||
msgid "Reject"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:84
|
||||
msgid "Boost"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:91
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:98
|
||||
msgid "Reply"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:136
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:151
|
||||
msgid "Toggle content"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:165
|
||||
msgid "[ This post contains sensitive content ]"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:234
|
||||
#: src/Widgets/Status.vala:120
|
||||
msgid "This post can't be boosted"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:287
|
||||
msgid "Unmute Conversation"
|
||||
#: src/Widgets/Status.vala:208 src/Widgets/Account.vala:30
|
||||
msgid "Copy Link"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:287
|
||||
msgid "Mute Conversation"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:293
|
||||
#: src/Widgets/Status.vala:210
|
||||
msgid "Copy Text"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:300
|
||||
#: src/Widgets/Status.vala:217
|
||||
msgid "Unpin from Profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:300
|
||||
#: src/Widgets/Status.vala:217
|
||||
msgid "Pin on Profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:304
|
||||
#: src/Widgets/Status.vala:221
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:308 src/Dialogs/PostDialog.vala:72
|
||||
#: src/Widgets/Status.vala:225 src/Dialogs/Compose.vala:73
|
||||
msgid "Redraft"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:27
|
||||
msgid "New Account"
|
||||
#: src/Widgets/Attachment/Box.vala:28
|
||||
msgid "Select media files to add"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:38
|
||||
msgid "What's an instance?"
|
||||
#: src/Widgets/Attachment/Box.vala:31
|
||||
msgid "_Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:42
|
||||
msgid "Code:"
|
||||
#: src/Widgets/Attachment/Box.vala:33
|
||||
msgid "_Open"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:46
|
||||
msgid "Paste your instance authorization code here"
|
||||
#: src/Dialogs/MainWindow.vala:49
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:49
|
||||
msgid "Add Account"
|
||||
#: src/Dialogs/MainWindow.vala:58
|
||||
msgid "Toot"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:60
|
||||
msgid "Instance:"
|
||||
#: src/Dialogs/MainWindow.vala:63
|
||||
msgid "Toggle content"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:102
|
||||
msgid "Please paste valid instance authorization code"
|
||||
#: src/Dialogs/Compose.vala:69
|
||||
msgid "Post"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:110
|
||||
msgid "Network Error"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:45
|
||||
msgid "Post Visibility"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:52
|
||||
msgid "Add Media"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:61
|
||||
msgid "Spoiler Warning"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:68
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:77
|
||||
msgid "Toot!"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:85
|
||||
msgid "Write your warning here"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:37
|
||||
#: src/Dialogs/Preferences.vala:36
|
||||
msgid "Appearance"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:38
|
||||
#: src/Dialogs/Preferences.vala:37
|
||||
msgid "Dark theme:"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:41
|
||||
#: src/Dialogs/Preferences.vala:40
|
||||
msgid "Timelines"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:42
|
||||
#: src/Dialogs/Preferences.vala:41
|
||||
msgid "Real-time updates:"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:44
|
||||
#: src/Dialogs/Preferences.vala:43
|
||||
msgid "Update public timelines:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -409,140 +343,115 @@ msgstr ""
|
|||
#. grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
||||
#. grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
||||
#. grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
||||
#. var cache_size = new Gtk.SpinButton.with_range (16, 256, 1);
|
||||
#. var cache_size = new SpinButton.with_range (16, 256, 1);
|
||||
#. settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
||||
#. grid.attach (cache_size, 1, i++);
|
||||
#: src/Dialogs/SettingsDialog.vala:55 src/Views/NotificationsView.vala:34
|
||||
#: src/Dialogs/Preferences.vala:54 src/Views/Notifications.vala:33
|
||||
msgid "Notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:56
|
||||
#: src/Dialogs/Preferences.vala:55
|
||||
msgid "Display notifications:"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:58
|
||||
#: src/Dialogs/Preferences.vala:57
|
||||
msgid "Always receive notifications:"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:64
|
||||
#: src/Dialogs/Preferences.vala:63 src/Dialogs/WatchlistEditor.vala:162
|
||||
msgid "_Close"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:20
|
||||
#: src/Dialogs/WatchlistEditor.vala:20
|
||||
msgid ""
|
||||
"You'll be notified when toots from this user appear in your Home timeline."
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:21
|
||||
#: src/Dialogs/WatchlistEditor.vala:21
|
||||
msgid ""
|
||||
"You'll be notified when toots with this hashtag appear in any public "
|
||||
"timelines."
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:137
|
||||
#: src/Dialogs/WatchlistEditor.vala:108
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:138 src/Views/SearchView.vala:100
|
||||
#: src/Dialogs/WatchlistEditor.vala:109 src/Views/Search.vala:100
|
||||
msgid "Hashtags"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:148
|
||||
#: src/Dialogs/WatchlistEditor.vala:122
|
||||
msgid "Add"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AbstractView.vala:59
|
||||
#: src/Views/Base.vala:6
|
||||
msgid "Nothing to see here"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:79
|
||||
msgid "Edit Profile"
|
||||
#: src/Views/NewAccount.vala:91
|
||||
msgid "Instance URL is invalid"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:80
|
||||
msgid "Mention"
|
||||
#: src/Views/NewAccount.vala:133
|
||||
msgid "Please paste a valid authorization code"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:81
|
||||
msgid "Report"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:82 src/Views/AccountView.vala:167
|
||||
msgid "Mute"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:83 src/Views/AccountView.vala:166
|
||||
msgid "Block"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:95
|
||||
msgid "More Actions"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:115
|
||||
msgid "Toots"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:116
|
||||
msgid "Follows"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:120
|
||||
msgid "Followers"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:155
|
||||
msgid "Unfollow"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:159
|
||||
msgid "Follow"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:166
|
||||
msgid "Unblock"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:167
|
||||
msgid "Unmute"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:228
|
||||
msgid "Sent follow request"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:230
|
||||
msgid "Blocked"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:232
|
||||
msgid "Follows you"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:234
|
||||
msgid "Blocking this instance"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:269
|
||||
msgid "User not found"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/FederatedView.vala:12
|
||||
msgid "Federated Timeline"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/HomeView.vala:12 src/Views/TimelineView.vala:36
|
||||
#: src/Views/Timeline.vala:34 src/Views/Home.vala:12
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/LocalView.vala:12
|
||||
#: src/Views/Local.vala:12
|
||||
msgid "Local Timeline"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/SearchView.vala:82
|
||||
#: src/Views/Federated.vala:12
|
||||
msgid "Federated Timeline"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:61
|
||||
#, c-format
|
||||
msgid "%s Posts"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:67
|
||||
#, c-format
|
||||
msgid "%s Follows"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:73
|
||||
#, c-format
|
||||
msgid "%s Followers"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:109
|
||||
msgid "Sent follow request"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:111
|
||||
msgid "Mutually follows you"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:113
|
||||
msgid "Follows you"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:124
|
||||
msgid "Follow back"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:126
|
||||
msgid "Unfollow"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:128
|
||||
msgid "Follow"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Search.vala:82
|
||||
msgid "Accounts"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/SearchView.vala:91
|
||||
#: src/Views/Search.vala:91
|
||||
msgid "Statuses"
|
||||
msgstr ""
|
||||
|
|
896
po/de_DE.po
808
po/fr_FR.po
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: com.github.bleakgrey.tootle\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-30 19:17+0300\n"
|
||||
"POT-Creation-Date: 2019-09-16 16:00+0300\n"
|
||||
"PO-Revision-Date: 2018-06-17 10:07+0200\n"
|
||||
"Last-Translator: Guillaume\n"
|
||||
"Language-Team: none\n"
|
||||
|
@ -20,7 +20,7 @@ msgstr ""
|
|||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.desktop.in:4
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7 src/MainWindow.vala:68
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7
|
||||
msgid "Tootle"
|
||||
msgstr "Tootle"
|
||||
|
||||
|
@ -42,30 +42,34 @@ msgid "Lightning fast client for Mastodon"
|
|||
msgstr "Client léger et rapide pour Mastodon"
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:11
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Tootle is a client for the world’s largest free, open-source, decentralized "
|
||||
"microblogging network with real-time notifications and multiple accounts "
|
||||
"support."
|
||||
"microblogging network with real-time notifications and support for multiple "
|
||||
"accounts."
|
||||
msgstr ""
|
||||
"Tootle est un client pour le plus grand réseau mondial de microblogging "
|
||||
"décentralisé, libre et open-source, avec un support multicomptes et la "
|
||||
"gestion des notifications instantanées."
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:14
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Mastodon is lovely crafted with power and speed in mind, resulting in a "
|
||||
"free, independent and popular alternative to the centralized social networks."
|
||||
"Mastodon is lovingly crafted with power and speed in mind, resulting in a "
|
||||
"free, independent, and popular alternative to the centralized social "
|
||||
"networks."
|
||||
msgstr ""
|
||||
"Mastodon est conçu avec amour dans un objectif de puissance et de vitesse, "
|
||||
"en faisant une alternative libre, indépendante et populaire aux réseaux "
|
||||
"sociaux centralisés."
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:17
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Anyone can run a server of Mastodon. Each server hosts individual user "
|
||||
"accounts, the content they produce, and the content they are subscribed. "
|
||||
"Every user can follow each other and share their posts regardless of their "
|
||||
"server."
|
||||
"Anyone can run a Mastodon server. Each server hosts individual user "
|
||||
"accounts, the content they produce, and the content to which they are "
|
||||
"subscribed. Every user can follow each other and share their posts "
|
||||
"regardless of their server."
|
||||
msgstr ""
|
||||
"N'importe qui peut faire tourner un serveur Mastodon. Chaque serveur héberge "
|
||||
"des comptes utilisateurs individuels, le contenu qu'ils produisent et le "
|
||||
|
@ -76,348 +80,282 @@ msgstr ""
|
|||
msgid "bleak_grey"
|
||||
msgstr ""
|
||||
|
||||
#: src/Desktop.vala:10 src/API/Account.vala:123 src/API/Account.vala:142
|
||||
#: src/API/Account.vala:161 src/Dialogs/NewAccountDialog.vala:102
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:80
|
||||
#, fuzzy
|
||||
msgid "Added Watchlist"
|
||||
msgstr "Liste de veille"
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:81
|
||||
msgid "Added Redraft support"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:82
|
||||
msgid "Added Pinning support"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:83
|
||||
msgid "Added Simplified Chinese and German translations"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:84
|
||||
msgid "Added --hidden Start Flag"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:85
|
||||
msgid "Added Shortcuts and Back mouse button support"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:86
|
||||
msgid "Changed Notifications screen behavior"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:87
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:102
|
||||
msgid "Fixed minor bugs"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:94
|
||||
msgid "Added Russian, French and Polish translations"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:95
|
||||
#, fuzzy
|
||||
msgid "Added Direct timeline"
|
||||
msgstr "Mettre à jour le fil public:"
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:96
|
||||
msgid "Added support for custom character limit"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:97
|
||||
msgid "Added support for streaming all timelines"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:98
|
||||
msgid "Added tooltips for image attachments"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:99
|
||||
msgid "Added remove action for attachments"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:100
|
||||
msgid "Changed behavior for mentioning users"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:101
|
||||
msgid "Changed behavior for missing image attachments"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:109
|
||||
msgid "Initial release"
|
||||
msgstr ""
|
||||
|
||||
#: src/Desktop.vala:10
|
||||
msgid "Error"
|
||||
msgstr "Erreur"
|
||||
|
||||
#: src/Desktop.vala:46
|
||||
#: src/Desktop.vala:47
|
||||
msgid "Media downloaded"
|
||||
msgstr "Médias téléchargés"
|
||||
|
||||
#: src/MainWindow.vala:48
|
||||
msgid "Back"
|
||||
msgstr "Retour"
|
||||
|
||||
#: src/MainWindow.vala:54 src/Dialogs/PostDialog.vala:29
|
||||
msgid "Toot"
|
||||
msgstr "Écrire un message"
|
||||
|
||||
#: src/Network.vala:58
|
||||
msgid "TLS Error"
|
||||
msgstr "Erreur TLS"
|
||||
|
||||
#: src/Network.vala:58
|
||||
msgid "Can't ensure secure connection: "
|
||||
msgstr "Impossible d'assurer une connexion sécurisée:"
|
||||
|
||||
#: src/Network.vala:66
|
||||
#: src/Services/Accounts.vala:31
|
||||
#, c-format
|
||||
msgid "Error: %s"
|
||||
msgstr "Erreur: %s"
|
||||
msgid ""
|
||||
"This instance has invalidated this session. Please sign in again.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/NotificationType.vala:50
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> mentioned you"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> vous a mentionné"
|
||||
#: src/Services/Network.vala:86
|
||||
msgid "Network Error"
|
||||
msgstr "Erreur réseau"
|
||||
|
||||
#: src/API/NotificationType.vala:52
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted your toot"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> a partagé votre pouet"
|
||||
#: src/API/Visibility.vala:36
|
||||
msgid "Unlisted"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/NotificationType.vala:54
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> favorited your toot"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> a ajouté votre pouet à ses favoris"
|
||||
|
||||
#: src/API/NotificationType.vala:56
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> now follows you"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> vous suit désormais"
|
||||
|
||||
#: src/API/NotificationType.vala:58
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> wants to follow you"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> demande à vous suivre"
|
||||
|
||||
#: src/API/NotificationType.vala:60
|
||||
#, fuzzy, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> posted a toot"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> a publié un pouet"
|
||||
|
||||
#: src/API/Status.vala:174
|
||||
msgid "Boosted!"
|
||||
msgstr "Partagé!"
|
||||
|
||||
#: src/API/Status.vala:176
|
||||
msgid "Removed boost"
|
||||
msgstr "Partage annulé"
|
||||
|
||||
#: src/API/Status.vala:189
|
||||
msgid "Favorited!"
|
||||
msgstr "Ajouté aux favoris!"
|
||||
|
||||
#: src/API/Status.vala:191
|
||||
msgid "Removed from favorites"
|
||||
msgstr "Supprimé des favoris"
|
||||
|
||||
#: src/API/Status.vala:204
|
||||
#: src/API/Visibility.vala:38
|
||||
#, fuzzy
|
||||
msgid "Muted!"
|
||||
msgstr "Masqué!"
|
||||
msgid "Followers-only"
|
||||
msgstr "Abonnés"
|
||||
|
||||
#: src/API/Status.vala:206
|
||||
msgid "Conversation unmuted"
|
||||
msgstr "Discussion visible"
|
||||
#: src/API/Visibility.vala:40
|
||||
msgid "Direct"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:219
|
||||
msgid "Pinned!"
|
||||
msgstr "Épinglé!"
|
||||
#: src/API/Visibility.vala:42
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/Status.vala:221
|
||||
#, fuzzy
|
||||
msgid "Unpinned from profile"
|
||||
msgstr "Désépinglé du profil"
|
||||
|
||||
#: src/API/Status.vala:231
|
||||
msgid "Poof!"
|
||||
msgstr "Poof!"
|
||||
|
||||
#: src/API/StatusVisibility.vala:40
|
||||
msgid "Post to public timelines"
|
||||
msgstr "Afficher dans le fil public"
|
||||
|
||||
#: src/API/StatusVisibility.vala:42
|
||||
#: src/API/Visibility.vala:49
|
||||
msgid "Don't post to public timelines"
|
||||
msgstr "Ne pas afficher dans le fil public"
|
||||
|
||||
#: src/API/StatusVisibility.vala:44
|
||||
#: src/API/Visibility.vala:51
|
||||
msgid "Post to followers only"
|
||||
msgstr "Afficher seulement pour les abonnés"
|
||||
|
||||
#: src/API/StatusVisibility.vala:46
|
||||
#: src/API/Visibility.vala:53
|
||||
msgid "Post to mentioned users only"
|
||||
msgstr "N'envoyer qu'aux personnes mentionnées"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:67
|
||||
msgid "Refresh"
|
||||
msgstr "Actualiser"
|
||||
#: src/API/Visibility.vala:55
|
||||
msgid "Post to public timelines"
|
||||
msgstr "Afficher dans le fil public"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:71
|
||||
msgid "Favorites"
|
||||
msgstr "Favoris"
|
||||
#: src/API/NotificationType.vala:56
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> mentioned you</span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> vous a mentionné"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:75 src/Views/DirectView.vala:12
|
||||
msgid "Direct Messages"
|
||||
msgstr "Messages directs"
|
||||
#: src/API/NotificationType.vala:58
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your status</"
|
||||
"span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> a partagé votre pouet"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:79 src/Views/SearchView.vala:12
|
||||
msgid "Search"
|
||||
msgstr "Chercher"
|
||||
#: src/API/NotificationType.vala:60
|
||||
#, fuzzy, c-format
|
||||
msgid "<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted</span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> a partagé"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:83
|
||||
msgid "Watchlist"
|
||||
msgstr "Liste de veille"
|
||||
#: src/API/NotificationType.vala:62
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your status</"
|
||||
"span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> a ajouté votre pouet à ses favoris"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:87 src/Dialogs/SettingsDialog.vala:18
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
#: src/API/NotificationType.vala:64
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> now follows you</span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> vous suit désormais"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:142
|
||||
msgid "<b>New Account</b>"
|
||||
msgstr "<b>Nouveau Compte</b>"
|
||||
#: src/API/NotificationType.vala:66
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> wants to follow you</"
|
||||
"span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> demande à vous suivre"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:143
|
||||
msgid "Click to add"
|
||||
msgstr "Cliquer pour ajouter"
|
||||
#: src/API/NotificationType.vala:68
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a status</span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> a publié un pouet"
|
||||
|
||||
#: src/Widgets/AccountWidget.vala:24 src/Widgets/AttachmentWidget.vala:130
|
||||
#: src/Widgets/StatusWidget.vala:289
|
||||
#: src/Widgets/RichLabel.vala:105 src/Widgets/Status.vala:206
|
||||
#: src/Widgets/Account.vala:28
|
||||
msgid "Open in Browser"
|
||||
msgstr "Ouvrir dans le navigateur"
|
||||
|
||||
#: src/Widgets/AccountWidget.vala:26 src/Widgets/AttachmentWidget.vala:132
|
||||
#: src/Widgets/StatusWidget.vala:291
|
||||
msgid "Copy Link"
|
||||
msgstr "Copier le lien"
|
||||
#: src/Widgets/AccountsButton.vala:62
|
||||
msgid "Refresh"
|
||||
msgstr "Actualiser"
|
||||
|
||||
#: src/Widgets/AttachmentBox.vala:41
|
||||
msgid "Select media files to add"
|
||||
msgstr "Sélectionner les média à ajouter"
|
||||
#: src/Widgets/AccountsButton.vala:67
|
||||
msgid "Favorites"
|
||||
msgstr "Favoris"
|
||||
|
||||
#: src/Widgets/AttachmentBox.vala:44
|
||||
msgid "_Cancel"
|
||||
msgstr "_Annuler"
|
||||
#: src/Widgets/AccountsButton.vala:71 src/Views/Direct.vala:12
|
||||
msgid "Direct Messages"
|
||||
msgstr "Messages directs"
|
||||
|
||||
#: src/Widgets/AttachmentBox.vala:46
|
||||
msgid "_Open"
|
||||
msgstr "_Ouvrir"
|
||||
#: src/Widgets/AccountsButton.vala:75 src/Views/Search.vala:12
|
||||
msgid "Search"
|
||||
msgstr "Chercher"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:67
|
||||
#, c-format
|
||||
msgid "Click to open %s media"
|
||||
msgstr "Cliquer pour ouvrir les médias %s"
|
||||
#: src/Widgets/AccountsButton.vala:79 src/Dialogs/WatchlistEditor.vala:99
|
||||
msgid "Watchlist"
|
||||
msgstr "Liste de veille"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:84
|
||||
msgid "Uploading..."
|
||||
msgstr "Téléchargement en cours..."
|
||||
#: src/Widgets/AccountsButton.vala:83 src/Dialogs/Preferences.vala:17
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:105
|
||||
msgid "File read error"
|
||||
msgstr "Erreur de lecture du fichier"
|
||||
#: src/Widgets/Status.vala:52
|
||||
msgid "[ Show more ]"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:105
|
||||
#, c-format
|
||||
msgid "Can't read file %s: %s"
|
||||
msgstr "Impossible de lire le fichier %s: %s"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:124
|
||||
msgid "Remove"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:134
|
||||
msgid "Download"
|
||||
msgstr "Télécharger"
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:20
|
||||
msgid "Unknown Notification"
|
||||
msgstr "Notification inconnue"
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:25
|
||||
msgid "Dismiss"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:64
|
||||
msgid "Accept"
|
||||
msgstr "Accepter"
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:66
|
||||
msgid "Reject"
|
||||
msgstr "Rejeter"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:84
|
||||
msgid "Boost"
|
||||
msgstr "Partager"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:91
|
||||
msgid "Favorite"
|
||||
msgstr "Ajouter aux favoris"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:98
|
||||
msgid "Reply"
|
||||
msgstr "Répondre"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:136
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> a partagé"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:151
|
||||
msgid "Toggle content"
|
||||
msgstr "Afficher le contenu"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:165
|
||||
msgid "[ This post contains sensitive content ]"
|
||||
msgstr "[ Ce message comporte un contenu sensible ]"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:234
|
||||
#: src/Widgets/Status.vala:120
|
||||
msgid "This post can't be boosted"
|
||||
msgstr "Ce message ne peut être partagé"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:287
|
||||
msgid "Unmute Conversation"
|
||||
msgstr "Rétablir la discussion"
|
||||
#: src/Widgets/Status.vala:208 src/Widgets/Account.vala:30
|
||||
msgid "Copy Link"
|
||||
msgstr "Copier le lien"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:287
|
||||
msgid "Mute Conversation"
|
||||
msgstr "Masquer la discussion"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:293
|
||||
#: src/Widgets/Status.vala:210
|
||||
msgid "Copy Text"
|
||||
msgstr "Copier le texte"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:300
|
||||
#: src/Widgets/Status.vala:217
|
||||
#, fuzzy
|
||||
msgid "Unpin from Profile"
|
||||
msgstr "Désépingler du profil"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:300
|
||||
#: src/Widgets/Status.vala:217
|
||||
#, fuzzy
|
||||
msgid "Pin on Profile"
|
||||
msgstr "Épingler sur le profil"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:304
|
||||
#: src/Widgets/Status.vala:221
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:308 src/Dialogs/PostDialog.vala:72
|
||||
#: src/Widgets/Status.vala:225 src/Dialogs/Compose.vala:73
|
||||
msgid "Redraft"
|
||||
msgstr "Réecrire"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:27
|
||||
msgid "New Account"
|
||||
msgstr "Nouveau compte"
|
||||
#: src/Widgets/Attachment/Box.vala:28
|
||||
msgid "Select media files to add"
|
||||
msgstr "Sélectionner les média à ajouter"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:38
|
||||
msgid "What's an instance?"
|
||||
msgstr "Qu'est une instance?"
|
||||
#: src/Widgets/Attachment/Box.vala:31
|
||||
msgid "_Cancel"
|
||||
msgstr "_Annuler"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:42
|
||||
msgid "Code:"
|
||||
msgstr "Code:"
|
||||
#: src/Widgets/Attachment/Box.vala:33
|
||||
msgid "_Open"
|
||||
msgstr "_Ouvrir"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:46
|
||||
msgid "Paste your instance authorization code here"
|
||||
msgstr "Coller le code d'autorisation de votre instance ici"
|
||||
#: src/Dialogs/MainWindow.vala:49
|
||||
msgid "Back"
|
||||
msgstr "Retour"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:49
|
||||
msgid "Add Account"
|
||||
msgstr "Ajouter un compte"
|
||||
#: src/Dialogs/MainWindow.vala:58
|
||||
msgid "Toot"
|
||||
msgstr "Écrire un message"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:60
|
||||
msgid "Instance:"
|
||||
msgstr "Instance:"
|
||||
#: src/Dialogs/MainWindow.vala:63
|
||||
msgid "Toggle content"
|
||||
msgstr "Afficher le contenu"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:102
|
||||
msgid "Please paste valid instance authorization code"
|
||||
msgstr "Veuillez coller un code d'autorisation valide"
|
||||
#: src/Dialogs/Compose.vala:69
|
||||
msgid "Post"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:110
|
||||
msgid "Network Error"
|
||||
msgstr "Erreur réseau"
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:45
|
||||
msgid "Post Visibility"
|
||||
msgstr "Visibilité du message"
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:52
|
||||
msgid "Add Media"
|
||||
msgstr "Ajouter un média"
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:61
|
||||
msgid "Spoiler Warning"
|
||||
msgstr "Ajouter un avertissement"
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:68
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:77
|
||||
msgid "Toot!"
|
||||
msgstr "Envoyer le message!"
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:85
|
||||
msgid "Write your warning here"
|
||||
msgstr "Écrire votre avertissement ici"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:37
|
||||
#: src/Dialogs/Preferences.vala:36
|
||||
msgid "Appearance"
|
||||
msgstr "Apparence"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:38
|
||||
#: src/Dialogs/Preferences.vala:37
|
||||
msgid "Dark theme:"
|
||||
msgstr "Thème sombre:"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:41
|
||||
#: src/Dialogs/Preferences.vala:40
|
||||
msgid "Timelines"
|
||||
msgstr "Fils"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:42
|
||||
#: src/Dialogs/Preferences.vala:41
|
||||
msgid "Real-time updates:"
|
||||
msgstr "Mises à jour instantanées:"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:44
|
||||
#: src/Dialogs/Preferences.vala:43
|
||||
msgid "Update public timelines:"
|
||||
msgstr "Mettre à jour le fil public:"
|
||||
|
||||
|
@ -425,144 +363,290 @@ msgstr "Mettre à jour le fil public:"
|
|||
#. grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
||||
#. grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
||||
#. grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
||||
#. var cache_size = new Gtk.SpinButton.with_range (16, 256, 1);
|
||||
#. var cache_size = new SpinButton.with_range (16, 256, 1);
|
||||
#. settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
||||
#. grid.attach (cache_size, 1, i++);
|
||||
#: src/Dialogs/SettingsDialog.vala:55 src/Views/NotificationsView.vala:34
|
||||
#: src/Dialogs/Preferences.vala:54 src/Views/Notifications.vala:33
|
||||
msgid "Notifications"
|
||||
msgstr "Notifications"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:56
|
||||
#: src/Dialogs/Preferences.vala:55
|
||||
msgid "Display notifications:"
|
||||
msgstr "Afficher les notifications:"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:58
|
||||
#: src/Dialogs/Preferences.vala:57
|
||||
msgid "Always receive notifications:"
|
||||
msgstr "Toujours recevoir les notifications:"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:64
|
||||
#: src/Dialogs/Preferences.vala:63 src/Dialogs/WatchlistEditor.vala:162
|
||||
msgid "_Close"
|
||||
msgstr "_Fermer"
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:20
|
||||
#: src/Dialogs/WatchlistEditor.vala:20
|
||||
msgid ""
|
||||
"You'll be notified when toots from this user appear in your Home timeline."
|
||||
msgstr "Vous serez averti lorsque des messages de cet utilisateur apparaîtront sur votre fil d'actualité."
|
||||
msgstr ""
|
||||
"Vous serez averti lorsque des messages de cet utilisateur apparaîtront sur "
|
||||
"votre fil d'actualité."
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:21
|
||||
#: src/Dialogs/WatchlistEditor.vala:21
|
||||
msgid ""
|
||||
"You'll be notified when toots with this hashtag appear in any public "
|
||||
"timelines."
|
||||
msgstr "Vous serez averti lorsque des toots contenant ce hashtag apparaîtront dans n'importe quel fil public."
|
||||
msgstr ""
|
||||
"Vous serez averti lorsque des toots contenant ce hashtag apparaîtront dans "
|
||||
"n'importe quel fil public."
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:137
|
||||
#: src/Dialogs/WatchlistEditor.vala:108
|
||||
msgid "Users"
|
||||
msgstr "Utilisateurs"
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:138 src/Views/SearchView.vala:100
|
||||
#: src/Dialogs/WatchlistEditor.vala:109 src/Views/Search.vala:100
|
||||
msgid "Hashtags"
|
||||
msgstr "Hashtags"
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:148
|
||||
#: src/Dialogs/WatchlistEditor.vala:122
|
||||
msgid "Add"
|
||||
msgstr "Ajouter"
|
||||
|
||||
#: src/Views/AbstractView.vala:59
|
||||
#: src/Views/Base.vala:6
|
||||
msgid "Nothing to see here"
|
||||
msgstr "Rien à voir par ici"
|
||||
|
||||
#: src/Views/AccountView.vala:79
|
||||
msgid "Edit Profile"
|
||||
msgstr "Éditer votre profil"
|
||||
#: src/Views/NewAccount.vala:91
|
||||
msgid "Instance URL is invalid"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:80
|
||||
msgid "Mention"
|
||||
msgstr "Mentionner"
|
||||
#: src/Views/NewAccount.vala:133
|
||||
#, fuzzy
|
||||
msgid "Please paste a valid authorization code"
|
||||
msgstr "Veuillez coller un code d'autorisation valide"
|
||||
|
||||
#: src/Views/AccountView.vala:81
|
||||
msgid "Report"
|
||||
msgstr "Signaler"
|
||||
|
||||
#: src/Views/AccountView.vala:82 src/Views/AccountView.vala:167
|
||||
msgid "Mute"
|
||||
msgstr "Masquer"
|
||||
|
||||
#: src/Views/AccountView.vala:83 src/Views/AccountView.vala:166
|
||||
msgid "Block"
|
||||
msgstr "Bloquer"
|
||||
|
||||
#: src/Views/AccountView.vala:95
|
||||
msgid "More Actions"
|
||||
msgstr "Plus d'actions"
|
||||
|
||||
#: src/Views/AccountView.vala:115
|
||||
msgid "Toots"
|
||||
msgstr "Pouets"
|
||||
|
||||
#: src/Views/AccountView.vala:116
|
||||
msgid "Follows"
|
||||
msgstr "Abonnements"
|
||||
|
||||
#: src/Views/AccountView.vala:120
|
||||
msgid "Followers"
|
||||
msgstr "Abonnés"
|
||||
|
||||
#: src/Views/AccountView.vala:155
|
||||
msgid "Unfollow"
|
||||
msgstr "Ne plus suivre"
|
||||
|
||||
#: src/Views/AccountView.vala:159
|
||||
msgid "Follow"
|
||||
msgstr "Suivre"
|
||||
|
||||
#: src/Views/AccountView.vala:166
|
||||
msgid "Unblock"
|
||||
msgstr "Débloquer"
|
||||
|
||||
#: src/Views/AccountView.vala:167
|
||||
msgid "Unmute"
|
||||
msgstr "Rétablir"
|
||||
|
||||
#: src/Views/AccountView.vala:228
|
||||
msgid "Sent follow request"
|
||||
msgstr "Demande de suivi envoyée"
|
||||
|
||||
#: src/Views/AccountView.vala:230
|
||||
msgid "Blocked"
|
||||
msgstr "Bloqué"
|
||||
|
||||
#: src/Views/AccountView.vala:232
|
||||
msgid "Follows you"
|
||||
msgstr "Vous suis actuellement"
|
||||
|
||||
#: src/Views/AccountView.vala:234
|
||||
msgid "Blocking this instance"
|
||||
msgstr "Blocage de cette instance"
|
||||
|
||||
#: src/Views/AccountView.vala:269
|
||||
msgid "User not found"
|
||||
msgstr "Utilisateur non trouvé"
|
||||
|
||||
#: src/Views/FederatedView.vala:12
|
||||
msgid "Federated Timeline"
|
||||
msgstr "Fil global"
|
||||
|
||||
#: src/Views/HomeView.vala:12 src/Views/TimelineView.vala:36
|
||||
#: src/Views/Timeline.vala:34 src/Views/Home.vala:12
|
||||
msgid "Home"
|
||||
msgstr "Accueil"
|
||||
|
||||
#: src/Views/LocalView.vala:12
|
||||
#: src/Views/Local.vala:12
|
||||
msgid "Local Timeline"
|
||||
msgstr "Fil local"
|
||||
|
||||
#: src/Views/SearchView.vala:82
|
||||
#: src/Views/Federated.vala:12
|
||||
msgid "Federated Timeline"
|
||||
msgstr "Fil global"
|
||||
|
||||
#: src/Views/Profile.vala:61
|
||||
#, c-format
|
||||
msgid "%s Posts"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:67
|
||||
#, fuzzy, c-format
|
||||
msgid "%s Follows"
|
||||
msgstr "Abonnements"
|
||||
|
||||
#: src/Views/Profile.vala:73
|
||||
#, fuzzy, c-format
|
||||
msgid "%s Followers"
|
||||
msgstr "Abonnés"
|
||||
|
||||
#: src/Views/Profile.vala:109
|
||||
msgid "Sent follow request"
|
||||
msgstr "Demande de suivi envoyée"
|
||||
|
||||
#: src/Views/Profile.vala:111
|
||||
#, fuzzy
|
||||
msgid "Mutually follows you"
|
||||
msgstr "Vous suis actuellement"
|
||||
|
||||
#: src/Views/Profile.vala:113
|
||||
msgid "Follows you"
|
||||
msgstr "Vous suis actuellement"
|
||||
|
||||
#: src/Views/Profile.vala:124
|
||||
#, fuzzy
|
||||
msgid "Follow back"
|
||||
msgstr "Suivre"
|
||||
|
||||
#: src/Views/Profile.vala:126
|
||||
msgid "Unfollow"
|
||||
msgstr "Ne plus suivre"
|
||||
|
||||
#: src/Views/Profile.vala:128
|
||||
msgid "Follow"
|
||||
msgstr "Suivre"
|
||||
|
||||
#: src/Views/Search.vala:82
|
||||
msgid "Accounts"
|
||||
msgstr "Comptes"
|
||||
|
||||
#: src/Views/SearchView.vala:91
|
||||
#: src/Views/Search.vala:91
|
||||
msgid "Statuses"
|
||||
msgstr "Statuts"
|
||||
|
||||
#~ msgid "TLS Error"
|
||||
#~ msgstr "Erreur TLS"
|
||||
|
||||
#~ msgid "Can't ensure secure connection: "
|
||||
#~ msgstr "Impossible d'assurer une connexion sécurisée:"
|
||||
|
||||
#~ msgid "Error: %s"
|
||||
#~ msgstr "Erreur: %s"
|
||||
|
||||
#~ msgid "Boosted!"
|
||||
#~ msgstr "Partagé!"
|
||||
|
||||
#~ msgid "Removed boost"
|
||||
#~ msgstr "Partage annulé"
|
||||
|
||||
#~ msgid "Favorited!"
|
||||
#~ msgstr "Ajouté aux favoris!"
|
||||
|
||||
#~ msgid "Removed from favorites"
|
||||
#~ msgstr "Supprimé des favoris"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "Muted!"
|
||||
#~ msgstr "Masqué!"
|
||||
|
||||
#~ msgid "Conversation unmuted"
|
||||
#~ msgstr "Discussion visible"
|
||||
|
||||
#~ msgid "Pinned!"
|
||||
#~ msgstr "Épinglé!"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "Unpinned from profile"
|
||||
#~ msgstr "Désépinglé du profil"
|
||||
|
||||
#~ msgid "Poof!"
|
||||
#~ msgstr "Poof!"
|
||||
|
||||
#~ msgid "<b>New Account</b>"
|
||||
#~ msgstr "<b>Nouveau Compte</b>"
|
||||
|
||||
#~ msgid "Click to add"
|
||||
#~ msgstr "Cliquer pour ajouter"
|
||||
|
||||
#~ msgid "Click to open %s media"
|
||||
#~ msgstr "Cliquer pour ouvrir les médias %s"
|
||||
|
||||
#~ msgid "Uploading..."
|
||||
#~ msgstr "Téléchargement en cours..."
|
||||
|
||||
#~ msgid "File read error"
|
||||
#~ msgstr "Erreur de lecture du fichier"
|
||||
|
||||
#~ msgid "Can't read file %s: %s"
|
||||
#~ msgstr "Impossible de lire le fichier %s: %s"
|
||||
|
||||
#~ msgid "Remove"
|
||||
#~ msgstr "Supprimer"
|
||||
|
||||
#~ msgid "Download"
|
||||
#~ msgstr "Télécharger"
|
||||
|
||||
#~ msgid "Unknown Notification"
|
||||
#~ msgstr "Notification inconnue"
|
||||
|
||||
#~ msgid "Dismiss"
|
||||
#~ msgstr "Annuler"
|
||||
|
||||
#~ msgid "Accept"
|
||||
#~ msgstr "Accepter"
|
||||
|
||||
#~ msgid "Reject"
|
||||
#~ msgstr "Rejeter"
|
||||
|
||||
#~ msgid "Boost"
|
||||
#~ msgstr "Partager"
|
||||
|
||||
#~ msgid "Favorite"
|
||||
#~ msgstr "Ajouter aux favoris"
|
||||
|
||||
#~ msgid "Reply"
|
||||
#~ msgstr "Répondre"
|
||||
|
||||
#~ msgid "[ This post contains sensitive content ]"
|
||||
#~ msgstr "[ Ce message comporte un contenu sensible ]"
|
||||
|
||||
#~ msgid "Unmute Conversation"
|
||||
#~ msgstr "Rétablir la discussion"
|
||||
|
||||
#~ msgid "Mute Conversation"
|
||||
#~ msgstr "Masquer la discussion"
|
||||
|
||||
#~ msgid "New Account"
|
||||
#~ msgstr "Nouveau compte"
|
||||
|
||||
#~ msgid "What's an instance?"
|
||||
#~ msgstr "Qu'est une instance?"
|
||||
|
||||
#~ msgid "Code:"
|
||||
#~ msgstr "Code:"
|
||||
|
||||
#~ msgid "Paste your instance authorization code here"
|
||||
#~ msgstr "Coller le code d'autorisation de votre instance ici"
|
||||
|
||||
#~ msgid "Add Account"
|
||||
#~ msgstr "Ajouter un compte"
|
||||
|
||||
#~ msgid "Instance:"
|
||||
#~ msgstr "Instance:"
|
||||
|
||||
#~ msgid "Post Visibility"
|
||||
#~ msgstr "Visibilité du message"
|
||||
|
||||
#~ msgid "Add Media"
|
||||
#~ msgstr "Ajouter un média"
|
||||
|
||||
#~ msgid "Spoiler Warning"
|
||||
#~ msgstr "Ajouter un avertissement"
|
||||
|
||||
#~ msgid "Cancel"
|
||||
#~ msgstr "Annuler"
|
||||
|
||||
#~ msgid "Toot!"
|
||||
#~ msgstr "Envoyer le message!"
|
||||
|
||||
#~ msgid "Write your warning here"
|
||||
#~ msgstr "Écrire votre avertissement ici"
|
||||
|
||||
#~ msgid "Edit Profile"
|
||||
#~ msgstr "Éditer votre profil"
|
||||
|
||||
#~ msgid "Mention"
|
||||
#~ msgstr "Mentionner"
|
||||
|
||||
#~ msgid "Report"
|
||||
#~ msgstr "Signaler"
|
||||
|
||||
#~ msgid "Mute"
|
||||
#~ msgstr "Masquer"
|
||||
|
||||
#~ msgid "Block"
|
||||
#~ msgstr "Bloquer"
|
||||
|
||||
#~ msgid "More Actions"
|
||||
#~ msgstr "Plus d'actions"
|
||||
|
||||
#~ msgid "Toots"
|
||||
#~ msgstr "Pouets"
|
||||
|
||||
#~ msgid "Unblock"
|
||||
#~ msgstr "Débloquer"
|
||||
|
||||
#~ msgid "Unmute"
|
||||
#~ msgstr "Rétablir"
|
||||
|
||||
#~ msgid "Blocked"
|
||||
#~ msgstr "Bloqué"
|
||||
|
||||
#~ msgid "Blocking this instance"
|
||||
#~ msgstr "Blocage de cette instance"
|
||||
|
||||
#~ msgid "User not found"
|
||||
#~ msgstr "Utilisateur non trouvé"
|
||||
|
||||
#~ msgid "Conversation muted"
|
||||
#~ msgstr "Discussion masquée"
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
cd .
|
||||
cd build
|
||||
ninja com.github.bleakgrey.tootle-update-po
|
734
po/ru.po
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: com.github.bleakgrey.tootle\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-30 19:17+0300\n"
|
||||
"POT-Creation-Date: 2019-09-16 16:00+0300\n"
|
||||
"PO-Revision-Date: 2018-05-10 00:35+0300\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
@ -19,7 +19,7 @@ msgstr ""
|
|||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.desktop.in:4
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7 src/MainWindow.vala:68
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7
|
||||
msgid "Tootle"
|
||||
msgstr ""
|
||||
|
||||
|
@ -41,30 +41,34 @@ msgid "Lightning fast client for Mastodon"
|
|||
msgstr "Молниеносный клиент для Мастодонта"
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:11
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Tootle is a client for the world’s largest free, open-source, decentralized "
|
||||
"microblogging network with real-time notifications and multiple accounts "
|
||||
"support."
|
||||
"microblogging network with real-time notifications and support for multiple "
|
||||
"accounts."
|
||||
msgstr ""
|
||||
"Tootle - клиент для крупнейшей в мире свободной, децентрализованной сети "
|
||||
"микроблогинга с открытым исходным кодом. Он поддерживает уведомления в "
|
||||
"реальном времени и несколько рабочих аккаунтов."
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:14
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Mastodon is lovely crafted with power and speed in mind, resulting in a "
|
||||
"free, independent and popular alternative to the centralized social networks."
|
||||
"Mastodon is lovingly crafted with power and speed in mind, resulting in a "
|
||||
"free, independent, and popular alternative to the centralized social "
|
||||
"networks."
|
||||
msgstr ""
|
||||
"Мастодонт был создан с учётом множества возможностей и скорости, что привело "
|
||||
"к созданию свободной альтернативы популярным централизованным социальным "
|
||||
"сетям."
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:17
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Anyone can run a server of Mastodon. Each server hosts individual user "
|
||||
"accounts, the content they produce, and the content they are subscribed. "
|
||||
"Every user can follow each other and share their posts regardless of their "
|
||||
"server."
|
||||
"Anyone can run a Mastodon server. Each server hosts individual user "
|
||||
"accounts, the content they produce, and the content to which they are "
|
||||
"subscribed. Every user can follow each other and share their posts "
|
||||
"regardless of their server."
|
||||
msgstr ""
|
||||
"Каждый может запустить свою копию Мастодонта. Сервер хранит аккаунты "
|
||||
"пользователей, контент, который они создают, и контент, на который они "
|
||||
|
@ -75,344 +79,280 @@ msgstr ""
|
|||
msgid "bleak_grey"
|
||||
msgstr ""
|
||||
|
||||
#: src/Desktop.vala:10 src/API/Account.vala:123 src/API/Account.vala:142
|
||||
#: src/API/Account.vala:161 src/Dialogs/NewAccountDialog.vala:102
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:80
|
||||
#, fuzzy
|
||||
msgid "Added Watchlist"
|
||||
msgstr "Список Наблюдения"
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:81
|
||||
msgid "Added Redraft support"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:82
|
||||
msgid "Added Pinning support"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:83
|
||||
msgid "Added Simplified Chinese and German translations"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:84
|
||||
msgid "Added --hidden Start Flag"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:85
|
||||
msgid "Added Shortcuts and Back mouse button support"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:86
|
||||
msgid "Changed Notifications screen behavior"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:87
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:102
|
||||
msgid "Fixed minor bugs"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:94
|
||||
msgid "Added Russian, French and Polish translations"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:95
|
||||
#, fuzzy
|
||||
msgid "Added Direct timeline"
|
||||
msgstr "Обновлять публичные ленты:"
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:96
|
||||
msgid "Added support for custom character limit"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:97
|
||||
msgid "Added support for streaming all timelines"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:98
|
||||
msgid "Added tooltips for image attachments"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:99
|
||||
msgid "Added remove action for attachments"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:100
|
||||
msgid "Changed behavior for mentioning users"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:101
|
||||
msgid "Changed behavior for missing image attachments"
|
||||
msgstr ""
|
||||
|
||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:109
|
||||
msgid "Initial release"
|
||||
msgstr ""
|
||||
|
||||
#: src/Desktop.vala:10
|
||||
msgid "Error"
|
||||
msgstr "Ошибка"
|
||||
|
||||
#: src/Desktop.vala:46
|
||||
#: src/Desktop.vala:47
|
||||
msgid "Media downloaded"
|
||||
msgstr "Медиаконтент загружен"
|
||||
|
||||
#: src/MainWindow.vala:48
|
||||
msgid "Back"
|
||||
msgstr "Назад"
|
||||
|
||||
#: src/MainWindow.vala:54 src/Dialogs/PostDialog.vala:29
|
||||
msgid "Toot"
|
||||
msgstr "Статус"
|
||||
|
||||
#: src/Network.vala:58
|
||||
msgid "TLS Error"
|
||||
msgstr "Ошибка TLS"
|
||||
|
||||
#: src/Network.vala:58
|
||||
msgid "Can't ensure secure connection: "
|
||||
msgstr "Не удалось установить безопасное соединение:"
|
||||
|
||||
#: src/Network.vala:66
|
||||
#: src/Services/Accounts.vala:31
|
||||
#, c-format
|
||||
msgid "Error: %s"
|
||||
msgstr "Ошибка: %s"
|
||||
msgid ""
|
||||
"This instance has invalidated this session. Please sign in again.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#: src/API/NotificationType.vala:50
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> mentioned you"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> упомянул вас"
|
||||
|
||||
#: src/API/NotificationType.vala:52
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted your toot"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> продвинул ваш статус"
|
||||
|
||||
#: src/API/NotificationType.vala:54
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> favorited your toot"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> понравился ваш статус"
|
||||
|
||||
#: src/API/NotificationType.vala:56
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> now follows you"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> подписался на вас"
|
||||
|
||||
#: src/API/NotificationType.vala:58
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> wants to follow you"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> хочет на вас подписаться"
|
||||
|
||||
#: src/API/NotificationType.vala:60
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> posted a toot"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> опубликовал статус"
|
||||
|
||||
#: src/API/Status.vala:174
|
||||
msgid "Boosted!"
|
||||
msgstr "Продвинуто!"
|
||||
|
||||
#: src/API/Status.vala:176
|
||||
msgid "Removed boost"
|
||||
msgstr "Продвижение отменено"
|
||||
|
||||
#: src/API/Status.vala:189
|
||||
msgid "Favorited!"
|
||||
msgstr "Добавлено в понравившиеся!"
|
||||
|
||||
#: src/API/Status.vala:191
|
||||
msgid "Removed from favorites"
|
||||
msgstr "Удалено из понравившихся"
|
||||
|
||||
#: src/API/Status.vala:204
|
||||
msgid "Muted!"
|
||||
msgstr "Заглушено!"
|
||||
|
||||
#: src/API/Status.vala:206
|
||||
msgid "Conversation unmuted"
|
||||
msgstr "Переписка включена"
|
||||
|
||||
#: src/API/Status.vala:219
|
||||
msgid "Pinned!"
|
||||
msgstr "Закреплено!"
|
||||
|
||||
#: src/API/Status.vala:221
|
||||
msgid "Unpinned from profile"
|
||||
msgstr "Откреплено от профиля"
|
||||
|
||||
#: src/API/Status.vala:231
|
||||
msgid "Poof!"
|
||||
msgstr "Вжух!"
|
||||
|
||||
#: src/API/StatusVisibility.vala:40
|
||||
msgid "Post to public timelines"
|
||||
msgstr "Видно в публичных лентах"
|
||||
|
||||
#: src/API/StatusVisibility.vala:42
|
||||
msgid "Don't post to public timelines"
|
||||
msgstr "Не видно в публичных лентах"
|
||||
|
||||
#: src/API/StatusVisibility.vala:44
|
||||
msgid "Post to followers only"
|
||||
msgstr "Только для подписчиков"
|
||||
|
||||
#: src/API/StatusVisibility.vala:46
|
||||
msgid "Post to mentioned users only"
|
||||
msgstr "Только для упомянутых"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:67
|
||||
msgid "Refresh"
|
||||
msgstr "Обновить"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:71
|
||||
msgid "Favorites"
|
||||
msgstr "Понравившиеся"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:75 src/Views/DirectView.vala:12
|
||||
msgid "Direct Messages"
|
||||
msgstr "Личные Сообщения"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:79 src/Views/SearchView.vala:12
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:83
|
||||
msgid "Watchlist"
|
||||
msgstr "Список Наблюдения"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:87 src/Dialogs/SettingsDialog.vala:18
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:142
|
||||
msgid "<b>New Account</b>"
|
||||
msgstr "<b>Новый аккаунт</b>"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:143
|
||||
msgid "Click to add"
|
||||
msgstr "Нажмите, чтобы добавить"
|
||||
|
||||
#: src/Widgets/AccountWidget.vala:24 src/Widgets/AttachmentWidget.vala:130
|
||||
#: src/Widgets/StatusWidget.vala:289
|
||||
msgid "Open in Browser"
|
||||
msgstr "Открыть в Браузере"
|
||||
|
||||
#: src/Widgets/AccountWidget.vala:26 src/Widgets/AttachmentWidget.vala:132
|
||||
#: src/Widgets/StatusWidget.vala:291
|
||||
msgid "Copy Link"
|
||||
msgstr "Скопировать Ссылку"
|
||||
|
||||
#: src/Widgets/AttachmentBox.vala:41
|
||||
msgid "Select media files to add"
|
||||
msgstr "Выберите файлы для добавления"
|
||||
|
||||
#: src/Widgets/AttachmentBox.vala:44
|
||||
msgid "_Cancel"
|
||||
msgstr "_Отмена"
|
||||
|
||||
#: src/Widgets/AttachmentBox.vala:46
|
||||
msgid "_Open"
|
||||
msgstr "_Выбрать"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:67
|
||||
#, c-format
|
||||
msgid "Click to open %s media"
|
||||
msgstr "Нажмите, чтобы открыть %s"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:84
|
||||
msgid "Uploading..."
|
||||
msgstr "Загрузка..."
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:105
|
||||
msgid "File read error"
|
||||
msgstr "Ошибка чтения файла"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:105
|
||||
#, c-format
|
||||
msgid "Can't read file %s: %s"
|
||||
msgstr "Не удалось прочитать файл %s: %s"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:124
|
||||
msgid "Remove"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: src/Widgets/AttachmentWidget.vala:134
|
||||
msgid "Download"
|
||||
msgstr "Скачать"
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:20
|
||||
msgid "Unknown Notification"
|
||||
msgstr "Неизвестное уведомление"
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:25
|
||||
msgid "Dismiss"
|
||||
msgstr "Скрыть"
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:64
|
||||
msgid "Accept"
|
||||
msgstr "Принять"
|
||||
|
||||
#: src/Widgets/NotificationWidget.vala:66
|
||||
msgid "Reject"
|
||||
msgstr "Отклонить"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:84
|
||||
msgid "Boost"
|
||||
msgstr "Продвинуть"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:91
|
||||
msgid "Favorite"
|
||||
msgstr "Нравится"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:98
|
||||
msgid "Reply"
|
||||
msgstr "Ответить"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:136
|
||||
#, c-format
|
||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> продвинул"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:151
|
||||
msgid "Toggle content"
|
||||
msgstr "Развернуть"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:165
|
||||
msgid "[ This post contains sensitive content ]"
|
||||
msgstr "[ Данный статус содержит чувствительный контент ]"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:234
|
||||
msgid "This post can't be boosted"
|
||||
msgstr "Этот статус нельзя продвинуть"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:287
|
||||
msgid "Unmute Conversation"
|
||||
msgstr "Включить переписку"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:287
|
||||
msgid "Mute Conversation"
|
||||
msgstr "Заглушить Переписку"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:293
|
||||
msgid "Copy Text"
|
||||
msgstr "Скопировать Текст"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:300
|
||||
msgid "Unpin from Profile"
|
||||
msgstr "Открепить от Профиля"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:300
|
||||
msgid "Pin on Profile"
|
||||
msgstr "Закрепить на Профиле"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:304
|
||||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: src/Widgets/StatusWidget.vala:308 src/Dialogs/PostDialog.vala:72
|
||||
msgid "Redraft"
|
||||
msgstr "Исправить"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:27
|
||||
msgid "New Account"
|
||||
msgstr "Новый аккаунт"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:38
|
||||
msgid "What's an instance?"
|
||||
msgstr "Что такое узел?"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:42
|
||||
msgid "Code:"
|
||||
msgstr "Код:"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:46
|
||||
msgid "Paste your instance authorization code here"
|
||||
msgstr "Вставьте свой код авторизации здесь"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:49
|
||||
msgid "Add Account"
|
||||
msgstr "Добавить Аккаунт"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:60
|
||||
msgid "Instance:"
|
||||
msgstr "Узел:"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:102
|
||||
msgid "Please paste valid instance authorization code"
|
||||
msgstr "Пожалуйста, вставьте корректный код авторизации"
|
||||
|
||||
#: src/Dialogs/NewAccountDialog.vala:110
|
||||
#: src/Services/Network.vala:86
|
||||
msgid "Network Error"
|
||||
msgstr "Сетевая Ошибка"
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:45
|
||||
msgid "Post Visibility"
|
||||
msgstr "Видимость Статуса"
|
||||
#: src/API/Visibility.vala:36
|
||||
msgid "Unlisted"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:52
|
||||
msgid "Add Media"
|
||||
msgstr "Добавить Медиаконтент"
|
||||
#: src/API/Visibility.vala:38
|
||||
#, fuzzy
|
||||
msgid "Followers-only"
|
||||
msgstr "Подписчиков"
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:61
|
||||
msgid "Spoiler Warning"
|
||||
msgstr "Спойлер"
|
||||
#: src/API/Visibility.vala:40
|
||||
msgid "Direct"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:68
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
#: src/API/Visibility.vala:42
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:77
|
||||
msgid "Toot!"
|
||||
msgstr "Отправить!"
|
||||
#: src/API/Visibility.vala:49
|
||||
msgid "Don't post to public timelines"
|
||||
msgstr "Не видно в публичных лентах"
|
||||
|
||||
#: src/Dialogs/PostDialog.vala:85
|
||||
msgid "Write your warning here"
|
||||
msgstr "Напишите здесь предупреждение"
|
||||
#: src/API/Visibility.vala:51
|
||||
msgid "Post to followers only"
|
||||
msgstr "Только для подписчиков"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:37
|
||||
#: src/API/Visibility.vala:53
|
||||
msgid "Post to mentioned users only"
|
||||
msgstr "Только для упомянутых"
|
||||
|
||||
#: src/API/Visibility.vala:55
|
||||
msgid "Post to public timelines"
|
||||
msgstr "Видно в публичных лентах"
|
||||
|
||||
#: src/API/NotificationType.vala:56
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> mentioned you</span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> упомянул вас"
|
||||
|
||||
#: src/API/NotificationType.vala:58
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your status</"
|
||||
"span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> продвинул ваш статус"
|
||||
|
||||
#: src/API/NotificationType.vala:60
|
||||
#, fuzzy, c-format
|
||||
msgid "<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted</span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> продвинул"
|
||||
|
||||
#: src/API/NotificationType.vala:62
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your status</"
|
||||
"span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> понравился ваш статус"
|
||||
|
||||
#: src/API/NotificationType.vala:64
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> now follows you</span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> подписался на вас"
|
||||
|
||||
#: src/API/NotificationType.vala:66
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> wants to follow you</"
|
||||
"span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> хочет на вас подписаться"
|
||||
|
||||
#: src/API/NotificationType.vala:68
|
||||
#, fuzzy, c-format
|
||||
msgid ""
|
||||
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a status</span>"
|
||||
msgstr "<a href=\"%s\"><b>%s</b></a> опубликовал статус"
|
||||
|
||||
#: src/Widgets/RichLabel.vala:105 src/Widgets/Status.vala:206
|
||||
#: src/Widgets/Account.vala:28
|
||||
msgid "Open in Browser"
|
||||
msgstr "Открыть в Браузере"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:62
|
||||
msgid "Refresh"
|
||||
msgstr "Обновить"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:67
|
||||
msgid "Favorites"
|
||||
msgstr "Понравившиеся"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:71 src/Views/Direct.vala:12
|
||||
msgid "Direct Messages"
|
||||
msgstr "Личные Сообщения"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:75 src/Views/Search.vala:12
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:79 src/Dialogs/WatchlistEditor.vala:99
|
||||
msgid "Watchlist"
|
||||
msgstr "Список Наблюдения"
|
||||
|
||||
#: src/Widgets/AccountsButton.vala:83 src/Dialogs/Preferences.vala:17
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
#: src/Widgets/Status.vala:52
|
||||
msgid "[ Show more ]"
|
||||
msgstr ""
|
||||
|
||||
#: src/Widgets/Status.vala:120
|
||||
msgid "This post can't be boosted"
|
||||
msgstr "Этот статус нельзя продвинуть"
|
||||
|
||||
#: src/Widgets/Status.vala:208 src/Widgets/Account.vala:30
|
||||
msgid "Copy Link"
|
||||
msgstr "Скопировать Ссылку"
|
||||
|
||||
#: src/Widgets/Status.vala:210
|
||||
msgid "Copy Text"
|
||||
msgstr "Скопировать Текст"
|
||||
|
||||
#: src/Widgets/Status.vala:217
|
||||
msgid "Unpin from Profile"
|
||||
msgstr "Открепить от Профиля"
|
||||
|
||||
#: src/Widgets/Status.vala:217
|
||||
msgid "Pin on Profile"
|
||||
msgstr "Закрепить на Профиле"
|
||||
|
||||
#: src/Widgets/Status.vala:221
|
||||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: src/Widgets/Status.vala:225 src/Dialogs/Compose.vala:73
|
||||
msgid "Redraft"
|
||||
msgstr "Исправить"
|
||||
|
||||
#: src/Widgets/Attachment/Box.vala:28
|
||||
msgid "Select media files to add"
|
||||
msgstr "Выберите файлы для добавления"
|
||||
|
||||
#: src/Widgets/Attachment/Box.vala:31
|
||||
msgid "_Cancel"
|
||||
msgstr "_Отмена"
|
||||
|
||||
#: src/Widgets/Attachment/Box.vala:33
|
||||
msgid "_Open"
|
||||
msgstr "_Выбрать"
|
||||
|
||||
#: src/Dialogs/MainWindow.vala:49
|
||||
msgid "Back"
|
||||
msgstr "Назад"
|
||||
|
||||
#: src/Dialogs/MainWindow.vala:58
|
||||
msgid "Toot"
|
||||
msgstr "Статус"
|
||||
|
||||
#: src/Dialogs/MainWindow.vala:63
|
||||
msgid "Toggle content"
|
||||
msgstr "Развернуть"
|
||||
|
||||
#: src/Dialogs/Compose.vala:69
|
||||
msgid "Post"
|
||||
msgstr ""
|
||||
|
||||
#: src/Dialogs/Preferences.vala:36
|
||||
msgid "Appearance"
|
||||
msgstr "Внешний вид"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:38
|
||||
#: src/Dialogs/Preferences.vala:37
|
||||
msgid "Dark theme:"
|
||||
msgstr "Тёмная тема:"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:41
|
||||
#: src/Dialogs/Preferences.vala:40
|
||||
msgid "Timelines"
|
||||
msgstr "Ленты"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:42
|
||||
#: src/Dialogs/Preferences.vala:41
|
||||
msgid "Real-time updates:"
|
||||
msgstr "Обновления в реальном времени:"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:44
|
||||
#: src/Dialogs/Preferences.vala:43
|
||||
msgid "Update public timelines:"
|
||||
msgstr "Обновлять публичные ленты:"
|
||||
|
||||
|
@ -420,33 +360,33 @@ msgstr "Обновлять публичные ленты:"
|
|||
#. grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
||||
#. grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
||||
#. grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
||||
#. var cache_size = new Gtk.SpinButton.with_range (16, 256, 1);
|
||||
#. var cache_size = new SpinButton.with_range (16, 256, 1);
|
||||
#. settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
||||
#. grid.attach (cache_size, 1, i++);
|
||||
#: src/Dialogs/SettingsDialog.vala:55 src/Views/NotificationsView.vala:34
|
||||
#: src/Dialogs/Preferences.vala:54 src/Views/Notifications.vala:33
|
||||
msgid "Notifications"
|
||||
msgstr "Уведомления"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:56
|
||||
#: src/Dialogs/Preferences.vala:55
|
||||
msgid "Display notifications:"
|
||||
msgstr "Отображать уведомления:"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:58
|
||||
#: src/Dialogs/Preferences.vala:57
|
||||
msgid "Always receive notifications:"
|
||||
msgstr "Всегда получать уведомления:"
|
||||
|
||||
#: src/Dialogs/SettingsDialog.vala:64
|
||||
#: src/Dialogs/Preferences.vala:63 src/Dialogs/WatchlistEditor.vala:162
|
||||
msgid "_Close"
|
||||
msgstr "_Закрыть"
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:20
|
||||
#: src/Dialogs/WatchlistEditor.vala:20
|
||||
msgid ""
|
||||
"You'll be notified when toots from this user appear in your Home timeline."
|
||||
msgstr ""
|
||||
"Вы получите уведомление, когда статусы от этого пользователя появятся в "
|
||||
"вашей Главной ленте."
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:21
|
||||
#: src/Dialogs/WatchlistEditor.vala:21
|
||||
msgid ""
|
||||
"You'll be notified when toots with this hashtag appear in any public "
|
||||
"timelines."
|
||||
|
@ -454,110 +394,88 @@ msgstr ""
|
|||
"Вы получите уведомление, когда статусы с данным хэштегом появятся в любой "
|
||||
"публичной ленте."
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:137
|
||||
#: src/Dialogs/WatchlistEditor.vala:108
|
||||
msgid "Users"
|
||||
msgstr "Пользователи"
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:138 src/Views/SearchView.vala:100
|
||||
#: src/Dialogs/WatchlistEditor.vala:109 src/Views/Search.vala:100
|
||||
msgid "Hashtags"
|
||||
msgstr "Хэштеги"
|
||||
|
||||
#: src/Dialogs/WatchlistDialog.vala:148
|
||||
#: src/Dialogs/WatchlistEditor.vala:122
|
||||
msgid "Add"
|
||||
msgstr "Добавить"
|
||||
|
||||
#: src/Views/AbstractView.vala:59
|
||||
#: src/Views/Base.vala:6
|
||||
msgid "Nothing to see here"
|
||||
msgstr "Тут ничего нет"
|
||||
|
||||
#: src/Views/AccountView.vala:79
|
||||
msgid "Edit Profile"
|
||||
msgstr "Редактировать Профиль"
|
||||
#: src/Views/NewAccount.vala:91
|
||||
msgid "Instance URL is invalid"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/AccountView.vala:80
|
||||
msgid "Mention"
|
||||
msgstr "Упомянуть"
|
||||
#: src/Views/NewAccount.vala:133
|
||||
#, fuzzy
|
||||
msgid "Please paste a valid authorization code"
|
||||
msgstr "Пожалуйста, вставьте корректный код авторизации"
|
||||
|
||||
#: src/Views/AccountView.vala:81
|
||||
msgid "Report"
|
||||
msgstr "Пожаловаться"
|
||||
|
||||
#: src/Views/AccountView.vala:82 src/Views/AccountView.vala:167
|
||||
msgid "Mute"
|
||||
msgstr "Заглушить"
|
||||
|
||||
#: src/Views/AccountView.vala:83 src/Views/AccountView.vala:166
|
||||
msgid "Block"
|
||||
msgstr "Заблокировать"
|
||||
|
||||
#: src/Views/AccountView.vala:95
|
||||
msgid "More Actions"
|
||||
msgstr "Больше Действий"
|
||||
|
||||
#: src/Views/AccountView.vala:115
|
||||
msgid "Toots"
|
||||
msgstr "Статусов"
|
||||
|
||||
#: src/Views/AccountView.vala:116
|
||||
msgid "Follows"
|
||||
msgstr "Подписок"
|
||||
|
||||
#: src/Views/AccountView.vala:120
|
||||
msgid "Followers"
|
||||
msgstr "Подписчиков"
|
||||
|
||||
#: src/Views/AccountView.vala:155
|
||||
msgid "Unfollow"
|
||||
msgstr "Отписаться"
|
||||
|
||||
#: src/Views/AccountView.vala:159
|
||||
msgid "Follow"
|
||||
msgstr "Подписаться"
|
||||
|
||||
#: src/Views/AccountView.vala:166
|
||||
msgid "Unblock"
|
||||
msgstr "Разблокировать"
|
||||
|
||||
#: src/Views/AccountView.vala:167
|
||||
msgid "Unmute"
|
||||
msgstr "Включить"
|
||||
|
||||
#: src/Views/AccountView.vala:228
|
||||
msgid "Sent follow request"
|
||||
msgstr "Отправлен запрос на подписку"
|
||||
|
||||
#: src/Views/AccountView.vala:230
|
||||
msgid "Blocked"
|
||||
msgstr "Заблокирован"
|
||||
|
||||
#: src/Views/AccountView.vala:232
|
||||
msgid "Follows you"
|
||||
msgstr "Подписан на вас"
|
||||
|
||||
#: src/Views/AccountView.vala:234
|
||||
msgid "Blocking this instance"
|
||||
msgstr "Данный узел заблокирован"
|
||||
|
||||
#: src/Views/AccountView.vala:269
|
||||
msgid "User not found"
|
||||
msgstr "Пользователь не найден"
|
||||
|
||||
#: src/Views/FederatedView.vala:12
|
||||
msgid "Federated Timeline"
|
||||
msgstr "Глобальная Лента"
|
||||
|
||||
#: src/Views/HomeView.vala:12 src/Views/TimelineView.vala:36
|
||||
#: src/Views/Timeline.vala:34 src/Views/Home.vala:12
|
||||
msgid "Home"
|
||||
msgstr "Главная"
|
||||
|
||||
#: src/Views/LocalView.vala:12
|
||||
#: src/Views/Local.vala:12
|
||||
msgid "Local Timeline"
|
||||
msgstr "Локальная Лента"
|
||||
|
||||
#: src/Views/SearchView.vala:82
|
||||
#: src/Views/Federated.vala:12
|
||||
msgid "Federated Timeline"
|
||||
msgstr "Глобальная Лента"
|
||||
|
||||
#: src/Views/Profile.vala:61
|
||||
#, c-format
|
||||
msgid "%s Posts"
|
||||
msgstr ""
|
||||
|
||||
#: src/Views/Profile.vala:67
|
||||
#, fuzzy, c-format
|
||||
msgid "%s Follows"
|
||||
msgstr "Подписок"
|
||||
|
||||
#: src/Views/Profile.vala:73
|
||||
#, fuzzy, c-format
|
||||
msgid "%s Followers"
|
||||
msgstr "Подписчиков"
|
||||
|
||||
#: src/Views/Profile.vala:109
|
||||
msgid "Sent follow request"
|
||||
msgstr "Отправлен запрос на подписку"
|
||||
|
||||
#: src/Views/Profile.vala:111
|
||||
#, fuzzy
|
||||
msgid "Mutually follows you"
|
||||
msgstr "Подписан на вас"
|
||||
|
||||
#: src/Views/Profile.vala:113
|
||||
msgid "Follows you"
|
||||
msgstr "Подписан на вас"
|
||||
|
||||
#: src/Views/Profile.vala:124
|
||||
#, fuzzy
|
||||
msgid "Follow back"
|
||||
msgstr "Подписаться"
|
||||
|
||||
#: src/Views/Profile.vala:126
|
||||
msgid "Unfollow"
|
||||
msgstr "Отписаться"
|
||||
|
||||
#: src/Views/Profile.vala:128
|
||||
msgid "Follow"
|
||||
msgstr "Подписаться"
|
||||
|
||||
#: src/Views/Search.vala:82
|
||||
msgid "Accounts"
|
||||
msgstr "Аккаунты"
|
||||
|
||||
#: src/Views/SearchView.vala:91
|
||||
#: src/Views/Search.vala:91
|
||||
msgid "Statuses"
|
||||
msgstr "Статусы"
|
||||
|
|
896
po/zh_CN.po
|
@ -0,0 +1,10 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = false
|
||||
charset = utf-8
|
||||
|
||||
[*.sh]
|
||||
indent_size = 4
|
||||
indent_style = tab
|
|
@ -1,60 +1,57 @@
|
|||
public class Tootle.API.Account {
|
||||
public class Tootle.API.Account : GLib.Object {
|
||||
|
||||
public abstract signal void updated ();
|
||||
|
||||
public int64 id;
|
||||
public string username;
|
||||
public string acct;
|
||||
public string display_name;
|
||||
public string note;
|
||||
public string header;
|
||||
public string avatar;
|
||||
public string url;
|
||||
public string created_at;
|
||||
public int64 followers_count;
|
||||
public int64 following_count;
|
||||
public int64 statuses_count;
|
||||
|
||||
public Relationship? rs;
|
||||
|
||||
public Account (int64 _id){
|
||||
id = _id;
|
||||
public int64 id { get; set; }
|
||||
public string username { get; set; }
|
||||
public string acct { get; set; }
|
||||
public string? _display_name = null;
|
||||
public string display_name {
|
||||
set {
|
||||
this._display_name = value;
|
||||
}
|
||||
get {
|
||||
return (_display_name == null || _display_name == "") ? username : _display_name;
|
||||
}
|
||||
}
|
||||
public string note { get; set; }
|
||||
public string header { get; set; }
|
||||
public string avatar { get; set; }
|
||||
public string url { get; set; }
|
||||
public string created_at { get; set; }
|
||||
public int64 followers_count { get; set; }
|
||||
public int64 following_count { get; set; }
|
||||
public int64 posts_count { get; set; }
|
||||
public Relationship? rs { get; set; default = null; }
|
||||
|
||||
public static Account parse(Json.Object obj) {
|
||||
var id = int64.parse (obj.get_string_member ("id"));
|
||||
var account = new Account (id);
|
||||
public Account (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
username: obj.get_string_member ("username"),
|
||||
acct: obj.get_string_member ("acct"),
|
||||
display_name: obj.get_string_member ("display_name"),
|
||||
note: obj.get_string_member ("note"),
|
||||
avatar: obj.get_string_member ("avatar"),
|
||||
header: obj.get_string_member ("header"),
|
||||
url: obj.get_string_member ("url"),
|
||||
created_at: obj.get_string_member ("created_at"),
|
||||
|
||||
account.username = obj.get_string_member ("username");
|
||||
account.acct = obj.get_string_member ("acct");
|
||||
account.display_name = obj.get_string_member ("display_name");
|
||||
if (account.display_name == "")
|
||||
account.display_name = account.username;
|
||||
account.note = obj.get_string_member ("note");
|
||||
account.avatar = obj.get_string_member ("avatar");
|
||||
account.header = obj.get_string_member ("header");
|
||||
account.url = obj.get_string_member ("url");
|
||||
account.created_at = obj.get_string_member ("created_at");
|
||||
|
||||
account.followers_count = obj.get_int_member ("followers_count");
|
||||
account.following_count = obj.get_int_member ("following_count");
|
||||
account.statuses_count = obj.get_int_member ("statuses_count");
|
||||
followers_count: obj.get_int_member ("followers_count"),
|
||||
following_count: obj.get_int_member ("following_count"),
|
||||
posts_count: obj.get_int_member ("statuses_count")
|
||||
);
|
||||
|
||||
if (obj.has_member ("fields")) {
|
||||
obj.get_array_member ("fields").foreach_element ((array, i, node) => {
|
||||
var field_obj = node.get_object ();
|
||||
var field_name = field_obj.get_string_member ("name");
|
||||
var field_val = field_obj.get_string_member ("value");
|
||||
account.note += "\n";
|
||||
account.note += field_name + ": ";
|
||||
account.note += field_val;
|
||||
note += "\n";
|
||||
note += field_name + ": ";
|
||||
note += field_val;
|
||||
});
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
public Json.Node? serialize () {
|
||||
public virtual Json.Node? serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
builder.set_member_name ("id");
|
||||
|
@ -66,7 +63,7 @@ public class Tootle.API.Account {
|
|||
builder.set_member_name ("followers_count");
|
||||
builder.add_int_value (followers_count);
|
||||
builder.set_member_name ("statuses_count");
|
||||
builder.add_int_value (statuses_count);
|
||||
builder.add_int_value (posts_count);
|
||||
builder.set_member_name ("display_name");
|
||||
builder.add_string_value (display_name);
|
||||
builder.set_member_name ("username");
|
||||
|
@ -86,83 +83,55 @@ public class Tootle.API.Account {
|
|||
return builder.get_root ();
|
||||
}
|
||||
|
||||
public bool is_self (){
|
||||
return id == accounts.current.id;
|
||||
public bool is_self () {
|
||||
return id == accounts.active.id;
|
||||
}
|
||||
|
||||
public Soup.Message get_relationship (){
|
||||
var url = "%s/api/v1/accounts/relationships?id=%lld".printf (accounts.formal.instance, id);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
Tootle.network.queue (msg, (sess, mess) => {
|
||||
try{
|
||||
var root = Tootle.network.parse_array (mess).get_object_element (0);
|
||||
rs = Relationship.parse (root);
|
||||
updated ();
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning ("Can't get account relationship:");
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
return msg;
|
||||
public Request get_relationship () {
|
||||
return new Request.GET ("/api/v1/accounts/relationships")
|
||||
.with_account (accounts.active)
|
||||
.with_param ("id", id.to_string ())
|
||||
.then_parse_array (node => {
|
||||
rs = new Relationship (node.get_object ());
|
||||
})
|
||||
.on_error (network.on_error)
|
||||
.exec ();
|
||||
}
|
||||
|
||||
public Soup.Message set_following (bool follow = true){
|
||||
var action = follow ? "follow" : "unfollow";
|
||||
var url = "%s/api/v1/accounts/%lld/%s".printf (accounts.formal.instance, id, action);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.queue (msg, (sess, mess) => {
|
||||
try{
|
||||
var root = network.parse (mess);
|
||||
rs = Relationship.parse (root);
|
||||
updated ();
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
app.error (_("Error"), e.message);
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
return msg;
|
||||
public Request set_following (bool state = true) {
|
||||
var action = state ? "follow" : "unfollow";
|
||||
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||
.with_account (accounts.active)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
rs = new Relationship (root);
|
||||
})
|
||||
.on_error (network.on_error)
|
||||
.exec ();
|
||||
}
|
||||
|
||||
public Soup.Message set_muted (bool mute = true){
|
||||
var action = mute ? "mute" : "unmute";
|
||||
var url = "%s/api/v1/accounts/%lld/%s".printf (accounts.formal.instance, id, action);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.queue (msg, (sess, mess) => {
|
||||
try{
|
||||
var root = network.parse (mess);
|
||||
rs = Relationship.parse (root);
|
||||
updated ();
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
app.error (_("Error"), e.message);
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
return msg;
|
||||
public Request set_muted (bool state = true) {
|
||||
var action = state ? "mute" : "unmute";
|
||||
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||
.with_account (accounts.active)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
rs = new Relationship (root);
|
||||
})
|
||||
.on_error (network.on_error)
|
||||
.exec ();
|
||||
}
|
||||
|
||||
public Soup.Message set_blocked (bool block = true){
|
||||
var action = block ? "block" : "unblock";
|
||||
var url = "%s/api/v1/accounts/%lld/%s".printf (accounts.formal.instance, id, action);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.queue (msg, (sess, mess) => {
|
||||
try{
|
||||
var root = network.parse (mess);
|
||||
rs = Relationship.parse (root);
|
||||
updated ();
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
app.error (_("Error"), e.message);
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
return msg;
|
||||
public Request set_blocked (bool state = true) {
|
||||
var action = state ? "block" : "unblock";
|
||||
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||
.with_account (accounts.active)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
rs = new Relationship (root);
|
||||
})
|
||||
.on_error (network.on_error)
|
||||
.exec ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,27 +1,24 @@
|
|||
public class Tootle.API.Attachment {
|
||||
public class Tootle.API.Attachment : GLib.Object {
|
||||
|
||||
public int64 id;
|
||||
public string type;
|
||||
public string url;
|
||||
public string preview_url;
|
||||
public string? description;
|
||||
public int64 id { get; construct set; }
|
||||
public string kind { get; set; }
|
||||
public string url { get; set; }
|
||||
public string? description { get; set; default = null; }
|
||||
|
||||
public Attachment (int64 _id) {
|
||||
id = _id;
|
||||
public string? _preview_url = null;
|
||||
public string preview_url {
|
||||
set { this._preview_url = value; }
|
||||
get { return (_preview_url == null || _preview_url == "") ? url : _preview_url; }
|
||||
}
|
||||
|
||||
public static Attachment parse (Json.Object obj) {
|
||||
var id = int64.parse (obj.get_string_member ("id"));
|
||||
var attachment = new Attachment (id);
|
||||
|
||||
attachment.type = obj.get_string_member ("type");
|
||||
attachment.preview_url = obj.get_string_member ("preview_url");
|
||||
attachment.url = obj.get_string_member ("url");
|
||||
|
||||
if (obj.has_member ("description"))
|
||||
attachment.description = obj.get_string_member ("description");
|
||||
|
||||
return attachment;
|
||||
public Attachment (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
kind: obj.get_string_member ("type"),
|
||||
preview_url: obj.get_string_member ("preview_url"),
|
||||
url: obj.get_string_member ("url"),
|
||||
description: obj.get_string_member ("description")
|
||||
);
|
||||
}
|
||||
|
||||
public Json.Node? serialize () {
|
||||
|
@ -30,7 +27,7 @@ public class Tootle.API.Attachment {
|
|||
builder.set_member_name ("id");
|
||||
builder.add_string_value (id.to_string ());
|
||||
builder.set_member_name ("type");
|
||||
builder.add_string_value (type);
|
||||
builder.add_string_value (kind);
|
||||
builder.set_member_name ("url");
|
||||
builder.add_string_value (url);
|
||||
builder.set_member_name ("preview_url");
|
||||
|
|
|
@ -1,30 +1,26 @@
|
|||
public class Tootle.API.Mention : GLib.Object {
|
||||
|
||||
public int64 id;
|
||||
public string username;
|
||||
public string acct;
|
||||
public string url;
|
||||
public int64 id { get; construct set; }
|
||||
public string username { get; construct set; }
|
||||
public string acct { get; construct set; }
|
||||
public string url { get; construct set; }
|
||||
|
||||
public Mention (int64 _id){
|
||||
id = _id;
|
||||
public Mention (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
username: obj.get_string_member ("username"),
|
||||
acct: obj.get_string_member ("acct"),
|
||||
url: obj.get_string_member ("url")
|
||||
);
|
||||
}
|
||||
|
||||
public Mention.from_account (Account account){
|
||||
id = account.id;
|
||||
username = account.username;
|
||||
acct = account.acct;
|
||||
url = account.url;
|
||||
}
|
||||
|
||||
public static Mention parse (Json.Object obj){
|
||||
var id = int64.parse (obj.get_string_member ("id"));
|
||||
var mention = new Mention (id);
|
||||
|
||||
mention.username = obj.get_string_member ("username");
|
||||
mention.acct = obj.get_string_member ("acct");
|
||||
mention.url = obj.get_string_member ("url");
|
||||
|
||||
return mention;
|
||||
public Mention.from_account (Account account) {
|
||||
Object (
|
||||
id: account.id,
|
||||
username: account.username,
|
||||
acct: account.acct,
|
||||
url: account.url
|
||||
);
|
||||
}
|
||||
|
||||
public Json.Node? serialize () {
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
public class Tootle.API.Notification {
|
||||
public class Tootle.API.Notification : GLib.Object {
|
||||
|
||||
public int64 id;
|
||||
public NotificationType type;
|
||||
public string created_at;
|
||||
public int64 id { get; construct set; }
|
||||
public Account account { get; construct set; }
|
||||
|
||||
public Status? status;
|
||||
public Account? account;
|
||||
public NotificationType kind { get; set; }
|
||||
public string created_at { get; set; }
|
||||
public Status? status { get; set; default = null; }
|
||||
|
||||
public Notification (int64 _id) {
|
||||
id = _id;
|
||||
}
|
||||
|
||||
public static Notification parse (Json.Object obj) {
|
||||
var id = int64.parse (obj.get_string_member ("id"));
|
||||
var notification = new Notification (id);
|
||||
|
||||
notification.type = NotificationType.from_string (obj.get_string_member ("type"));
|
||||
notification.created_at = obj.get_string_member ("created_at");
|
||||
public Notification (Json.Object obj) throws Oopsie {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
kind: NotificationType.from_string (obj.get_string_member ("type")),
|
||||
created_at: obj.get_string_member ("created_at"),
|
||||
account: new Account (obj.get_object_member ("account"))
|
||||
);
|
||||
|
||||
if (obj.has_member ("status"))
|
||||
notification.status = Status.parse (obj.get_object_member ("status"));
|
||||
if (obj.has_member ("account"))
|
||||
notification.account = Account.parse (obj.get_object_member ("account"));
|
||||
status = new Status (obj.get_object_member ("status"));
|
||||
}
|
||||
|
||||
return notification;
|
||||
public Notification.follow_request (Json.Object obj) {
|
||||
Object (
|
||||
id: 0,
|
||||
kind: NotificationType.FOLLOW_REQUEST,
|
||||
account: new Account (obj)
|
||||
);
|
||||
}
|
||||
|
||||
public Json.Node? serialize () {
|
||||
|
@ -32,7 +33,7 @@ public class Tootle.API.Notification {
|
|||
builder.set_member_name ("id");
|
||||
builder.add_string_value (id.to_string ());
|
||||
builder.set_member_name ("type");
|
||||
builder.add_string_value (type.to_string ());
|
||||
builder.add_string_value (kind.to_string ());
|
||||
builder.set_member_name ("created_at");
|
||||
builder.add_string_value (created_at);
|
||||
|
||||
|
@ -49,47 +50,35 @@ public class Tootle.API.Notification {
|
|||
return builder.get_root ();
|
||||
}
|
||||
|
||||
public static Notification parse_follow_request (Json.Object obj) {
|
||||
var notification = new Notification (-1);
|
||||
var account = Account.parse (obj);
|
||||
|
||||
notification.type = NotificationType.FOLLOW_REQUEST;
|
||||
notification.account = account;
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
public Soup.Message? dismiss () {
|
||||
if (type == NotificationType.WATCHLIST) {
|
||||
if (accounts.formal.cached_notifications.remove (this))
|
||||
if (kind == NotificationType.WATCHLIST) {
|
||||
if (accounts.active.cached_notifications.remove (this))
|
||||
accounts.save ();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type == NotificationType.FOLLOW_REQUEST)
|
||||
if (kind == NotificationType.FOLLOW_REQUEST)
|
||||
return reject_follow_request ();
|
||||
|
||||
var url = "%s/api/v1/notifications/dismiss?id=%lld".printf (accounts.formal.instance, id);
|
||||
var msg = new Soup.Message ("POST", url);
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg);
|
||||
return msg;
|
||||
var req = new Request.POST ("/api/v1/notifications/dismiss")
|
||||
.with_account (accounts.active)
|
||||
.with_param ("id", id.to_string ())
|
||||
.exec ();
|
||||
return req;
|
||||
}
|
||||
|
||||
public Soup.Message accept_follow_request () {
|
||||
var url = "%s/api/v1/follow_requests/%lld/authorize".printf (accounts.formal.instance, account.id);
|
||||
var msg = new Soup.Message ("POST", url);
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg);
|
||||
return msg;
|
||||
var req = new Request.POST (@"/api/v1/follow_requests/$(account.id)/authorize")
|
||||
.with_account (accounts.active)
|
||||
.exec ();
|
||||
return req;
|
||||
}
|
||||
|
||||
public Soup.Message reject_follow_request () {
|
||||
var url = "%s/api/v1/follow_requests/%lld/reject".printf (accounts.formal.instance, account.id);
|
||||
var msg = new Soup.Message ("POST", url);
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg);
|
||||
return msg;
|
||||
var req = new Request.POST (@"/api/v1/follow_requests/$(account.id)/reject")
|
||||
.with_account (accounts.active)
|
||||
.exec ();
|
||||
return req;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ public enum Tootle.API.NotificationType {
|
|||
FOLLOW_REQUEST, // Internal
|
||||
WATCHLIST; // Internal
|
||||
|
||||
public string to_string() {
|
||||
public string to_string () {
|
||||
switch (this) {
|
||||
case MENTION:
|
||||
return "mention";
|
||||
|
@ -24,11 +24,12 @@ public enum Tootle.API.NotificationType {
|
|||
case WATCHLIST:
|
||||
return "watchlist";
|
||||
default:
|
||||
assert_not_reached();
|
||||
warning (@"Unknown notification type: $this");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static NotificationType from_string (string str) {
|
||||
public static NotificationType from_string (string str) throws Oopsie {
|
||||
switch (str) {
|
||||
case "mention":
|
||||
return MENTION;
|
||||
|
@ -45,7 +46,7 @@ public enum Tootle.API.NotificationType {
|
|||
case "watchlist":
|
||||
return WATCHLIST;
|
||||
default:
|
||||
assert_not_reached();
|
||||
throw new Oopsie.INSTANCE (@"Unknown notification type: $str");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,19 +55,20 @@ public enum Tootle.API.NotificationType {
|
|||
case MENTION:
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> mentioned you</span>").printf (account.url, account.display_name);
|
||||
case REBLOG:
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your toot</span>").printf (account.url, account.display_name);
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your status</span>").printf (account.url, account.display_name);
|
||||
case REBLOG_REMOTE_USER:
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted</span>").printf (account.url, account.display_name);
|
||||
case FAVORITE:
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your toot</span>").printf (account.url, account.display_name);
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your status</span>").printf (account.url, account.display_name);
|
||||
case FOLLOW:
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> now follows you</span>").printf (account.url, account.display_name);
|
||||
case FOLLOW_REQUEST:
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> wants to follow you</span>").printf (account.url, account.display_name);
|
||||
case WATCHLIST:
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a toot</span>").printf (account.url, account.display_name);
|
||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a status</span>").printf (account.url, account.display_name);
|
||||
default:
|
||||
assert_not_reached();
|
||||
warning (@"Unknown notification type: $this");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,6 +78,7 @@ public enum Tootle.API.NotificationType {
|
|||
case WATCHLIST:
|
||||
return "user-available-symbolic";
|
||||
case REBLOG:
|
||||
case REBLOG_REMOTE_USER:
|
||||
return "media-playlist-repeat-symbolic";
|
||||
case FAVORITE:
|
||||
return "emblem-favorite-symbolic";
|
||||
|
@ -83,7 +86,8 @@ public enum Tootle.API.NotificationType {
|
|||
case FOLLOW_REQUEST:
|
||||
return "contact-new-symbolic";
|
||||
default:
|
||||
assert_not_reached();
|
||||
warning (@"Unknown notification type: $this");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
using GLib;
|
||||
public class Tootle.API.Relationship : GLib.Object {
|
||||
|
||||
public class Tootle.API.Relationship : Object {
|
||||
public int64 id { get; construct set; }
|
||||
public bool following { get; set; default = false; }
|
||||
public bool followed_by { get; set; default = false; }
|
||||
public bool muting { get; set; default = false; }
|
||||
public bool muting_notifications { get; set; default = false; }
|
||||
public bool requested { get; set; default = false; }
|
||||
public bool blocking { get; set; default = false; }
|
||||
public bool domain_blocking { get; set; default = false; }
|
||||
|
||||
public int64 id;
|
||||
public bool following;
|
||||
public bool followed_by;
|
||||
public bool blocking;
|
||||
public bool muting;
|
||||
public bool muting_notifications;
|
||||
public bool requested;
|
||||
public bool domain_blocking;
|
||||
|
||||
public Relationship (int64 _id) {
|
||||
id = _id;
|
||||
}
|
||||
|
||||
public static Relationship parse (Json.Object obj) {
|
||||
var id = int64.parse (obj.get_string_member ("id"));
|
||||
var relationship = new Relationship (id);
|
||||
relationship.following = obj.get_boolean_member ("following");
|
||||
relationship.followed_by = obj.get_boolean_member ("followed_by");
|
||||
relationship.blocking = obj.get_boolean_member ("blocking");
|
||||
relationship.muting = obj.get_boolean_member ("muting");
|
||||
relationship.muting_notifications = obj.get_boolean_member ("muting_notifications");
|
||||
relationship.requested = obj.get_boolean_member ("requested");
|
||||
relationship.domain_blocking = obj.get_boolean_member ("domain_blocking");
|
||||
return relationship;
|
||||
public Relationship (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
following: obj.get_boolean_member ("following"),
|
||||
followed_by: obj.get_boolean_member ("followed_by"),
|
||||
blocking: obj.get_boolean_member ("blocking"),
|
||||
muting: obj.get_boolean_member ("muting"),
|
||||
muting_notifications: obj.get_boolean_member ("muting_notifications"),
|
||||
requested: obj.get_boolean_member ("requested"),
|
||||
domain_blocking: obj.get_boolean_member ("domain_blocking")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,89 +1,115 @@
|
|||
public class Tootle.API.Status {
|
||||
using Gee;
|
||||
|
||||
public signal void updated ();
|
||||
public class Tootle.API.Status : GLib.Object {
|
||||
|
||||
public API.Account account;
|
||||
public int64 id;
|
||||
public string uri;
|
||||
public string url;
|
||||
public string? spoiler_text;
|
||||
public string content;
|
||||
public int64 replies_count;
|
||||
public int64 reblogs_count;
|
||||
public int64 favourites_count;
|
||||
public string created_at;
|
||||
public bool reblogged = false;
|
||||
public bool favorited = false;
|
||||
public bool sensitive = false;
|
||||
public bool muted = false;
|
||||
public bool pinned = false;
|
||||
public API.StatusVisibility visibility;
|
||||
public API.Status? reblog;
|
||||
public API.Mention[]? mentions;
|
||||
public API.Attachment[]? attachments;
|
||||
|
||||
public Status (int64 _id) {
|
||||
id = _id;
|
||||
public int64 id { get; construct set; }
|
||||
public API.Account account { get; construct set; }
|
||||
public string uri { get; set; }
|
||||
public string? url { get; set; default = null; }
|
||||
public string? spoiler_text { get; set; default = null; }
|
||||
public string? in_reply_to_id { get; set; default = null; }
|
||||
public string? in_reply_to_account_id { get; set; default = null; }
|
||||
public string content { get; set; default = ""; }
|
||||
public int64 replies_count { get; set; default = 0; }
|
||||
public int64 reblogs_count { get; set; default = 0; }
|
||||
public int64 favourites_count { get; set; default = 0; }
|
||||
public string created_at { get; set; default = "0"; }
|
||||
public bool reblogged { get; set; default = false; }
|
||||
public bool favorited { get; set; default = false; }
|
||||
public bool sensitive { get; set; default = false; }
|
||||
public bool muted { get; set; default = false; }
|
||||
public bool pinned { get; set; default = false; }
|
||||
public API.Visibility visibility { get; set; default = API.Visibility.PUBLIC; }
|
||||
public API.Status? reblog { get; set; default = null; }
|
||||
public ArrayList<API.Mention>? mentions { get; set; default = null; }
|
||||
public ArrayList<API.Attachment>? attachments { get; set; default = null; }
|
||||
|
||||
public Status formal {
|
||||
get { return reblog ?? this; }
|
||||
}
|
||||
|
||||
public Status get_formal () {
|
||||
return reblog != null ? reblog : this;
|
||||
public bool has_spoiler {
|
||||
get {
|
||||
return formal.spoiler_text != null || formal.sensitive;
|
||||
}
|
||||
}
|
||||
|
||||
public static Status parse (Json.Object obj) {
|
||||
var id = int64.parse (obj.get_string_member ("id"));
|
||||
var status = new Status (id);
|
||||
public Status (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
account: new Account (obj.get_object_member ("account")),
|
||||
uri: obj.get_string_member ("uri"),
|
||||
created_at: obj.get_string_member ("created_at"),
|
||||
content: Html.simplify ( obj.get_string_member ("content")),
|
||||
sensitive: obj.get_boolean_member ("sensitive"),
|
||||
visibility: Visibility.from_string (obj.get_string_member ("visibility")),
|
||||
|
||||
status.account = Account.parse (obj.get_object_member ("account"));
|
||||
status.uri = obj.get_string_member ("uri");
|
||||
status.created_at = obj.get_string_member ("created_at");
|
||||
status.replies_count = obj.get_int_member ("replies_count");
|
||||
status.reblogs_count = obj.get_int_member ("reblogs_count");
|
||||
status.favourites_count = obj.get_int_member ("favourites_count");
|
||||
status.content = Html.simplify ( obj.get_string_member ("content"));
|
||||
status.sensitive = obj.get_boolean_member ("sensitive");
|
||||
status.visibility = StatusVisibility.from_string (obj.get_string_member ("visibility"));
|
||||
in_reply_to_id: obj.get_string_member ("in_reply_to_id") ?? null,
|
||||
in_reply_to_account_id: obj.get_string_member ("in_reply_to_account_id") ?? null,
|
||||
|
||||
replies_count: obj.get_int_member ("replies_count"),
|
||||
reblogs_count: obj.get_int_member ("reblogs_count"),
|
||||
favourites_count: obj.get_int_member ("favourites_count")
|
||||
);
|
||||
|
||||
if (obj.has_member ("url"))
|
||||
status.url = obj.get_string_member ("url");
|
||||
url = obj.get_string_member ("url");
|
||||
else
|
||||
status.url = obj.get_string_member ("uri").replace ("/activity", "");
|
||||
url = obj.get_string_member ("uri").replace ("/activity", "");
|
||||
|
||||
var spoiler = obj.get_string_member ("spoiler_text");
|
||||
if (spoiler != "")
|
||||
status.spoiler_text = Html.simplify (spoiler);
|
||||
spoiler_text = Html.simplify (spoiler);
|
||||
|
||||
if (obj.has_member ("reblogged"))
|
||||
status.reblogged = obj.get_boolean_member ("reblogged");
|
||||
reblogged = obj.get_boolean_member ("reblogged");
|
||||
if (obj.has_member ("favourited"))
|
||||
status.favorited = obj.get_boolean_member ("favourited");
|
||||
favorited = obj.get_boolean_member ("favourited");
|
||||
if (obj.has_member ("muted"))
|
||||
status.muted = obj.get_boolean_member ("muted");
|
||||
muted = obj.get_boolean_member ("muted");
|
||||
if (obj.has_member ("pinned"))
|
||||
status.pinned = obj.get_boolean_member ("pinned");
|
||||
pinned = obj.get_boolean_member ("pinned");
|
||||
|
||||
if (obj.has_member ("reblog") && obj.get_null_member("reblog") != true)
|
||||
status.reblog = Status.parse (obj.get_object_member ("reblog"));
|
||||
reblog = new Status (obj.get_object_member ("reblog"));
|
||||
|
||||
API.Mention[]? _mentions = {};
|
||||
obj.get_array_member ("mentions").foreach_element ((array, i, node) => {
|
||||
var object = node.get_object ();
|
||||
if (object != null)
|
||||
_mentions += API.Mention.parse (object);
|
||||
var entity = node.get_object ();
|
||||
if (entity != null) {
|
||||
if (mentions == null)
|
||||
mentions = new ArrayList<API.Mention> ();
|
||||
mentions.add (new API.Mention (entity));
|
||||
}
|
||||
});
|
||||
if (_mentions.length > 0)
|
||||
status.mentions = _mentions;
|
||||
|
||||
API.Attachment[]? _attachments = {};
|
||||
obj.get_array_member ("media_attachments").foreach_element ((array, i, node) => {
|
||||
var object = node.get_object ();
|
||||
if (object != null)
|
||||
_attachments += API.Attachment.parse (object);
|
||||
var entity = node.get_object ();
|
||||
if (entity != null) {
|
||||
if (attachments == null)
|
||||
attachments = new ArrayList<API.Attachment> ();
|
||||
attachments.add (new API.Attachment (entity));
|
||||
}
|
||||
});
|
||||
if (_attachments.length > 0)
|
||||
status.attachments = _attachments;
|
||||
}
|
||||
|
||||
return status;
|
||||
public Status.empty () {
|
||||
Object (id: -1);
|
||||
}
|
||||
|
||||
public Status.from_account (API.Account account) {
|
||||
Object (
|
||||
id: 0,
|
||||
account: account,
|
||||
created_at: account.created_at
|
||||
);
|
||||
|
||||
if (account.note == "")
|
||||
content = "";
|
||||
else if ("\n" in account.note)
|
||||
content = Html.remove_tags (account.note.split ("\n")[0]);
|
||||
else
|
||||
content = Html.remove_tags (account.note);
|
||||
}
|
||||
|
||||
public Json.Node? serialize () {
|
||||
|
@ -142,21 +168,17 @@ public class Tootle.API.Status {
|
|||
}
|
||||
|
||||
public bool is_owned (){
|
||||
return get_formal ().account.id == accounts.current.id;
|
||||
}
|
||||
|
||||
public bool has_spoiler () {
|
||||
return get_formal ().spoiler_text != null || get_formal ().sensitive;
|
||||
return formal.account.id == accounts.active.id;
|
||||
}
|
||||
|
||||
public string get_reply_mentions () {
|
||||
var result = "";
|
||||
if (account.acct != accounts.current.acct)
|
||||
if (account.acct != accounts.active.acct)
|
||||
result = "@%s ".printf (account.acct);
|
||||
|
||||
if (mentions != null) {
|
||||
foreach (var mention in mentions) {
|
||||
var equals_current = mention.acct == accounts.current.acct;
|
||||
var equals_current = mention.acct == accounts.active.acct;
|
||||
var already_mentioned = mention.acct in result;
|
||||
|
||||
if (!equals_current && ! already_mentioned)
|
||||
|
@ -167,69 +189,29 @@ public class Tootle.API.Status {
|
|||
return result;
|
||||
}
|
||||
|
||||
public void set_reblogged (bool rebl, Network.ErrorCallback? err = network.on_error) {
|
||||
var action = rebl ? "reblog" : "unreblog";
|
||||
var msg = new Soup.Message ("POST", "%s/api/v1/statuses/%lld/%s".printf (accounts.formal.instance, id, action));
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, message) => {
|
||||
reblogged = rebl;
|
||||
updated ();
|
||||
}, (status, reason) => {
|
||||
err (status, reason);
|
||||
});
|
||||
public void action (string action, owned Network.ErrorCallback? err = network.on_error) {
|
||||
new Request.POST (@"/api/v1/statuses/$(formal.id)/$action")
|
||||
.with_account (accounts.active)
|
||||
.then_parse_obj (obj => {
|
||||
var status = new API.Status (obj).formal;
|
||||
formal.reblogged = status.reblogged;
|
||||
formal.favorited = status.favorited;
|
||||
formal.muted = status.muted;
|
||||
formal.pinned = status.pinned;
|
||||
})
|
||||
.on_error ((status, reason) => err (status, reason))
|
||||
.exec ();
|
||||
}
|
||||
|
||||
public void set_favorited (bool fav, Network.ErrorCallback? err = network.on_error) {
|
||||
var action = fav ? "favourite" : "unfavourite";
|
||||
var msg = new Soup.Message ("POST", "%s/api/v1/statuses/%lld/%s".printf (accounts.formal.instance, id, action));
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, message) => {
|
||||
favorited = fav;
|
||||
updated ();
|
||||
}, (status, reason) => {
|
||||
err (status, reason);
|
||||
});
|
||||
}
|
||||
|
||||
public void set_muted (bool mute, Network.ErrorCallback? err = network.on_error) {
|
||||
var action = mute ? "mute" : "unmute";
|
||||
var msg = new Soup.Message ("POST", "%s/api/v1/statuses/%lld/%s".printf (accounts.formal.instance, id, action));
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, message) => {
|
||||
muted = mute;
|
||||
updated ();
|
||||
}, (status, reason) => {
|
||||
err (status, reason);
|
||||
});
|
||||
}
|
||||
|
||||
public void set_pinned (bool pin, Network.ErrorCallback? err = network.on_error) {
|
||||
var action = pin ? "pin" : "unpin";
|
||||
var msg = new Soup.Message ("POST", "%s/api/v1/statuses/%lld/%s".printf (accounts.formal.instance, id, action));
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, message) => {
|
||||
pinned = pin;
|
||||
updated ();
|
||||
}, (status, reason) => {
|
||||
err (status, reason);
|
||||
});
|
||||
}
|
||||
|
||||
public void poof (Soup.SessionCallback? cb = null, Network.ErrorCallback? err = network.on_error) {
|
||||
var msg = new Soup.Message ("DELETE", "%s/api/v1/statuses/%lld".printf (accounts.formal.instance, id));
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, message) => {
|
||||
network.status_removed (id);
|
||||
if (cb != null)
|
||||
cb (sess, message);
|
||||
}, (status, reason) => {
|
||||
err (status, reason);
|
||||
});
|
||||
public void poof (owned Soup.SessionCallback? cb = null, owned Network.ErrorCallback? err = network.on_error) {
|
||||
new Request.DELETE (@"/api/v1/statuses/$id")
|
||||
.with_account (accounts.active)
|
||||
.then ((sess, msg) => {
|
||||
streams.status_removed (id);
|
||||
cb (sess, msg);
|
||||
})
|
||||
.on_error ((status, reason) => err (status, reason))
|
||||
.exec ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
public class Tootle.API.Tag{
|
||||
public class Tootle.API.Tag : GLib.Object {
|
||||
|
||||
public string name;
|
||||
public string url;
|
||||
public string name { get; construct set; }
|
||||
public string url { get; construct set; }
|
||||
|
||||
public Tag (string _name, string _url) {
|
||||
name = _name;
|
||||
url = _url;
|
||||
}
|
||||
|
||||
public static Tag parse (Json.Object obj) {
|
||||
var name = obj.get_string_member ("name");
|
||||
var url = obj.get_string_member ("url");
|
||||
return new Tag (name, url);
|
||||
public Tag (Json.Object obj) {
|
||||
Object (
|
||||
name: obj.get_string_member ("name"),
|
||||
url: obj.get_string_member ("url")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
public enum Tootle.API.StatusVisibility {
|
||||
public enum Tootle.API.Visibility {
|
||||
PUBLIC,
|
||||
UNLISTED,
|
||||
PRIVATE,
|
||||
|
@ -6,8 +6,6 @@ public enum Tootle.API.StatusVisibility {
|
|||
|
||||
public string to_string () {
|
||||
switch (this) {
|
||||
case PUBLIC:
|
||||
return "public";
|
||||
case UNLISTED:
|
||||
return "unlisted";
|
||||
case PRIVATE:
|
||||
|
@ -15,29 +13,38 @@ public enum Tootle.API.StatusVisibility {
|
|||
case DIRECT:
|
||||
return "direct";
|
||||
default:
|
||||
assert_not_reached();
|
||||
return "public";
|
||||
}
|
||||
}
|
||||
|
||||
public static StatusVisibility from_string (string str) {
|
||||
public static Visibility from_string (string str) {
|
||||
switch (str) {
|
||||
case "public":
|
||||
return StatusVisibility.PUBLIC;
|
||||
case "unlisted":
|
||||
return StatusVisibility.UNLISTED;
|
||||
return Visibility.UNLISTED;
|
||||
case "private":
|
||||
return StatusVisibility.PRIVATE;
|
||||
return Visibility.PRIVATE;
|
||||
case "direct":
|
||||
return StatusVisibility.DIRECT;
|
||||
return Visibility.DIRECT;
|
||||
default:
|
||||
assert_not_reached();
|
||||
return Visibility.PUBLIC;
|
||||
}
|
||||
}
|
||||
|
||||
public string get_name () {
|
||||
switch (this) {
|
||||
case UNLISTED:
|
||||
return _("Unlisted");
|
||||
case PRIVATE:
|
||||
return _("Followers-only");
|
||||
case DIRECT:
|
||||
return _("Direct");
|
||||
default:
|
||||
return _("Public");
|
||||
}
|
||||
}
|
||||
|
||||
public string get_desc () {
|
||||
switch (this) {
|
||||
case PUBLIC:
|
||||
return _("Post to public timelines");
|
||||
case UNLISTED:
|
||||
return _("Don\'t post to public timelines");
|
||||
case PRIVATE:
|
||||
|
@ -45,27 +52,25 @@ public enum Tootle.API.StatusVisibility {
|
|||
case DIRECT:
|
||||
return _("Post to mentioned users only");
|
||||
default:
|
||||
assert_not_reached();
|
||||
return _("Post to public timelines");
|
||||
}
|
||||
}
|
||||
|
||||
public string get_icon () {
|
||||
switch (this) {
|
||||
case PUBLIC:
|
||||
return "network-workgroup-symbolic";
|
||||
case UNLISTED:
|
||||
return "view-private-symbolic";
|
||||
return "changes-allow-symbolic";
|
||||
case PRIVATE:
|
||||
return "security-medium-symbolic";
|
||||
return "changes-prevent-symbolic";
|
||||
case DIRECT:
|
||||
return "user-available-symbolic";
|
||||
default:
|
||||
assert_not_reached();
|
||||
return "network-workgroup-symbolic";
|
||||
}
|
||||
}
|
||||
|
||||
public static StatusVisibility[] get_all () {
|
||||
return {StatusVisibility.PUBLIC, StatusVisibility.UNLISTED, StatusVisibility.PRIVATE, StatusVisibility.DIRECT};
|
||||
public static Visibility[] all () {
|
||||
return {Visibility.PUBLIC, Visibility.UNLISTED, Visibility.PRIVATE, Visibility.DIRECT};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
using GLib;
|
||||
|
||||
public class Tootle.Accounts : Object {
|
||||
|
||||
private string dir_path;
|
||||
private string file_path;
|
||||
|
||||
public signal void switched (API.Account? account);
|
||||
public signal void updated (GenericArray<InstanceAccount> accounts);
|
||||
|
||||
public GenericArray<InstanceAccount> saved_accounts = new GenericArray<InstanceAccount> ();
|
||||
public InstanceAccount? formal {get; set;}
|
||||
public API.Account? current {get; set;}
|
||||
|
||||
public Accounts () {
|
||||
dir_path = "%s/%s".printf (GLib.Environment.get_user_config_dir (), app.application_id);
|
||||
file_path = "%s/%s".printf (dir_path, "accounts.json");
|
||||
}
|
||||
|
||||
public void switch_account (int id) {
|
||||
info ("Switching to #%i", id);
|
||||
settings.current_account = id;
|
||||
formal = saved_accounts.@get (id);
|
||||
var msg = new Soup.Message ("GET", "%s/api/v1/accounts/verify_credentials".printf (accounts.formal.instance));
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
current = API.Account.parse (root);
|
||||
switched (current);
|
||||
updated (saved_accounts);
|
||||
},
|
||||
network.on_show_error);
|
||||
}
|
||||
|
||||
public void add (InstanceAccount account) {
|
||||
info ("Adding account for %s at %s", account.username, account.instance);
|
||||
saved_accounts.add (account);
|
||||
save ();
|
||||
updated (saved_accounts);
|
||||
switch_account (saved_accounts.length - 1);
|
||||
account.start_notificator ();
|
||||
}
|
||||
|
||||
public void remove (int i) {
|
||||
var account = saved_accounts.@get (i);
|
||||
account.close_notificator ();
|
||||
|
||||
saved_accounts.remove_index (i);
|
||||
if (saved_accounts.length < 1)
|
||||
switched (null);
|
||||
else {
|
||||
var id = settings.current_account - 1;
|
||||
if (id > saved_accounts.length - 1)
|
||||
id = saved_accounts.length - 1;
|
||||
else if (id < saved_accounts.length - 1)
|
||||
id = 0;
|
||||
switch_account (id);
|
||||
}
|
||||
save ();
|
||||
updated (saved_accounts);
|
||||
|
||||
if (is_empty ()) {
|
||||
window.destroy ();
|
||||
Dialogs.NewAccount.open ();
|
||||
}
|
||||
}
|
||||
|
||||
public bool is_empty () {
|
||||
return saved_accounts.length == 0;
|
||||
}
|
||||
|
||||
public void init () {
|
||||
save (false);
|
||||
load ();
|
||||
|
||||
if (saved_accounts.length < 1)
|
||||
Dialogs.NewAccount.open ();
|
||||
else
|
||||
switch_account (settings.current_account);
|
||||
}
|
||||
|
||||
public void save (bool overwrite = true) {
|
||||
try {
|
||||
var dir = File.new_for_path (dir_path);
|
||||
if (!dir.query_exists ())
|
||||
dir.make_directory ();
|
||||
|
||||
var file = File.new_for_path (file_path);
|
||||
if (file.query_exists () && !overwrite)
|
||||
return;
|
||||
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_array ();
|
||||
saved_accounts.foreach ((acc) => {
|
||||
var node = acc.serialize ();
|
||||
builder.add_value (node);
|
||||
});
|
||||
builder.end_array ();
|
||||
|
||||
var generator = new Json.Generator ();
|
||||
generator.set_root (builder.get_root ());
|
||||
var data = generator.to_data (null);
|
||||
|
||||
if (file.query_exists ())
|
||||
file.@delete ();
|
||||
|
||||
FileOutputStream stream = file.create (FileCreateFlags.PRIVATE);
|
||||
stream.write (data.data);
|
||||
}
|
||||
catch (GLib.Error e){
|
||||
warning (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void load () {
|
||||
try {
|
||||
uint8[] data;
|
||||
string etag;
|
||||
var file = File.new_for_path (file_path);
|
||||
file.load_contents (null, out data, out etag);
|
||||
var contents = (string) data;
|
||||
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data (contents, -1);
|
||||
var array = parser.get_root ().get_array ();
|
||||
|
||||
saved_accounts = new GenericArray<InstanceAccount> ();
|
||||
array.foreach_element ((_arr, _i, node) => {
|
||||
var obj = node.get_object ();
|
||||
var account = InstanceAccount.parse (obj);
|
||||
if (account != null) {
|
||||
saved_accounts.add (account);
|
||||
account.start_notificator ();
|
||||
}
|
||||
});
|
||||
debug ("Loaded %i saved accounts", saved_accounts.length);
|
||||
updated (saved_accounts);
|
||||
}
|
||||
catch (GLib.Error e){
|
||||
warning (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,12 @@ using Granite;
|
|||
|
||||
namespace Tootle {
|
||||
|
||||
public errordomain Oopsie {
|
||||
USER,
|
||||
PARSING,
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public static Application app;
|
||||
public static Dialogs.MainWindow? window;
|
||||
public static Window window_dummy;
|
||||
|
@ -10,16 +16,23 @@ namespace Tootle {
|
|||
public static Settings settings;
|
||||
public static Accounts accounts;
|
||||
public static Network network;
|
||||
public static ImageCache image_cache;
|
||||
public static Watchlist watchlist;
|
||||
public static Cache cache;
|
||||
public static Streams streams;
|
||||
|
||||
public static bool start_hidden = false;
|
||||
|
||||
public class Application : Granite.Application {
|
||||
|
||||
public abstract signal void refresh ();
|
||||
public abstract signal void toast (string title);
|
||||
public abstract signal void error (string title, string text);
|
||||
// These are used for the GTK Inspector
|
||||
public Settings app_settings { get {return Tootle.settings; } }
|
||||
public Accounts app_accounts { get {return Tootle.accounts; } }
|
||||
public Network app_network { get {return Tootle.network; } }
|
||||
public Cache app_cache { get {return Tootle.cache; } }
|
||||
public Streams app_streams { get {return Tootle.streams; } }
|
||||
|
||||
public signal void refresh ();
|
||||
public signal void toast (string title);
|
||||
public signal void error (string title, string text);
|
||||
|
||||
public const GLib.OptionEntry[] app_options = {
|
||||
{ "hidden", 0, 0, OptionArg.NONE, ref start_hidden, "Do not show main window on start", null },
|
||||
|
@ -27,22 +40,20 @@ namespace Tootle {
|
|||
};
|
||||
|
||||
public const GLib.ActionEntry[] app_entries = {
|
||||
{"compose-toot", compose_toot_activated },
|
||||
{"toggle-reveal", on_sensitive_toggled },
|
||||
{"compose", compose_activated },
|
||||
{"back", back_activated },
|
||||
{"refresh", refresh_activated },
|
||||
{"switch-timeline", switch_timeline_activated, "i" }
|
||||
};
|
||||
|
||||
construct {
|
||||
application_id = "com.github.bleakgrey.tootle";
|
||||
application_id = Build.DOMAIN;
|
||||
flags = ApplicationFlags.FLAGS_NONE;
|
||||
program_name = "Tootle";
|
||||
build_version = "0.2.0";
|
||||
program_name = Build.NAME;
|
||||
build_version = Build.VERSION;
|
||||
}
|
||||
|
||||
public string[] ACCEL_NEW_POST = {"<Ctrl>T"};
|
||||
public string[] ACCEL_TOGGLE_REVEAL = {"<Ctrl>S"};
|
||||
public string[] ACCEL_BACK = {"<Alt>BackSpace", "<Alt>Left"};
|
||||
public string[] ACCEL_REFRESH = {"<Ctrl>R", "F5"};
|
||||
public string[] ACCEL_TIMELINE_0 = {"<Alt>1"};
|
||||
|
@ -53,6 +64,9 @@ namespace Tootle {
|
|||
public static int main (string[] args) {
|
||||
Gtk.init (ref args);
|
||||
|
||||
Stacktrace.register_handlers ();
|
||||
//assert (true == false); // I'm not crazy. It's for stacktrace testing.
|
||||
|
||||
try {
|
||||
var opt_context = new OptionContext ("- Options");
|
||||
opt_context.add_main_entries (app_options, null);
|
||||
|
@ -71,10 +85,10 @@ namespace Tootle {
|
|||
Granite.Services.Logger.DisplayLevel = Granite.Services.LogLevel.INFO;
|
||||
|
||||
settings = new Settings ();
|
||||
streams = new Streams ();
|
||||
accounts = new Accounts ();
|
||||
network = new Network ();
|
||||
image_cache = new ImageCache ();
|
||||
watchlist = new Watchlist ();
|
||||
cache = new Cache ();
|
||||
accounts.init ();
|
||||
|
||||
app.error.connect (app.on_error);
|
||||
|
@ -82,8 +96,7 @@ namespace Tootle {
|
|||
window_dummy = new Window ();
|
||||
add_window (window_dummy);
|
||||
|
||||
set_accels_for_action ("app.compose-toot", ACCEL_NEW_POST);
|
||||
set_accels_for_action ("app.toggle-reveal", ACCEL_TOGGLE_REVEAL);
|
||||
set_accels_for_action ("app.compose", ACCEL_NEW_POST);
|
||||
set_accels_for_action ("app.back", ACCEL_BACK);
|
||||
set_accels_for_action ("app.refresh", ACCEL_REFRESH);
|
||||
set_accels_for_action ("app.switch-timeline(0)", ACCEL_TIMELINE_0);
|
||||
|
@ -104,14 +117,10 @@ namespace Tootle {
|
|||
return;
|
||||
}
|
||||
|
||||
debug ("Creating new window");
|
||||
if (accounts.is_empty ())
|
||||
Dialogs.NewAccount.open ();
|
||||
else {
|
||||
info ("Creating new window");
|
||||
window = new Dialogs.MainWindow (this);
|
||||
window.present ();
|
||||
}
|
||||
}
|
||||
|
||||
protected void on_error (string title, string msg){
|
||||
var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (title, msg, "dialog-warning");
|
||||
|
@ -120,12 +129,8 @@ namespace Tootle {
|
|||
message_dialog.destroy ();
|
||||
}
|
||||
|
||||
private void on_sensitive_toggled () {
|
||||
window.button_reveal.clicked ();
|
||||
}
|
||||
|
||||
private void compose_toot_activated () {
|
||||
Dialogs.Compose.open ();
|
||||
private void compose_activated () {
|
||||
new Dialogs.Compose ();
|
||||
}
|
||||
|
||||
private void back_activated () {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
public class Build {
|
||||
|
||||
public const string NAME = "Tootle";
|
||||
public const string WEBSITE = "https://github.com/bleakgrey/tootle";
|
||||
public const string DOMAIN = "com.github.bleakgrey.tootle";
|
||||
public const string RESOURCES = "/com/github/bleakgrey/tootle/";
|
||||
public const string VERSION = "1.0.0";
|
||||
|
||||
}
|
|
@ -30,19 +30,21 @@ public class Tootle.Desktop {
|
|||
}
|
||||
|
||||
// Download a file from the web to a user's configured Downloads folder
|
||||
public static void download_file (string url) {
|
||||
debug ("Downloading file: %s", url);
|
||||
public delegate void DownloadCallback (string path);
|
||||
public static void download (string url, DownloadCallback? cb = null, Network.ErrorCallback? ecb = null) {
|
||||
info (@"Downloading file: $url...");
|
||||
|
||||
var i = url.last_index_of ("/");
|
||||
var name = url.substring (i + 1, url.length - i - 1);
|
||||
if (name == null)
|
||||
name = "unknown";
|
||||
name = _("Unknown Attachment");
|
||||
|
||||
var dir_path = "%s/%s".printf (GLib.Environment.get_user_special_dir (UserDirectory.DOWNLOAD), app.program_name);
|
||||
var file_path = "%s/%s".printf (dir_path, name);
|
||||
var downloads = GLib.Environment.get_user_special_dir (UserDirectory.DOWNLOAD);
|
||||
var dir_path = @"$downloads/$(Build.NAME)";
|
||||
var file_path = @"$dir_path/$name";
|
||||
|
||||
var msg = new Soup.Message("GET", url);
|
||||
msg.finished.connect(() => {
|
||||
new Request.GET (url)
|
||||
.then ((sess, msg) => {
|
||||
try {
|
||||
var dir = File.new_for_path (dir_path);
|
||||
if (!dir.query_exists ())
|
||||
|
@ -54,13 +56,16 @@ public class Tootle.Desktop {
|
|||
FileOutputStream stream = file.create (FileCreateFlags.PRIVATE);
|
||||
stream.write (data);
|
||||
}
|
||||
app.toast (_("Media downloaded"));
|
||||
info ("OK");
|
||||
cb (file_path);
|
||||
|
||||
} catch (Error e) {
|
||||
app.toast (e.message);
|
||||
warning ("Error: %s\n", e.message);
|
||||
ecb (0, e.message);
|
||||
}
|
||||
});
|
||||
network.queue (msg);
|
||||
})
|
||||
.on_error ((code, reason) => ecb)
|
||||
.exec ();
|
||||
}
|
||||
|
||||
public static string fallback_icon (string normal, string fallback) {
|
||||
|
|
|
@ -1,241 +1,145 @@
|
|||
using Gtk;
|
||||
|
||||
public class Tootle.Dialogs.Compose : Dialog {
|
||||
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/dialogs/compose.ui")]
|
||||
public class Tootle.Dialogs.Compose : Window {
|
||||
|
||||
private static Compose dialog;
|
||||
public API.Status? status { get; construct set; }
|
||||
public string style_class { get; construct set; }
|
||||
public string label { get; construct set; }
|
||||
public int char_limit {
|
||||
get {
|
||||
return 250;
|
||||
}
|
||||
}
|
||||
|
||||
protected TextView text;
|
||||
private ScrolledWindow scroll;
|
||||
private Label counter;
|
||||
private Widgets.ImageToggleButton spoiler;
|
||||
private MenuButton visibility;
|
||||
private Button attach;
|
||||
private Button cancel;
|
||||
private Button publish;
|
||||
protected Widgets.AttachmentGrid attachments;
|
||||
private Revealer spoiler_revealer;
|
||||
private Entry spoiler_text;
|
||||
[GtkChild]
|
||||
protected Box box;
|
||||
|
||||
protected API.Status? replying_to;
|
||||
protected API.Status? redrafting;
|
||||
protected API.StatusVisibility visibility_opt = API.StatusVisibility.PUBLIC;
|
||||
protected int char_limit;
|
||||
[GtkChild]
|
||||
protected Revealer cw_revealer;
|
||||
[GtkChild]
|
||||
protected ToggleButton cw_button;
|
||||
[GtkChild]
|
||||
protected Entry cw;
|
||||
[GtkChild]
|
||||
protected Label counter;
|
||||
|
||||
public Compose (API.Status? _replying_to = null, API.Status? _redrafting = null) {
|
||||
border_width = 6;
|
||||
deletable = false;
|
||||
resizable = true;
|
||||
title = _("Toot");
|
||||
[GtkChild]
|
||||
protected MenuButton visibility_button;
|
||||
[GtkChild]
|
||||
protected Image visibility_icon;
|
||||
protected Widgets.VisibilityPopover visibility_popover;
|
||||
[GtkChild]
|
||||
protected Button post_button;
|
||||
|
||||
[GtkChild]
|
||||
protected TextView content;
|
||||
|
||||
construct {
|
||||
transient_for = window;
|
||||
char_limit = settings.char_limit;
|
||||
replying_to = _replying_to;
|
||||
redrafting = _redrafting;
|
||||
|
||||
if (replying_to != null)
|
||||
visibility_opt = replying_to.visibility;
|
||||
if (redrafting != null)
|
||||
visibility_opt = redrafting.visibility;
|
||||
post_button.label = label;
|
||||
foreach (Widget w in new Widget[] { visibility_button, post_button })
|
||||
w.get_style_context ().add_class (style_class);
|
||||
|
||||
var actions = get_action_area ().get_parent () as Box;
|
||||
var content = get_content_area ();
|
||||
get_action_area ().hexpand = false;
|
||||
|
||||
visibility = get_visibility_btn ();
|
||||
visibility.tooltip_text = _("Post Visibility");
|
||||
visibility.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||
visibility.get_style_context ().remove_class ("image-button");
|
||||
visibility.can_default = false;
|
||||
(visibility as Widget).set_focus_on_click (false);
|
||||
|
||||
attach = new Button.from_icon_name ("mail-attachment-symbolic");
|
||||
attach.tooltip_text = _("Add Media");
|
||||
attach.valign = Align.CENTER;
|
||||
attach.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||
attach.get_style_context ().remove_class ("image-button");
|
||||
attach.can_default = false;
|
||||
(attach as Widget).set_focus_on_click (false);
|
||||
attach.clicked.connect (() => attachments.select ());
|
||||
|
||||
spoiler = new Widgets.ImageToggleButton ("image-red-eye-symbolic");
|
||||
spoiler.tooltip_text = _("Spoiler Warning");
|
||||
spoiler.set_action ();
|
||||
spoiler.toggled.connect (() => {
|
||||
spoiler_revealer.reveal_child = spoiler.active;
|
||||
validate ();
|
||||
visibility_popover = new Widgets.VisibilityPopover.with_button (visibility_button);
|
||||
visibility_popover.bind_property ("selected", visibility_icon, "icon-name", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
target.set_string (((API.Visibility)src).get_icon ());
|
||||
return true;
|
||||
});
|
||||
|
||||
cancel = add_button (_("Cancel"), 5) as Button;
|
||||
cancel.clicked.connect(() => destroy ());
|
||||
cw_button.bind_property ("active", cw_revealer, "reveal_child", BindingFlags.SYNC_CREATE);
|
||||
|
||||
if (redrafting != null) {
|
||||
publish = add_button (_("Redraft"), 5) as Button;
|
||||
publish.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);
|
||||
publish.clicked.connect (redraft_post);
|
||||
cw_button.toggled.connect (validate);
|
||||
cw.buffer.deleted_text.connect (() => validate ());
|
||||
cw.buffer.inserted_text.connect (() => validate ());
|
||||
content.buffer.changed.connect (validate);
|
||||
post_button.clicked.connect (on_post_button_clicked);
|
||||
|
||||
if (status.spoiler_text != null) {
|
||||
cw.text = status.spoiler_text;
|
||||
cw_button.active = true;
|
||||
}
|
||||
else {
|
||||
publish = add_button (_("Toot!"), 5) as Button;
|
||||
publish.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
|
||||
publish.clicked.connect (publish_post);
|
||||
content.buffer.text = Html.remove_tags (status.content);
|
||||
|
||||
show ();
|
||||
}
|
||||
|
||||
spoiler_text = new Entry ();
|
||||
spoiler_text.margin_start = 6;
|
||||
spoiler_text.margin_end = 6;
|
||||
spoiler_text.placeholder_text = _("Write your warning here");
|
||||
spoiler_text.changed.connect (validate);
|
||||
|
||||
spoiler_revealer = new Revealer ();
|
||||
spoiler_revealer.add (spoiler_text);
|
||||
|
||||
text = new TextView ();
|
||||
text.get_style_context ().add_class ("toot-text");
|
||||
text.wrap_mode = WrapMode.WORD;
|
||||
text.accepts_tab = false;
|
||||
text.vexpand = true;
|
||||
text.buffer.changed.connect (validate);
|
||||
|
||||
scroll = new ScrolledWindow (null, null);
|
||||
scroll.hscrollbar_policy = PolicyType.NEVER;
|
||||
scroll.min_content_height = 120;
|
||||
scroll.vexpand = true;
|
||||
scroll.propagate_natural_height = true;
|
||||
scroll.margin_start = 6;
|
||||
scroll.margin_end = 6;
|
||||
scroll.add (text);
|
||||
scroll.show_all ();
|
||||
|
||||
attachments = new Widgets.AttachmentGrid (true);
|
||||
counter = new Label ("");
|
||||
|
||||
actions.pack_start (counter, false, false, 6);
|
||||
actions.pack_end (spoiler, false, false, 6);
|
||||
actions.pack_end (visibility, false, false, 0);
|
||||
actions.pack_end (attach, false, false, 6);
|
||||
content.pack_start (spoiler_revealer, false, false, 6);
|
||||
content.pack_start (scroll, false, false, 6);
|
||||
content.pack_start (attachments, false, false, 6);
|
||||
content.set_size_request (350, 120);
|
||||
|
||||
if (replying_to != null) {
|
||||
spoiler.active = replying_to.sensitive;
|
||||
var status_spoiler_text = replying_to.spoiler_text != null ? replying_to.spoiler_text : "";
|
||||
spoiler_text.set_text (status_spoiler_text);
|
||||
}
|
||||
if (redrafting != null) {
|
||||
spoiler.active = redrafting.sensitive;
|
||||
var status_spoiler_text = redrafting.spoiler_text != null ? redrafting.spoiler_text : "";
|
||||
spoiler_text.set_text (status_spoiler_text);
|
||||
public Compose () {
|
||||
Object (status: new API.Status.empty (), style_class: STYLE_CLASS_SUGGESTED_ACTION, label: _("Post"));
|
||||
}
|
||||
|
||||
destroy.connect (() => dialog = null);
|
||||
|
||||
show_all ();
|
||||
attachments.hide ();
|
||||
text.grab_focus ();
|
||||
validate ();
|
||||
public Compose.redraft (API.Status status) {
|
||||
Object (status: status, style_class: STYLE_CLASS_DESTRUCTIVE_ACTION, label: _("Redraft"));
|
||||
}
|
||||
|
||||
private MenuButton get_visibility_btn () {
|
||||
var button = new MenuButton ();
|
||||
var menu = new Popover (null);
|
||||
var box = new Box (Orientation.VERTICAL, 6);
|
||||
box.margin = 12;
|
||||
menu.add (box);
|
||||
button.direction = ArrowType.DOWN;
|
||||
button.image = new Image.from_icon_name (visibility_opt.get_icon (), IconSize.BUTTON);
|
||||
|
||||
RadioButton? first = null;
|
||||
foreach (API.StatusVisibility opt in API.StatusVisibility.get_all ()){
|
||||
var item = new RadioButton.with_label_from_widget (first, opt.get_desc ());
|
||||
if (first == null)
|
||||
first = item;
|
||||
|
||||
item.toggled.connect (() => {
|
||||
visibility_opt = opt;
|
||||
(button.image as Image).icon_name = visibility_opt.get_icon ();
|
||||
});
|
||||
item.active = visibility_opt == opt;
|
||||
box.pack_start (item, false, false, 0);
|
||||
public Compose.reply (API.Status status) {
|
||||
var template = new API.Status.empty ();
|
||||
template.in_reply_to_id = status.in_reply_to_id;
|
||||
template.in_reply_to_account_id = status.in_reply_to_account_id;
|
||||
template.content = status.formal.get_reply_mentions ();
|
||||
Object (status: template, style_class: STYLE_CLASS_SUGGESTED_ACTION, label: _("Reply"));
|
||||
visibility_popover.selected = status.visibility;
|
||||
}
|
||||
|
||||
box.show_all ();
|
||||
button.use_popover = true;
|
||||
button.popover = menu;
|
||||
button.valign = Align.CENTER;
|
||||
button.show ();
|
||||
return button;
|
||||
}
|
||||
|
||||
private void validate () {
|
||||
var remain = char_limit - text.buffer.get_char_count ();
|
||||
if (spoiler.active)
|
||||
remain -= (int)spoiler_text.buffer.length;
|
||||
protected void validate () {
|
||||
var remain = char_limit - content.buffer.get_char_count ();
|
||||
if (cw_button.active)
|
||||
remain -= (int)cw.buffer.length;
|
||||
|
||||
counter.label = remain.to_string ();
|
||||
publish.sensitive = remain >= 0;
|
||||
post_button.sensitive = remain >= 0;
|
||||
visibility_button.sensitive = true;
|
||||
box.sensitive = true;
|
||||
}
|
||||
|
||||
public static void open (string? text = null, API.Status? reply_to = null) {
|
||||
if (dialog == null){
|
||||
dialog = new Compose (reply_to);
|
||||
|
||||
if (text != null)
|
||||
dialog.text.buffer.text = text;
|
||||
}
|
||||
else if (text != null)
|
||||
dialog.text.buffer.text += text;
|
||||
protected void on_error (int32 code, string reason) { //TODO: display errors
|
||||
warning (reason);
|
||||
validate ();
|
||||
}
|
||||
|
||||
public static void reply (API.Status status) {
|
||||
if (dialog != null)
|
||||
return;
|
||||
protected void on_post_button_clicked () {
|
||||
post_button.sensitive = false;
|
||||
visibility_button.sensitive = false;
|
||||
box.sensitive = false;
|
||||
|
||||
open (null, status);
|
||||
dialog.text.buffer.text = status.get_reply_mentions ();
|
||||
if (status.id >= 0) {
|
||||
info ("Removing old status...");
|
||||
status.poof (publish, on_error);
|
||||
}
|
||||
else {
|
||||
publish ();
|
||||
}
|
||||
}
|
||||
|
||||
public static void redraft (API.Status status) {
|
||||
if (dialog != null)
|
||||
return;
|
||||
dialog = new Compose (null, status);
|
||||
protected void publish () {
|
||||
info ("Publishing new status...");
|
||||
status.content = content.buffer.text;
|
||||
status.spoiler_text = cw.text;
|
||||
|
||||
if (status.attachments != null) {
|
||||
foreach (API.Attachment attachment in status.attachments)
|
||||
dialog.attachments.append (attachment);
|
||||
var req = new Request.POST ("/api/v1/statuses")
|
||||
.with_account ()
|
||||
.with_param ("visibility", visibility_popover.selected.to_string ())
|
||||
.with_param ("status", Html.uri_encode (status.content));
|
||||
|
||||
if (cw_button.active) {
|
||||
req.with_param ("sensitive", "true");
|
||||
req.with_param ("spoiler_text", Html.uri_encode (cw.text));
|
||||
}
|
||||
|
||||
var content = Html.simplify (status.content);
|
||||
content = Html.remove_tags (content);
|
||||
content = Widgets.RichLabel.restore_entities (content);
|
||||
dialog.text.buffer.text = content;
|
||||
}
|
||||
if (status.in_reply_to_id != null)
|
||||
req.with_param ("in_reply_to_id", status.in_reply_to_id);
|
||||
if (status.in_reply_to_account_id != null)
|
||||
req.with_param ("in_reply_to_account_id", status.in_reply_to_account_id);
|
||||
|
||||
private void publish_post () {
|
||||
var pars = "?status=%s&visibility=%s".printf (Html.uri_encode (text.buffer.text), visibility_opt.to_string ());
|
||||
pars += attachments.get_uri_array ();
|
||||
if (replying_to != null)
|
||||
pars += "&in_reply_to_id=%s".printf (replying_to.id.to_string ());
|
||||
|
||||
if (spoiler.active) {
|
||||
pars += "&sensitive=true";
|
||||
pars += "&spoiler_text=" + Html.uri_encode (spoiler_text.buffer.text);
|
||||
}
|
||||
|
||||
var url = "%s/api/v1/statuses%s".printf (accounts.formal.instance, pars);
|
||||
var msg = new Soup.Message ("POST", url);
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, mess) => {
|
||||
req.then ((sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
var status = API.Status.parse (root);
|
||||
debug ("Posted: %s", status.id.to_string ()); //TODO: Live updates
|
||||
var status = new API.Status (root);
|
||||
info ("OK: status id is %s", status.id.to_string ());
|
||||
destroy ();
|
||||
});
|
||||
}
|
||||
|
||||
private void redraft_post () {
|
||||
redrafting.poof ((sess, msg) => {
|
||||
publish_post ();
|
||||
});
|
||||
})
|
||||
.on_error (on_error)
|
||||
.exec ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,122 +1,55 @@
|
|||
using Gtk;
|
||||
using Gdk;
|
||||
|
||||
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/dialogs/main.ui")]
|
||||
public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
|
||||
|
||||
private Overlay overlay;
|
||||
public Granite.Widgets.Toast toast;
|
||||
private Grid grid;
|
||||
private Stack view_stack;
|
||||
private Stack timeline_stack;
|
||||
[GtkChild]
|
||||
protected Stack view_stack;
|
||||
[GtkChild]
|
||||
protected Stack timeline_stack;
|
||||
|
||||
public HeaderBar header;
|
||||
public Granite.Widgets.ModeButton button_mode;
|
||||
private Widgets.AccountsButton button_accounts;
|
||||
private Spinner spinner;
|
||||
private Button button_toot;
|
||||
private Button button_back;
|
||||
public Button button_reveal;
|
||||
|
||||
public Views.Home home = new Views.Home ();
|
||||
public Views.Notifications notifications = new Views.Notifications ();
|
||||
public Views.Local local = new Views.Local ();
|
||||
public Views.Federated federated = new Views.Federated ();
|
||||
[GtkChild]
|
||||
protected HeaderBar header;
|
||||
[GtkChild]
|
||||
protected Button back_button;
|
||||
[GtkChild]
|
||||
protected Button compose_button;
|
||||
[GtkChild]
|
||||
protected Granite.Widgets.ModeButton timeline_switcher;
|
||||
[GtkChild]
|
||||
protected Widgets.AccountsButton accounts_button;
|
||||
|
||||
construct {
|
||||
|
||||
var provider = new Gtk.CssProvider ();
|
||||
provider.load_from_resource ("/com/github/bleakgrey/tootle/app.css");
|
||||
StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
provider.load_from_resource (@"$(Build.RESOURCES)app.css");
|
||||
StyleContext.add_provider_for_screen (Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
back_button.clicked.connect (() => back ());
|
||||
Desktop.set_hotkey_tooltip (back_button, _("Back"), app.ACCEL_BACK);
|
||||
|
||||
compose_button.clicked.connect (() => new Dialogs.Compose ());
|
||||
Desktop.set_hotkey_tooltip (compose_button, _("Compose"), app.ACCEL_NEW_POST);
|
||||
|
||||
timeline_switcher.mode_changed.connect (on_mode_changed);
|
||||
|
||||
add_header_view (new Views.Home (), app.ACCEL_TIMELINE_0, 0);
|
||||
add_header_view (new Views.Notifications (), app.ACCEL_TIMELINE_1, 1);
|
||||
add_header_view (new Views.Local (), app.ACCEL_TIMELINE_2, 2);
|
||||
add_header_view (new Views.Federated (), app.ACCEL_TIMELINE_3, 3);
|
||||
timeline_switcher.set_active (0);
|
||||
|
||||
button_press_event.connect (on_button_press);
|
||||
settings.changed.connect (update_theme);
|
||||
update_theme ();
|
||||
|
||||
timeline_stack = new Stack();
|
||||
timeline_stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
|
||||
timeline_stack.show ();
|
||||
view_stack = new Stack();
|
||||
view_stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
|
||||
view_stack.show ();
|
||||
view_stack.add_named (timeline_stack, "0");
|
||||
view_stack.hexpand = view_stack.vexpand = true;
|
||||
|
||||
spinner = new Spinner ();
|
||||
spinner.active = true;
|
||||
|
||||
button_accounts = new Widgets.AccountsButton ();
|
||||
|
||||
button_back = new Button ();
|
||||
button_back.valign = Align.CENTER;
|
||||
button_back.label = _("Back");
|
||||
button_back.get_style_context ().add_class (Granite.STYLE_CLASS_BACK_BUTTON);
|
||||
button_back.clicked.connect (() => back ());
|
||||
Desktop.set_hotkey_tooltip (button_back, null, app.ACCEL_BACK);
|
||||
|
||||
button_toot = new Button ();
|
||||
button_toot.valign = Align.CENTER;
|
||||
button_toot.image = new Image.from_icon_name ("document-edit-symbolic", IconSize.LARGE_TOOLBAR);
|
||||
button_toot.clicked.connect (() => Dialogs.Compose.open ());
|
||||
Desktop.set_hotkey_tooltip (button_toot, _("Toot"), app.ACCEL_NEW_POST);
|
||||
|
||||
button_reveal = new Button ();
|
||||
button_reveal.valign = Align.CENTER;
|
||||
button_reveal.image = new Image.from_icon_name ("image-red-eye-symbolic", IconSize.LARGE_TOOLBAR);
|
||||
Desktop.set_hotkey_tooltip (button_reveal, _("Toggle content"), app.ACCEL_TOGGLE_REVEAL);
|
||||
|
||||
button_mode = new Granite.Widgets.ModeButton ();
|
||||
button_mode.get_style_context ().add_class ("mode");
|
||||
button_mode.vexpand = true;
|
||||
button_mode.valign = Align.FILL;
|
||||
button_mode.mode_changed.connect (on_mode_changed);
|
||||
button_mode.show ();
|
||||
|
||||
header = new HeaderBar ();
|
||||
header.get_style_context ().add_class ("compact");
|
||||
header.show_close_button = true;
|
||||
header.title = _("Tootle");
|
||||
header.custom_title = button_mode;
|
||||
header.pack_start (button_back);
|
||||
header.pack_start (button_toot);
|
||||
header.pack_end (button_accounts);
|
||||
header.pack_end (button_reveal);
|
||||
header.pack_end (spinner);
|
||||
header.show_all ();
|
||||
|
||||
grid = new Grid ();
|
||||
grid.attach (view_stack, 0, 0, 1, 1);
|
||||
|
||||
add_header_view (home, app.ACCEL_TIMELINE_0, 0);
|
||||
add_header_view (notifications, app.ACCEL_TIMELINE_1, 1);
|
||||
add_header_view (local, app.ACCEL_TIMELINE_2, 2);
|
||||
add_header_view (federated, app.ACCEL_TIMELINE_3, 3);
|
||||
button_mode.set_active (0);
|
||||
|
||||
toast = new Granite.Widgets.Toast ("");
|
||||
overlay = new Overlay ();
|
||||
overlay.add_overlay (grid);
|
||||
overlay.add_overlay (toast);
|
||||
overlay.set_size_request (450, 600);
|
||||
add (overlay);
|
||||
|
||||
update_header ();
|
||||
restore_state ();
|
||||
show_all ();
|
||||
|
||||
button_reveal.hide ();
|
||||
}
|
||||
|
||||
public MainWindow (Gtk.Application _app) {
|
||||
application = _app;
|
||||
icon_name = "com.github.bleakgrey.tootle";
|
||||
resizable = true;
|
||||
window_position = WindowPosition.CENTER;
|
||||
set_titlebar (header);
|
||||
update_header ();
|
||||
|
||||
app.toast.connect (on_toast);
|
||||
network.started.connect (() => spinner.show ());
|
||||
network.finished.connect (() => spinner.hide ());
|
||||
accounts.updated (accounts.saved_accounts);
|
||||
button_press_event.connect (on_button_press);
|
||||
public MainWindow (Gtk.Application app) {
|
||||
Object (application: app, icon_name: Build.DOMAIN, resizable: true, window_position: WindowPosition.CENTER);
|
||||
if (accounts.is_empty ())
|
||||
open_view (new Views.NewAccount (false));
|
||||
}
|
||||
|
||||
private bool on_button_press (EventButton ev) {
|
||||
|
@ -125,22 +58,19 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
|
|||
return false;
|
||||
}
|
||||
|
||||
private void add_header_view (Views.Abstract view, string[] accelerators, int32 num) {
|
||||
private void add_header_view (Views.Base view, string[] accelerators, int32 num) {
|
||||
var img = new Image.from_icon_name (view.get_icon (), IconSize.LARGE_TOOLBAR);
|
||||
Desktop.set_hotkey_tooltip (img, view.get_name (), accelerators);
|
||||
button_mode.append (img);
|
||||
timeline_switcher.append (img);
|
||||
view.image = img;
|
||||
timeline_stack.add_named (view, num.to_string ());
|
||||
|
||||
if (view is Views.Notifications)
|
||||
img.pixel_size = 20; // For some reason Notifications icon is too small without this
|
||||
}
|
||||
|
||||
public int get_visible_id () {
|
||||
return int.parse (view_stack.get_visible_child_name ());
|
||||
}
|
||||
|
||||
public bool open_view (Views.Abstract widget) {
|
||||
public bool open_view (Views.Base widget) {
|
||||
var i = get_visible_id ();
|
||||
i++;
|
||||
widget.stack_pos = i;
|
||||
|
@ -171,7 +101,7 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
|
|||
}
|
||||
}
|
||||
|
||||
public override bool delete_event (Gdk.EventAny event) {
|
||||
public override bool delete_event (EventAny event) {
|
||||
destroy.connect (() => {
|
||||
if (!settings.always_online || accounts.is_empty ())
|
||||
app.remove_window (window_dummy);
|
||||
|
@ -181,39 +111,28 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
|
|||
}
|
||||
|
||||
public void switch_timeline (int32 timeline_no) {
|
||||
button_mode.set_active (timeline_no);
|
||||
timeline_switcher.set_active (timeline_no);
|
||||
}
|
||||
|
||||
private void update_theme () {
|
||||
var provider = new Gtk.CssProvider ();
|
||||
var is_dark = settings.dark_theme;
|
||||
var theme = is_dark ? "dark" : "light";
|
||||
provider.load_from_resource ("/com/github/bleakgrey/tootle/%s.css".printf (theme));
|
||||
StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
Gtk.Settings.get_default ().gtk_application_prefer_dark_theme = is_dark;
|
||||
Gtk.Settings.get_default ().gtk_application_prefer_dark_theme = settings.dark_theme;
|
||||
}
|
||||
|
||||
private void update_header () {
|
||||
bool primary_mode = get_visible_id () == 0;
|
||||
button_mode.sensitive = primary_mode;
|
||||
button_mode.opacity = primary_mode ? 1 : 0; //Prevent HeaderBar height jitter
|
||||
button_toot.set_visible (primary_mode);
|
||||
button_back.set_visible (!primary_mode);
|
||||
button_accounts.set_visible (true);
|
||||
}
|
||||
|
||||
private void on_toast (string msg){
|
||||
toast.title = msg;
|
||||
toast.send_notification ();
|
||||
timeline_switcher.sensitive = primary_mode;
|
||||
timeline_switcher.opacity = primary_mode ? 1 : 0; //Prevent HeaderBar height jitter
|
||||
compose_button.visible = primary_mode;
|
||||
back_button.visible = !primary_mode;
|
||||
}
|
||||
|
||||
private void on_mode_changed (Widget widget) {
|
||||
var visible = timeline_stack.get_visible_child () as Views.Abstract;
|
||||
var visible = timeline_stack.get_visible_child () as Views.Base;
|
||||
visible.current = false;
|
||||
|
||||
timeline_stack.set_visible_child_name (button_mode.selected.to_string ());
|
||||
timeline_stack.set_visible_child_name (timeline_switcher.selected.to_string ());
|
||||
|
||||
visible = timeline_stack.get_visible_child () as Views.Abstract;
|
||||
visible = timeline_stack.get_visible_child () as Views.Base;
|
||||
visible.current = true;
|
||||
visible.on_set_current ();
|
||||
}
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
using Gtk;
|
||||
|
||||
public class Tootle.Dialogs.NewAccount : Dialog {
|
||||
|
||||
private static NewAccount dialog;
|
||||
|
||||
private Grid grid;
|
||||
private Button button_done;
|
||||
private Image logo;
|
||||
private Entry instance_entry;
|
||||
private Label instance_register;
|
||||
private Label code_name;
|
||||
private Entry code_entry;
|
||||
|
||||
private string? instance;
|
||||
private string? client_id;
|
||||
private string? client_secret;
|
||||
private string? code;
|
||||
private string? token;
|
||||
private string? username;
|
||||
|
||||
public NewAccount () {
|
||||
border_width = 6;
|
||||
deletable = true;
|
||||
resizable = false;
|
||||
title = _("New Account");
|
||||
transient_for = window;
|
||||
|
||||
logo = new Image.from_resource ("/com/github/bleakgrey/tootle/logo128");
|
||||
logo.halign = Align.CENTER;
|
||||
logo.hexpand = true;
|
||||
logo.margin_bottom = 24;
|
||||
|
||||
instance_entry = new Entry ();
|
||||
instance_entry.width_chars = 30;
|
||||
|
||||
instance_register = new Label ("<a href=\"https://joinmastodon.org/\">%s</a>".printf (_("What's an instance?")));
|
||||
instance_register.halign = Align.END;
|
||||
instance_register.set_use_markup (true);
|
||||
|
||||
code_name = new Widgets.AlignedLabel (_("Code:"));
|
||||
|
||||
code_entry = new Entry ();
|
||||
code_entry.secondary_icon_name = "dialog-question-symbolic";
|
||||
code_entry.secondary_icon_tooltip_text = _("Paste your instance authorization code here");
|
||||
code_entry.secondary_icon_activatable = false;
|
||||
|
||||
button_done = new Button.with_label (_("Add Account"));
|
||||
button_done.clicked.connect (on_done_clicked);
|
||||
button_done.halign = Align.END;
|
||||
button_done.margin_top = 24;
|
||||
|
||||
grid = new Grid ();
|
||||
grid.column_spacing = 12;
|
||||
grid.row_spacing = 6;
|
||||
grid.hexpand = true;
|
||||
grid.halign = Align.CENTER;
|
||||
grid.attach (logo, 0, 0, 2, 1);
|
||||
grid.attach (new Widgets.AlignedLabel (_("Instance:")), 0, 1);
|
||||
grid.attach (instance_entry, 1, 1);
|
||||
grid.attach (code_name, 0, 3);
|
||||
grid.attach (code_entry, 1, 3);
|
||||
grid.attach (instance_register, 1, 5);
|
||||
grid.attach (button_done, 1, 10);
|
||||
|
||||
var content = get_content_area () as Box;
|
||||
content.pack_start (grid, false, false, 0);
|
||||
|
||||
destroy.connect (() => {
|
||||
dialog = null;
|
||||
|
||||
if (accounts.is_empty ())
|
||||
app.remove_window (window_dummy);
|
||||
});
|
||||
|
||||
show_all ();
|
||||
clear ();
|
||||
}
|
||||
|
||||
private void clear () {
|
||||
code_name.hide ();
|
||||
code_entry.hide ();
|
||||
code_entry.text = "";
|
||||
client_id = client_secret = code = token = null;
|
||||
}
|
||||
|
||||
private void on_done_clicked () {
|
||||
instance = "https://" + instance_entry.text
|
||||
.replace ("/", "")
|
||||
.replace (":", "")
|
||||
.replace ("https", "")
|
||||
.replace ("http", "");
|
||||
code = code_entry.text;
|
||||
|
||||
if (client_id == null || client_secret == null) {
|
||||
request_client_tokens ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == "")
|
||||
app.error (_("Error"), _("Please paste valid instance authorization code"));
|
||||
else
|
||||
try_auth (code);
|
||||
}
|
||||
|
||||
private void request_client_tokens (){
|
||||
var pars = "?client_name=Tootle";
|
||||
pars += "&redirect_uris=urn:ietf:wg:oauth:2.0:oob";
|
||||
pars += "&website=https://github.com/bleakgrey/tootle";
|
||||
pars += "&scopes=read%20write%20follow";
|
||||
|
||||
grid.sensitive = false;
|
||||
var message = new Soup.Message ("POST", "%s/api/v1/apps%s".printf (instance, pars));
|
||||
network.queue (message, (sess, msg) => {
|
||||
grid.sensitive = true;
|
||||
|
||||
var root = network.parse (msg);
|
||||
var id = root.get_string_member ("client_id");
|
||||
var secret = root.get_string_member ("client_secret");
|
||||
client_id = id;
|
||||
client_secret = secret;
|
||||
|
||||
info ("Received tokens from %s", instance);
|
||||
request_auth_code ();
|
||||
code_name.show ();
|
||||
code_entry.show ();
|
||||
}, (status, reason) => {
|
||||
network.on_show_error (status, reason);
|
||||
});
|
||||
}
|
||||
|
||||
private void request_auth_code (){
|
||||
var pars = "?scope=read%20write%20follow";
|
||||
pars += "&response_type=code";
|
||||
pars += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob";
|
||||
pars += "&client_id=" + client_id;
|
||||
|
||||
info ("Requesting auth token");
|
||||
Desktop.open_uri ("%s/oauth/authorize%s".printf (instance, pars));
|
||||
}
|
||||
|
||||
private void try_auth (string code){
|
||||
var pars = "?client_id=" + client_id;
|
||||
pars += "&client_secret=" + client_secret;
|
||||
pars += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob";
|
||||
pars += "&grant_type=authorization_code";
|
||||
pars += "&code=" + code;
|
||||
|
||||
var message = new Soup.Message ("POST", "%s/oauth/token%s".printf (instance, pars));
|
||||
network.queue (message, (sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
token = root.get_string_member ("access_token");
|
||||
|
||||
info ("Got access token");
|
||||
get_username ();
|
||||
}, (status, reason) => {
|
||||
network.on_show_error (status, reason);
|
||||
});
|
||||
}
|
||||
|
||||
private void get_username () {
|
||||
var message = new Soup.Message("GET", "%s/api/v1/accounts/verify_credentials".printf (instance));
|
||||
message.request_headers.append ("Authorization", "Bearer " + token);
|
||||
network.queue (message, (sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
username = root.get_string_member ("username");
|
||||
add_account ();
|
||||
window.show ();
|
||||
window.present ();
|
||||
destroy ();
|
||||
}, (status, reason) => {
|
||||
network.on_show_error (status, reason);
|
||||
});
|
||||
}
|
||||
|
||||
private void add_account () {
|
||||
var account = new InstanceAccount ();
|
||||
account.username = username;
|
||||
account.instance = instance;
|
||||
account.client_id = client_id;
|
||||
account.client_secret = client_secret;
|
||||
account.token = token;
|
||||
accounts.add (account);
|
||||
app.activate ();
|
||||
}
|
||||
|
||||
public static void open () {
|
||||
if (dialog == null)
|
||||
dialog = new NewAccount ();
|
||||
}
|
||||
|
||||
}
|
|
@ -88,7 +88,7 @@ public class Tootle.Dialogs.Preferences : Dialog {
|
|||
halign = Align.START;
|
||||
valign = Align.CENTER;
|
||||
margin_bottom = 6;
|
||||
settings.schema.bind (setting, this, "active", SettingsBindFlags.DEFAULT);
|
||||
settings.bind (setting, this, "active", SettingsBindFlags.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,206 +0,0 @@
|
|||
using Gtk;
|
||||
using Gee;
|
||||
|
||||
public class Tootle.Dialogs.WatchlistEditor : Dialog {
|
||||
|
||||
private static WatchlistEditor dialog;
|
||||
|
||||
private StackSwitcher switcher;
|
||||
private MenuButton button_add;
|
||||
private Button button_remove;
|
||||
private Stack stack;
|
||||
private ListStack users;
|
||||
private ListStack hashtags;
|
||||
private ActionBar actionbar;
|
||||
private Popover popover;
|
||||
private Grid popover_grid;
|
||||
private Entry popover_entry;
|
||||
private Button popover_button;
|
||||
|
||||
private const string TIP_USERS = _("You'll be notified when toots from this user appear in your Home timeline.");
|
||||
private const string TIP_HASHTAGS = _("You'll be notified when toots with this hashtag appear in any public timelines.");
|
||||
|
||||
private class ModelItem : GLib.Object {
|
||||
public string name;
|
||||
|
||||
public ModelItem (string name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private class ModelView : ListBoxRow {
|
||||
public Label label;
|
||||
public ModelView (ModelItem item) {
|
||||
label = new Label (item.name);
|
||||
label.margin = 6;
|
||||
label.halign = Align.START;
|
||||
label.justify = Justification.LEFT;
|
||||
add (label);
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
|
||||
private class Model : GLib.ListModel, GLib.Object {
|
||||
private GenericArray<ModelItem> items = new GenericArray<ModelItem> ();
|
||||
|
||||
public GLib.Type get_item_type () {
|
||||
return typeof (ModelItem);
|
||||
}
|
||||
|
||||
public uint get_n_items () {
|
||||
return items.length;
|
||||
}
|
||||
|
||||
public GLib.Object? get_item (uint position) {
|
||||
return items.@get ((int)position);
|
||||
}
|
||||
|
||||
public void append (ModelItem item) {
|
||||
this.items.add (item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Widget create_row (GLib.Object obj) {
|
||||
var item = (ModelItem) obj;
|
||||
return new ModelView (item);
|
||||
}
|
||||
|
||||
private class ListStack : ScrolledWindow {
|
||||
public Model model;
|
||||
public ListBox list;
|
||||
|
||||
public void update (ArrayList<string> array) {
|
||||
array.@foreach (item => {
|
||||
model.append (new ModelItem (item));
|
||||
return true;
|
||||
});
|
||||
list.bind_model (model, create_row);
|
||||
}
|
||||
|
||||
public ListStack (ArrayList<string> array) {
|
||||
model = new Model ();
|
||||
list = new ListBox ();
|
||||
add (list);
|
||||
update (array);
|
||||
}
|
||||
}
|
||||
|
||||
private void set_tip () {
|
||||
var is_user = stack.visible_child_name == "users";
|
||||
popover_entry.secondary_icon_tooltip_text = is_user ? TIP_USERS : TIP_HASHTAGS;
|
||||
}
|
||||
|
||||
public WatchlistEditor () {
|
||||
border_width = 6;
|
||||
deletable = false;
|
||||
resizable = false;
|
||||
transient_for = window;
|
||||
title = _("Watchlist");
|
||||
|
||||
users = new ListStack (watchlist.users);
|
||||
hashtags = new ListStack (watchlist.hashtags);
|
||||
|
||||
stack = new Stack ();
|
||||
stack.transition_type = StackTransitionType.SLIDE_LEFT_RIGHT;
|
||||
stack.hexpand = true;
|
||||
stack.vexpand = true;
|
||||
stack.add_titled (users, "users", _("Users"));
|
||||
stack.add_titled (hashtags, "hashtags", _("Hashtags"));
|
||||
|
||||
switcher = new StackSwitcher ();
|
||||
switcher.stack = stack;
|
||||
switcher.halign = Align.CENTER;
|
||||
switcher.margin_bottom = 12;
|
||||
|
||||
popover_entry = new Entry ();
|
||||
popover_entry.hexpand = true;
|
||||
popover_entry.secondary_icon_name = "dialog-information-symbolic";
|
||||
popover_entry.secondary_icon_activatable = false;
|
||||
popover_entry.activate.connect (() => submit ());
|
||||
|
||||
popover_button = new Button.with_label (_("Add"));
|
||||
popover_button.halign = Align.END;
|
||||
popover_button.margin_start = 6;
|
||||
popover_button.clicked.connect (() => submit ());
|
||||
|
||||
popover_grid = new Grid ();
|
||||
popover_grid.margin = 6;
|
||||
popover_grid.attach (popover_entry, 0, 0);
|
||||
popover_grid.attach (popover_button, 1, 0);
|
||||
popover_grid.show_all ();
|
||||
|
||||
popover = new Popover (null);
|
||||
popover.add (popover_grid);
|
||||
|
||||
button_add = new MenuButton ();
|
||||
button_add.image = new Image.from_icon_name ("list-add-symbolic", IconSize.BUTTON);
|
||||
button_add.popover = popover;
|
||||
button_add.clicked.connect (() => set_tip ());
|
||||
|
||||
button_remove = new Button ();
|
||||
button_remove.image = new Image.from_icon_name ("list-remove-symbolic", IconSize.BUTTON);
|
||||
button_remove.clicked.connect (on_remove);
|
||||
|
||||
actionbar = new ActionBar ();
|
||||
actionbar.add (button_add);
|
||||
actionbar.add (button_remove);
|
||||
|
||||
var grid = new Grid ();
|
||||
grid.attach (stack, 0, 1);
|
||||
grid.attach (actionbar, 0, 2);
|
||||
|
||||
var frame = new Frame (null);
|
||||
frame.margin_bottom = 6;
|
||||
frame.add (grid);
|
||||
frame.set_size_request (350, 350);
|
||||
|
||||
var content = get_content_area ();
|
||||
content.pack_start (switcher, true, true, 0);
|
||||
content.pack_start (frame, true, true, 0);
|
||||
|
||||
add_button (_("_Close"), ResponseType.DELETE_EVENT);
|
||||
show_all ();
|
||||
|
||||
response.connect (on_response);
|
||||
destroy.connect (() => dialog = null);
|
||||
}
|
||||
|
||||
private void on_response (int i) {
|
||||
destroy ();
|
||||
}
|
||||
|
||||
private void on_remove () {
|
||||
var is_hashtag = stack.visible_child_name == "hashtags";
|
||||
ListStack stack = is_hashtag ? hashtags : users;
|
||||
stack.list.get_selected_rows ().@foreach (_row => {
|
||||
var row = _row as ModelView;
|
||||
watchlist.remove (row.label.label, is_hashtag);
|
||||
watchlist.save ();
|
||||
row.destroy ();
|
||||
});
|
||||
}
|
||||
|
||||
private void submit () {
|
||||
if (popover_entry.text_length < 1)
|
||||
return;
|
||||
|
||||
var is_hashtag = stack.visible_child_name == "hashtags";
|
||||
var entity = popover_entry.text
|
||||
.replace ("#", "")
|
||||
.replace (" ", "");
|
||||
|
||||
watchlist.add (entity, is_hashtag);
|
||||
watchlist.save ();
|
||||
button_add.active = false;
|
||||
|
||||
var stack = is_hashtag ? hashtags : users;
|
||||
stack.list.insert (create_row (new ModelItem (entity)), 0);
|
||||
}
|
||||
|
||||
public static void open () {
|
||||
if (dialog == null)
|
||||
dialog = new WatchlistEditor ();
|
||||
}
|
||||
|
||||
}
|
|
@ -13,23 +13,24 @@ public class Tootle.Drawing {
|
|||
ctx.close_path ();
|
||||
}
|
||||
|
||||
public static Pixbuf make_pixbuf_thumbnail (Pixbuf pixbuf, int view_w, int view_h, bool fill_parent = false) {
|
||||
// Don't resize if parent view is bigger than actual image
|
||||
if (view_w >= pixbuf.width && view_h >= pixbuf.height)
|
||||
return pixbuf;
|
||||
public static void center (Cairo.Context ctx, int w, int h, int tw, int th) {
|
||||
var cx = w/2 - tw/2;
|
||||
var cy = h/2 - th/2;
|
||||
ctx.translate (cx, cy);
|
||||
}
|
||||
|
||||
//Otherwise fit the image into the parent view
|
||||
var resized_w = view_w;
|
||||
var resized_h = view_h;
|
||||
//resized_w = (pixbuf.width * view_h) / pixbuf.height;
|
||||
//resized_h = (pixbuf.height * view_w) / pixbuf.width;
|
||||
public static Pixbuf make_thumbnail (Pixbuf pb, int view_w, int view_h) {
|
||||
if (view_w >= pb.width && view_h >= pb.height)
|
||||
return pb;
|
||||
|
||||
if (fill_parent)
|
||||
resized_h = (pixbuf.height * view_w) / pixbuf.width;
|
||||
else
|
||||
resized_w = (pixbuf.width * view_h) / pixbuf.height;
|
||||
double ratio_x = (double) view_w / (double) pb.width;
|
||||
double ratio_y = (double) view_h / (double) pb.height;
|
||||
double ratio = ratio_x < ratio_y ? ratio_x : ratio_y;
|
||||
|
||||
return pixbuf.scale_simple (resized_w, resized_h, InterpType.BILINEAR);
|
||||
return pb.scale_simple (
|
||||
(int) (pb.width * ratio),
|
||||
(int) (pb.height * ratio),
|
||||
InterpType.BILINEAR);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,30 +1,46 @@
|
|||
public class Tootle.Html {
|
||||
|
||||
public static string remove_tags (string content) {
|
||||
var all_tags = new Regex("<(.|\n)*?>", RegexCompileFlags.CASELESS);
|
||||
return all_tags.replace(content, -1, 0, "");
|
||||
var all_tags = new Regex ("<(.|\n)*?>", RegexCompileFlags.CASELESS);
|
||||
return GLib.Markup.escape_text (all_tags.replace (content, -1, 0, ""));
|
||||
}
|
||||
|
||||
public static string simplify (string content) {
|
||||
var divided = content
|
||||
public static string escape_pango_entities (string str) {
|
||||
return str
|
||||
.replace (" ", " ")
|
||||
.replace ("'", "'")
|
||||
.replace ("& ", "&");
|
||||
}
|
||||
|
||||
public static string restore_entities (string str) {
|
||||
return str
|
||||
.replace ("&", "&")
|
||||
.replace ("<", "<")
|
||||
.replace (">", ">")
|
||||
.replace ("'", "'")
|
||||
.replace (""", "\"");
|
||||
}
|
||||
|
||||
public static string simplify (string str) {
|
||||
var divided = str
|
||||
.replace("<br>", "\n")
|
||||
.replace("</br>", "")
|
||||
.replace("<br />", "\n")
|
||||
.replace("<p>", "")
|
||||
.replace("</p>", "\n\n");
|
||||
|
||||
var html_params = new Regex("(class|target|rel)=\"(.|\n)*?\"", RegexCompileFlags.CASELESS);
|
||||
var simplified = html_params.replace(divided, -1, 0, "");
|
||||
var html_params = new Regex ("(class|target|rel)=\"(.|\n)*?\"", RegexCompileFlags.CASELESS);
|
||||
var simplified = html_params.replace (divided, -1, 0, "");
|
||||
|
||||
while (simplified.has_suffix ("\n"))
|
||||
simplified = simplified.slice (0, simplified.last_index_of ("\n"));
|
||||
|
||||
return simplified;
|
||||
return escape_pango_entities (simplified);
|
||||
}
|
||||
|
||||
public static string uri_encode (string content) {
|
||||
var to_escape = ";&+";
|
||||
return Soup.URI.encode (content, to_escape);
|
||||
public static string uri_encode (string str) {
|
||||
var restored = restore_entities (str);
|
||||
return Soup.URI.encode (restored, ";&+");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
using Soup;
|
||||
using GLib;
|
||||
using Gdk;
|
||||
using Json;
|
||||
|
||||
private struct CachedImage {
|
||||
|
||||
public string uri;
|
||||
public int size;
|
||||
|
||||
public CachedImage (string _uri, int _size) {
|
||||
uri = _uri;
|
||||
size = _size;
|
||||
}
|
||||
|
||||
public static uint hash(CachedImage? c) {
|
||||
assert (c != null);
|
||||
assert (c.uri != null);
|
||||
return GLib.int64_hash (c.size) ^ c.uri.hash ();
|
||||
}
|
||||
|
||||
public static bool equal (CachedImage? a, CachedImage? b) {
|
||||
if (a == null || b == null)
|
||||
return false;
|
||||
return a.size == b.size && a.uri == b.uri;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public delegate void PixbufCallback (Gdk.Pixbuf pb);
|
||||
|
||||
public class Tootle.ImageCache : GLib.Object {
|
||||
|
||||
private GLib.HashTable<CachedImage?, Soup.Message> in_progress;
|
||||
private GLib.HashTable<CachedImage?, Gdk.Pixbuf> pixbufs;
|
||||
private uint total_size_est;
|
||||
private uint size_limit;
|
||||
private string cache_path;
|
||||
|
||||
construct {
|
||||
pixbufs = new GLib.HashTable<CachedImage?, Gdk.Pixbuf> (CachedImage.hash, CachedImage.equal);
|
||||
in_progress = new GLib.HashTable<CachedImage?, Soup.Message> (CachedImage.hash, CachedImage.equal);
|
||||
total_size_est = 0;
|
||||
cache_path = "%s/%s".printf (GLib.Environment.get_user_cache_dir (), app.application_id);
|
||||
|
||||
settings.changed.connect (on_settings_changed);
|
||||
on_settings_changed ();
|
||||
}
|
||||
|
||||
public ImageCache() {}
|
||||
|
||||
private void on_settings_changed () {
|
||||
// assume 32BPP (divide bytes by 4 to get # pixels) and raw, overhead-free storage
|
||||
// cache_size setting is number of megabytes
|
||||
size_limit = (1024 * 1024 * settings.cache_size) / 4;
|
||||
if (settings.cache)
|
||||
enforce_size_limit ();
|
||||
else
|
||||
remove_all ();
|
||||
}
|
||||
|
||||
public void remove_all () {
|
||||
debug("Image cache cleared");
|
||||
pixbufs.remove_all ();
|
||||
total_size_est = 0;
|
||||
}
|
||||
|
||||
public void remove_one (string uri, int size) {
|
||||
CachedImage ci = CachedImage (uri, size);
|
||||
bool removed = pixbufs.remove(ci);
|
||||
if (removed) {
|
||||
assert (total_size_est >= size * size);
|
||||
total_size_est -= size * size;
|
||||
debug("Cache usage: %zd", total_size_est);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: fix me
|
||||
// remove least used image
|
||||
private void remove_least_used () {
|
||||
var keys = pixbufs.get_keys();
|
||||
if (keys.first() != null) {
|
||||
var ci = keys.first().data;
|
||||
remove_one(ci.uri, ci.size);
|
||||
}
|
||||
}
|
||||
|
||||
private void enforce_size_limit () {
|
||||
debug("Updating size limit (%zd/%zd)", total_size_est, size_limit);
|
||||
while (total_size_est > size_limit && pixbufs.size() > 0)
|
||||
remove_least_used ();
|
||||
|
||||
assert (total_size_est <= size_limit);
|
||||
}
|
||||
|
||||
private void store_pixbuf (CachedImage ci, Gdk.Pixbuf pixbuf) {
|
||||
assert (!pixbufs.contains (ci));
|
||||
pixbufs.insert (ci, pixbuf);
|
||||
in_progress.remove (ci);
|
||||
total_size_est += ci.size * ci.size;
|
||||
enforce_size_limit ();
|
||||
}
|
||||
|
||||
public async void get_image (string uri, int size, owned PixbufCallback? cb = null) {
|
||||
CachedImage ci = CachedImage (uri, size);
|
||||
Gdk.Pixbuf? pb = pixbufs.get(ci);
|
||||
if (pb != null) {
|
||||
cb (pb);
|
||||
return;
|
||||
}
|
||||
|
||||
Soup.Message? msg = in_progress.get(ci);
|
||||
if (msg == null) {
|
||||
msg = new Soup.Message("GET", uri);
|
||||
ulong id = 0;
|
||||
id = msg.finished.connect(() => {
|
||||
debug("Caching %s@%d", uri, size);
|
||||
var data = msg.response_body.data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
||||
store_pixbuf(ci, pixbuf);
|
||||
cb(pixbuf);
|
||||
msg.disconnect(id);
|
||||
});
|
||||
in_progress[ci] = msg;
|
||||
network.queue (msg);
|
||||
} else {
|
||||
ulong id = 0;
|
||||
id = msg.finished.connect(() => {
|
||||
cb(pixbufs[ci]);
|
||||
msg.disconnect(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void load_avatar (string uri, Granite.Widgets.Avatar avatar, int size) {
|
||||
get_image.begin (uri, size, (pixbuf) => avatar.pixbuf = pixbuf.scale_simple (size, size, Gdk.InterpType.BILINEAR));
|
||||
}
|
||||
|
||||
public void load_image (string uri, Gtk.Image image) {
|
||||
load_scaled_image (uri, image, -1);
|
||||
}
|
||||
|
||||
public void load_scaled_image (string uri, Gtk.Image image, int size) {
|
||||
get_image.begin (uri, size, image.set_from_pixbuf);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,58 +1,89 @@
|
|||
using GLib;
|
||||
using Gee;
|
||||
|
||||
public class Tootle.InstanceAccount : Object {
|
||||
public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
||||
|
||||
public string username {get; set;}
|
||||
public string instance {get; set;}
|
||||
public string client_id {get; set;}
|
||||
public string client_secret {get; set;}
|
||||
public string token {get; set;}
|
||||
public string instance { get; set; }
|
||||
public string client_id { get; set; }
|
||||
public string client_secret { get; set; }
|
||||
public string token { get; set; }
|
||||
|
||||
public int64 last_seen_notification {get; set; default = 0;}
|
||||
public bool has_unread_notifications {get; set; default = false;}
|
||||
public ArrayList<API.Notification> cached_notifications {get; set;}
|
||||
public int64 last_seen_notification { get; set; default = 0; }
|
||||
public bool has_unread_notifications { get; set; default = false; }
|
||||
public ArrayList<API.Notification> cached_notifications { get; set; default = new ArrayList<API.Notification> (); }
|
||||
|
||||
private Notificator? notificator;
|
||||
protected string? stream;
|
||||
|
||||
public InstanceAccount () {
|
||||
cached_notifications = new ArrayList<API.Notification> ();
|
||||
public string handle {
|
||||
owned get { return @"@$username@$short_instance"; }
|
||||
}
|
||||
|
||||
public string get_pretty_instance () {
|
||||
public string short_instance {
|
||||
owned get {
|
||||
return instance
|
||||
.replace ("https://", "")
|
||||
.replace ("/","");
|
||||
}
|
||||
}
|
||||
|
||||
public void start_notificator () {
|
||||
if (notificator != null)
|
||||
notificator.close ();
|
||||
public InstanceAccount (Json.Object obj) {
|
||||
Object (
|
||||
username: obj.get_string_member ("username"),
|
||||
instance: obj.get_string_member ("instance"),
|
||||
client_id: obj.get_string_member ("id"),
|
||||
client_secret: obj.get_string_member ("secret"),
|
||||
token: obj.get_string_member ("access_token"),
|
||||
last_seen_notification: obj.get_int_member ("last_seen_notification"),
|
||||
has_unread_notifications: obj.get_boolean_member ("has_unread_notifications")
|
||||
);
|
||||
|
||||
notificator = new Notificator (get_stream ());
|
||||
notificator.status_added.connect (status_added);
|
||||
notificator.status_removed.connect (status_removed);
|
||||
notificator.notification.connect (notification);
|
||||
notificator.start ();
|
||||
var cached = obj.get_object_member ("cached_profile");
|
||||
var account = new API.Account (cached);
|
||||
patch (account);
|
||||
|
||||
var notifications = obj.get_array_member ("cached_notifications");
|
||||
notifications.foreach_element ((arr, i, node) => {
|
||||
var notification = new API.Notification (node.get_object ());
|
||||
cached_notifications.add (notification);
|
||||
});
|
||||
}
|
||||
~InstanceAccount () {
|
||||
unsubscribe ();
|
||||
}
|
||||
|
||||
public InstanceAccount.empty (string instance){
|
||||
Object (id: 0, instance: instance);
|
||||
}
|
||||
|
||||
public InstanceAccount.from_account (API.Account account) {
|
||||
Object (id: account.id);
|
||||
patch (account);
|
||||
}
|
||||
|
||||
public InstanceAccount patch (API.Account account) {
|
||||
Utils.merge (this, account);
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool is_current () {
|
||||
return accounts.formal.token == token;
|
||||
return accounts.active.token == token;
|
||||
}
|
||||
|
||||
public Soup.Message get_stream () {
|
||||
var url = "%s/api/v1/streaming/?stream=user&access_token=%s".printf (instance, token);
|
||||
return new Soup.Message ("GET", url);
|
||||
public string get_stream_url () {
|
||||
return @"$instance/api/v1/streaming/?stream=user&access_token=$token";
|
||||
}
|
||||
|
||||
public void close_notificator () {
|
||||
if (notificator != null)
|
||||
notificator.close ();
|
||||
public void subscribe () {
|
||||
streams.subscribe (get_stream_url (), this, out stream);
|
||||
}
|
||||
|
||||
public Json.Node serialize () {
|
||||
public void unsubscribe () {
|
||||
streams.unsubscribe (stream, this);
|
||||
}
|
||||
|
||||
public override Json.Node? serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
|
||||
builder.set_member_name ("hash");
|
||||
builder.add_string_value ("test");
|
||||
builder.set_member_name ("username");
|
||||
|
@ -63,13 +94,17 @@ public class Tootle.InstanceAccount : Object {
|
|||
builder.add_string_value (client_id);
|
||||
builder.set_member_name ("secret");
|
||||
builder.add_string_value (client_secret);
|
||||
builder.set_member_name ("token");
|
||||
builder.set_member_name ("access_token");
|
||||
builder.add_string_value (token);
|
||||
builder.set_member_name ("last_seen_notification");
|
||||
builder.add_int_value (last_seen_notification);
|
||||
builder.set_member_name ("has_unread_notifications");
|
||||
builder.add_boolean_value (has_unread_notifications);
|
||||
|
||||
var cached_profile = base.serialize ();
|
||||
builder.set_member_name ("cached_profile");
|
||||
builder.add_value (cached_profile);
|
||||
|
||||
builder.set_member_name ("cached_notifications");
|
||||
builder.begin_array ();
|
||||
cached_notifications.@foreach (notification => {
|
||||
|
@ -84,31 +119,12 @@ public class Tootle.InstanceAccount : Object {
|
|||
return builder.get_root ();
|
||||
}
|
||||
|
||||
public static InstanceAccount parse (Json.Object obj) {
|
||||
var acc = new InstanceAccount ();
|
||||
acc.username = obj.get_string_member ("username");
|
||||
acc.instance = obj.get_string_member ("instance");
|
||||
acc.client_id = obj.get_string_member ("id");
|
||||
acc.client_secret = obj.get_string_member ("secret");
|
||||
acc.token = obj.get_string_member ("token");
|
||||
acc.last_seen_notification = obj.get_int_member ("last_seen_notification");
|
||||
acc.has_unread_notifications = obj.get_boolean_member ("has_unread_notifications");
|
||||
|
||||
var notifications = obj.get_array_member ("cached_notifications");
|
||||
notifications.foreach_element ((arr, i, node) => {
|
||||
var notification = API.Notification.parse (node.get_object ());
|
||||
acc.cached_notifications.add (notification);
|
||||
});
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
public void notification (API.Notification obj) {
|
||||
var title = Html.remove_tags (obj.type.get_desc (obj.account));
|
||||
public override void on_notification (API.Notification obj) {
|
||||
var title = Html.remove_tags (obj.kind.get_desc (obj.account));
|
||||
var notification = new GLib.Notification (title);
|
||||
if (obj.status != null) {
|
||||
var body = "";
|
||||
body += get_pretty_instance ();
|
||||
body += short_instance;
|
||||
body += "\n";
|
||||
body += Html.remove_tags (obj.status.content);
|
||||
notification.set_body (body);
|
||||
|
@ -118,34 +134,34 @@ public class Tootle.InstanceAccount : Object {
|
|||
app.send_notification (app.application_id + ":" + obj.id.to_string (), notification);
|
||||
|
||||
if (is_current ())
|
||||
network.notification (obj);
|
||||
streams.notification (obj);
|
||||
|
||||
if (obj.type == API.NotificationType.WATCHLIST) {
|
||||
if (obj.kind == API.NotificationType.WATCHLIST) {
|
||||
cached_notifications.add (obj);
|
||||
accounts.save ();
|
||||
}
|
||||
}
|
||||
|
||||
private void status_removed (int64 id) {
|
||||
public override void on_status_removed (int64 id) {
|
||||
if (is_current ())
|
||||
network.status_removed (id);
|
||||
streams.status_removed (id);
|
||||
}
|
||||
|
||||
private void status_added (API.Status status) {
|
||||
public override void on_status_added (API.Status status) {
|
||||
if (!is_current ())
|
||||
return;
|
||||
|
||||
watchlist.users.@foreach (item => {
|
||||
var acct = status.account.acct;
|
||||
if (item == acct || item == "@" + acct) {
|
||||
var obj = new API.Notification (-1);
|
||||
obj.type = API.NotificationType.WATCHLIST;
|
||||
obj.account = status.account;
|
||||
obj.status = status;
|
||||
notification (obj);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// watchlist.users.@foreach (item => {
|
||||
// var acct = status.account.acct;
|
||||
// if (item == acct || item == "@" + acct) {
|
||||
// var obj = new API.Notification (-1);
|
||||
// obj.kind = API.NotificationType.WATCHLIST;
|
||||
// obj.account = status.account;
|
||||
// obj.status = status;
|
||||
// on_notification (obj);
|
||||
// }
|
||||
// return true;
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
208
src/Network.vala
|
@ -1,208 +0,0 @@
|
|||
using Soup;
|
||||
using GLib;
|
||||
using Gdk;
|
||||
using Json;
|
||||
|
||||
public class Tootle.Network : GLib.Object {
|
||||
|
||||
public const string INJECT_TOKEN = "X-HeyMate-PlsInjectToken4MeThx";
|
||||
|
||||
public signal void started ();
|
||||
public signal void finished ();
|
||||
public signal void notification (API.Notification notification);
|
||||
public signal void status_removed (int64 id);
|
||||
|
||||
public delegate void ErrorCallback (int32 code, string reason);
|
||||
public delegate void SuccessCallback (Session session, Message msg) throws GLib.Error;
|
||||
|
||||
private int requests_processing = 0;
|
||||
private Soup.Session session;
|
||||
|
||||
construct {
|
||||
session = new Soup.Session ();
|
||||
session.ssl_strict = true;
|
||||
session.ssl_use_system_ca_file = true;
|
||||
session.timeout = 15;
|
||||
session.max_conns = 20;
|
||||
session.request_unqueued.connect (msg => {
|
||||
requests_processing--;
|
||||
if (requests_processing <= 0)
|
||||
finished ();
|
||||
});
|
||||
|
||||
// Soup.Logger logger = new Soup.Logger (Soup.LoggerLogLevel.BODY, -1);
|
||||
// session.add_feature (logger);
|
||||
}
|
||||
|
||||
public Network () {}
|
||||
|
||||
public async WebsocketConnection stream (Soup.Message msg) throws GLib.Error {
|
||||
return yield session.websocket_connect_async (msg, null, null, null);
|
||||
}
|
||||
|
||||
public void cancel_request (Soup.Message? msg) {
|
||||
if (msg == null)
|
||||
return;
|
||||
switch (msg.status_code) {
|
||||
case Soup.Status.CANCELLED:
|
||||
case Soup.Status.OK:
|
||||
return;
|
||||
}
|
||||
session.cancel_message (msg, Soup.Status.CANCELLED);
|
||||
}
|
||||
|
||||
public void inject (Soup.Message msg, string header) {
|
||||
msg.request_headers.append (header, "VeryPls");
|
||||
}
|
||||
|
||||
private void inject_headers (ref Soup.Message msg) {
|
||||
var headers = msg.request_headers;
|
||||
var formal = accounts.formal;
|
||||
if (headers.get_one (INJECT_TOKEN) != null && formal != null) {
|
||||
headers.remove (INJECT_TOKEN);
|
||||
headers.append ("Authorization", "Bearer " + formal.token);
|
||||
}
|
||||
}
|
||||
|
||||
public void queue (owned Soup.Message message, owned SuccessCallback? cb = null, owned ErrorCallback? errcb = null) {
|
||||
requests_processing++;
|
||||
started ();
|
||||
|
||||
inject_headers (ref message);
|
||||
|
||||
session.queue_message (message, (sess, msg) => {
|
||||
var status = msg.status_code;
|
||||
if (status != Soup.Status.CANCELLED) {
|
||||
if (status == Soup.Status.OK) {
|
||||
if (cb != null) {
|
||||
try {
|
||||
cb (session, msg);
|
||||
}
|
||||
catch (Error e) {
|
||||
warning ("Caught exception on network request:");
|
||||
warning (e.message);
|
||||
if (errcb != null)
|
||||
errcb (Soup.Status.NONE, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (errcb != null)
|
||||
errcb ((int32)status, get_error_reason ((int32)status));
|
||||
}
|
||||
}
|
||||
// msg.request_body.free ();
|
||||
// msg.response_body.free ();
|
||||
// msg.request_headers.free ();
|
||||
// msg.response_headers.free ();
|
||||
});
|
||||
}
|
||||
|
||||
public string get_error_reason (int32 status) {
|
||||
return "Error " + status.to_string () + ": " + Soup.Status.get_phrase (status);
|
||||
}
|
||||
|
||||
public void on_error (int32 code, string message) {
|
||||
warning (message);
|
||||
app.toast (message);
|
||||
}
|
||||
|
||||
public void on_show_error (int32 code, string message) {
|
||||
warning (message);
|
||||
app.error (_("Network Error"), message);
|
||||
}
|
||||
|
||||
public Json.Object parse (Soup.Message msg) throws GLib.Error {
|
||||
// debug ("Status Code: %u", msg.status_code);
|
||||
// debug ("Message length: %lld", msg.response_body.length);
|
||||
// debug ("Object: %s", (string) msg.response_body.data);
|
||||
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||
return parser.get_root ().get_object ();
|
||||
}
|
||||
|
||||
public Json.Array parse_array (Soup.Message msg) throws GLib.Error {
|
||||
// debug ("Status Code: %u", msg.status_code);
|
||||
// debug ("Message length: %lld", msg.response_body.length);
|
||||
// debug ("Array: %s", (string) msg.response_body.data);
|
||||
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||
return parser.get_root ().get_array ();
|
||||
}
|
||||
|
||||
//TODO: Cache
|
||||
public void load_avatar (string url, Granite.Widgets.Avatar avatar, int size){
|
||||
var message = new Soup.Message("GET", url);
|
||||
network.queue (message, (sess, msg) => {
|
||||
if (msg.status_code != Soup.Status.OK) {
|
||||
avatar.show_default (size);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = msg.response_body.data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
||||
|
||||
avatar.pixbuf = pixbuf.scale_simple (size, size, Gdk.InterpType.BILINEAR);
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: Cache
|
||||
public delegate void PixbufCallback (Gdk.Pixbuf pixbuf);
|
||||
public Soup.Message load_pixbuf (string url, PixbufCallback cb) {
|
||||
var message = new Soup.Message("GET", url);
|
||||
network.queue (message, (sess, msg) => {
|
||||
Gdk.Pixbuf? pixbuf = null;
|
||||
try {
|
||||
var data = msg.response_body.flatten ().data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
pixbuf = new Gdk.Pixbuf.from_stream (stream);
|
||||
}
|
||||
catch (Error e) {
|
||||
warning ("Can't get image: %s".printf (url));
|
||||
warning ("Reason: " + e.message);
|
||||
}
|
||||
finally {
|
||||
if (msg.status_code != Soup.Status.OK)
|
||||
warning ("Invalid response code %s: %s".printf (msg.status_code.to_string (), url));
|
||||
}
|
||||
cb (pixbuf);
|
||||
});
|
||||
return message;
|
||||
}
|
||||
|
||||
//TODO: Cache
|
||||
public void load_image (string url, Gtk.Image image) {
|
||||
var message = new Soup.Message("GET", url);
|
||||
network.queue (message, (sess, msg) => {
|
||||
if (msg.status_code != Soup.Status.OK) {
|
||||
image.set_from_icon_name ("image-missing", Gtk.IconSize.LARGE_TOOLBAR);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = msg.response_body.data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
var pixbuf = new Gdk.Pixbuf.from_stream (stream);
|
||||
image.set_from_pixbuf (pixbuf);
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: Cache
|
||||
public void load_scaled_image (string url, Gtk.Image image, int size) {
|
||||
var message = new Soup.Message("GET", url);
|
||||
network.queue (message, (sess, msg) => {
|
||||
if (msg.status_code != Soup.Status.OK) {
|
||||
image.set_from_icon_name ("image-missing", Gtk.IconSize.LARGE_TOOLBAR);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = msg.response_body.data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
||||
image.set_from_pixbuf (pixbuf);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
using GLib;
|
||||
using Soup;
|
||||
|
||||
public class Tootle.Notificator : GLib.Object {
|
||||
|
||||
private WebsocketConnection? connection;
|
||||
private Soup.Message msg;
|
||||
private bool closing = false;
|
||||
private int timeout = 2;
|
||||
|
||||
public signal void notification (API.Notification notification);
|
||||
public signal void status_added (API.Status status);
|
||||
public signal void status_removed (int64 id);
|
||||
|
||||
public Notificator (Soup.Message _msg){
|
||||
msg = _msg;
|
||||
msg.priority = Soup.MessagePriority.VERY_HIGH;
|
||||
msg.set_flags (Soup.MessageFlags.IGNORE_CONNECTION_LIMITS);
|
||||
}
|
||||
|
||||
public string get_url () {
|
||||
return msg.get_uri ().to_string (false);
|
||||
}
|
||||
|
||||
public string get_name () {
|
||||
var name = msg.get_uri ().to_string (true);
|
||||
if ("&access_token" in name) {
|
||||
var pos = name.last_index_of ("&access_token");
|
||||
name = name.slice (0, pos);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public async void start () {
|
||||
if (connection != null)
|
||||
return;
|
||||
|
||||
try {
|
||||
info ("Starting: %s", get_name ());
|
||||
connection = yield network.stream (msg);
|
||||
connection.error.connect (on_error);
|
||||
connection.message.connect (on_message);
|
||||
connection.closed.connect (on_closed);
|
||||
timeout = 2;
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning (e.message);
|
||||
on_closed ();
|
||||
}
|
||||
}
|
||||
|
||||
public void close () {
|
||||
if (connection == null)
|
||||
return;
|
||||
|
||||
info ("Closing: %s", get_name ());
|
||||
closing = true;
|
||||
connection.close (0, null);
|
||||
}
|
||||
|
||||
private bool reconnect () {
|
||||
start ();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void on_closed () {
|
||||
if (closing)
|
||||
return;
|
||||
|
||||
warning ("Aborted: %s. Reconnecting in %i seconds.", get_name (), timeout);
|
||||
GLib.Timeout.add_seconds (timeout, reconnect);
|
||||
timeout = int.min (timeout*2, 60);
|
||||
}
|
||||
|
||||
private void on_error (Error e) {
|
||||
if (!closing)
|
||||
warning ("Error in %s: %s", get_name (), e.message);
|
||||
}
|
||||
|
||||
private void on_message (int i, Bytes bytes) {
|
||||
var msg = (string) bytes.get_data ();
|
||||
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data (msg, -1);
|
||||
var root = parser.get_root ().get_object ();
|
||||
|
||||
var type = root.get_string_member ("event");
|
||||
switch (type) {
|
||||
case "update":
|
||||
if (!settings.live_updates)
|
||||
return;
|
||||
|
||||
var status = API.Status.parse (sanitize (root));
|
||||
status_added (status);
|
||||
break;
|
||||
case "delete":
|
||||
if (!settings.live_updates)
|
||||
return;
|
||||
|
||||
var id = int64.parse (root.get_string_member("payload"));
|
||||
status_removed (id);
|
||||
break;
|
||||
case "notification":
|
||||
var notif = API.Notification.parse (sanitize (root));
|
||||
notification (notif);
|
||||
break;
|
||||
default:
|
||||
warning ("Unknown push event: %s", type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private Json.Object sanitize (Json.Object root) {
|
||||
var payload = root.get_string_member ("payload");
|
||||
var sanitized = Soup.URI.decode (payload);
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data (sanitized, -1);
|
||||
return parser.get_root ().get_object ();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
using Soup;
|
||||
using Gee;
|
||||
|
||||
public class Tootle.Request : Soup.Message {
|
||||
|
||||
public string url { construct set; get; }
|
||||
private Network.SuccessCallback? cb;
|
||||
private Network.ErrorCallback? error_cb;
|
||||
private HashMap<string, string>? pars;
|
||||
private weak InstanceAccount? account;
|
||||
private bool needs_token = false;
|
||||
|
||||
public Request.GET (string url) {
|
||||
Object (method: "GET", url: url);
|
||||
}
|
||||
public Request.POST (string url) {
|
||||
Object (method: "POST", url: url);
|
||||
}
|
||||
public Request.DELETE (string url) {
|
||||
Object (method: "DELETE", url: url);
|
||||
}
|
||||
|
||||
public Request then (owned Network.SuccessCallback cb) {
|
||||
this.cb = (owned) cb;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request then_parse_array (owned Network.NodeCallback _cb) {
|
||||
this.cb = (sess, msg) => {
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||
parser.get_root ().get_array ().foreach_element ((array, i, node) => _cb (node, msg));
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request then_parse_obj (owned Network.ObjectCallback _cb) {
|
||||
this.cb = (sess, msg) => {
|
||||
_cb (network.parse (msg));
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request on_error (owned Network.ErrorCallback cb) {
|
||||
this.error_cb = (owned) cb;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request with_account (InstanceAccount? account = null) {
|
||||
this.needs_token = true;
|
||||
if (account != null)
|
||||
this.account = account;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request with_param (string name, string val) {
|
||||
if (pars == null)
|
||||
pars = new HashMap<string, string> ();
|
||||
pars[name] = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Should be used for requests with default priority
|
||||
public Request queue () {
|
||||
var parameters = "";
|
||||
if (pars != null) {
|
||||
parameters = "?";
|
||||
var parameters_counter = 0;
|
||||
pars.@foreach (entry => {
|
||||
parameters_counter++;
|
||||
var key = (string) entry.key;
|
||||
var val = (string) entry.value;
|
||||
parameters += @"$key=$val";
|
||||
|
||||
if (parameters_counter < pars.size)
|
||||
parameters += "&";
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if (needs_token) {
|
||||
if (account == null) {
|
||||
warning (@"No account found for: $method: $url$parameters");
|
||||
return this;
|
||||
}
|
||||
|
||||
request_headers.append ("Authorization", @"Bearer $(account.token)");
|
||||
}
|
||||
|
||||
if (!("://" in url)) {
|
||||
url = account.instance + url;
|
||||
}
|
||||
|
||||
this.uri = new URI (url + "" + parameters);
|
||||
|
||||
url = uri.to_string (false);
|
||||
info (@"$method: $url");
|
||||
|
||||
network.queue (this, (owned) cb, (owned) error_cb);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Should be used for real-time user interactions (liking, removing and browsing posts)
|
||||
public Request exec () {
|
||||
this.priority = MessagePriority.HIGH;
|
||||
return this.queue ();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
using Gee;
|
||||
|
||||
public class Tootle.Accounts : GLib.Object {
|
||||
|
||||
private string dir_path;
|
||||
private string file_path;
|
||||
|
||||
public ArrayList<InstanceAccount> saved { get; set; default = new ArrayList<InstanceAccount> (); }
|
||||
public InstanceAccount? active { get; set; }
|
||||
|
||||
construct {
|
||||
dir_path = @"$(GLib.Environment.get_user_config_dir ())/$(app.application_id)";
|
||||
file_path = @"$dir_path/accounts.json";
|
||||
}
|
||||
|
||||
public void switch_account (int id) {
|
||||
var acc = saved.@get (id);
|
||||
info (@"Switching to account: $(acc.handle)...");
|
||||
new Request.GET ("/api/v1/accounts/verify_credentials")
|
||||
.with_account (acc)
|
||||
.then ((sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
var profile = new API.Account (root);
|
||||
acc.patch (profile);
|
||||
info ("OK: Token is valid");
|
||||
active = acc;
|
||||
settings.current_account = id;
|
||||
})
|
||||
.on_error ((code, reason) => {
|
||||
warning ("Token invalid!");
|
||||
network.on_show_error (code, _("This instance has invalidated this session. Please sign in again.\n\n%s").printf (reason));
|
||||
})
|
||||
.exec ();
|
||||
}
|
||||
|
||||
public void add (InstanceAccount account) {
|
||||
info (@"Adding new account: $(account.handle)");
|
||||
saved.add (account);
|
||||
save ();
|
||||
switch_account (saved.size - 1);
|
||||
account.subscribe ();
|
||||
}
|
||||
|
||||
public void remove (InstanceAccount account) {
|
||||
account.unsubscribe ();
|
||||
saved.remove (account);
|
||||
saved.notify_property ("size");
|
||||
|
||||
if (saved.size < 1)
|
||||
active = null;
|
||||
else {
|
||||
var id = settings.current_account - 1;
|
||||
if (id > saved.size - 1)
|
||||
id = saved.size - 1;
|
||||
else if (id < saved.size - 1)
|
||||
id = 0;
|
||||
switch_account (id);
|
||||
}
|
||||
save ();
|
||||
|
||||
if (is_empty ())
|
||||
window.open_view (new Views.NewAccount (false));
|
||||
}
|
||||
|
||||
public bool is_empty () {
|
||||
return saved.size == 0;
|
||||
}
|
||||
|
||||
public void init () {
|
||||
save (false);
|
||||
load ();
|
||||
|
||||
if (saved.size < 1)
|
||||
window.open_view (new Views.NewAccount (false));
|
||||
else
|
||||
switch_account (settings.current_account);
|
||||
}
|
||||
|
||||
public void save (bool overwrite = true) {
|
||||
try {
|
||||
var dir = File.new_for_path (dir_path);
|
||||
if (!dir.query_exists ())
|
||||
dir.make_directory ();
|
||||
|
||||
var file = File.new_for_path (file_path);
|
||||
if (file.query_exists () && !overwrite)
|
||||
return;
|
||||
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_array ();
|
||||
saved.foreach ((acc) => {
|
||||
var node = acc.serialize ();
|
||||
builder.add_value (node);
|
||||
return true;
|
||||
});
|
||||
builder.end_array ();
|
||||
|
||||
var generator = new Json.Generator ();
|
||||
generator.set_root (builder.get_root ());
|
||||
var data = generator.to_data (null);
|
||||
|
||||
if (file.query_exists ())
|
||||
file.@delete ();
|
||||
|
||||
FileOutputStream stream = file.create (FileCreateFlags.PRIVATE);
|
||||
stream.write (data.data);
|
||||
info ("Saved accounts");
|
||||
}
|
||||
catch (Error e){
|
||||
warning (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void load () {
|
||||
try {
|
||||
uint8[] data;
|
||||
string etag;
|
||||
var file = File.new_for_path (file_path);
|
||||
file.load_contents (null, out data, out etag);
|
||||
var contents = (string) data;
|
||||
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data (contents, -1);
|
||||
var array = parser.get_root ().get_array ();
|
||||
|
||||
array.foreach_element ((_arr, _i, node) => {
|
||||
var obj = node.get_object ();
|
||||
var account = new InstanceAccount (obj);
|
||||
if (account != null) {
|
||||
saved.add (account);
|
||||
account.subscribe ();
|
||||
}
|
||||
});
|
||||
info (@"Loaded $(saved.size) accounts");
|
||||
}
|
||||
catch (Error e){
|
||||
warning (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
using Gee;
|
||||
using Gdk;
|
||||
|
||||
public class Tootle.Cache : GLib.Object {
|
||||
|
||||
protected HashTable<string, Item> items { get; set; }
|
||||
protected HashTable<string, Soup.Message> items_in_progress { get; set; }
|
||||
protected uint size {
|
||||
get {
|
||||
return items.size ();
|
||||
}
|
||||
}
|
||||
|
||||
construct {
|
||||
items = new HashTable<string, Item> (GLib.str_hash, GLib.str_equal);
|
||||
items_in_progress = new HashTable<string, Soup.Message> (GLib.str_hash, GLib.str_equal);
|
||||
}
|
||||
|
||||
public delegate void CachedResultCallback (Reference? result);
|
||||
|
||||
public struct Reference {
|
||||
public string key;
|
||||
public weak Pixbuf? data;
|
||||
public bool loading;
|
||||
}
|
||||
|
||||
protected class Item : GLib.Object {
|
||||
public Pixbuf data { get; construct set; }
|
||||
public int64 references { get; construct set; }
|
||||
|
||||
public Item (Pixbuf d, int64 r) {
|
||||
Object (data: d, references: r);
|
||||
}
|
||||
}
|
||||
|
||||
public void unload (Reference? r) {
|
||||
if (r == null)
|
||||
return;
|
||||
|
||||
if (r.data == null)
|
||||
return;
|
||||
|
||||
var item = items[r.key];
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
item.references--;
|
||||
//info (@"DEREF $(r.key) $(item.references)");
|
||||
if (item.references <= 0) {
|
||||
//info ("REMOVE %s", r.key);
|
||||
items.remove (r.key);
|
||||
items_in_progress.remove (r.key);
|
||||
}
|
||||
}
|
||||
|
||||
public void load (string? url, owned CachedResultCallback cb) {
|
||||
if (url == null)
|
||||
return;
|
||||
|
||||
var key = url;
|
||||
if (items.contains (key)) {
|
||||
//info (@"LOAD $key");
|
||||
var item = items.@get (key);
|
||||
item.references++;
|
||||
cb (Reference () {
|
||||
data = item.data,
|
||||
key = key,
|
||||
loading = false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var item = items.@get (key);
|
||||
|
||||
var message = items_in_progress.@get (key);
|
||||
if (message == null) {
|
||||
message = new Soup.Message ("GET", url);
|
||||
ulong id = 0;
|
||||
id = message.finished.connect (() => {
|
||||
Pixbuf? pixbuf = null;
|
||||
|
||||
var data = message.response_body.flatten ().data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
pixbuf = new Pixbuf.from_stream (stream);
|
||||
stream.close ();
|
||||
|
||||
//info (@"< STORE $key");
|
||||
items[key] = new Item (pixbuf, 1);
|
||||
items_in_progress.remove (key);
|
||||
|
||||
cb (Reference () {
|
||||
data = items[key].data,
|
||||
key = key,
|
||||
loading = false
|
||||
});
|
||||
|
||||
message.disconnect (id);
|
||||
});
|
||||
|
||||
network.queue (message, (sess, msg) => {
|
||||
// no one cares
|
||||
},
|
||||
(code, reason) => {
|
||||
cb (null);
|
||||
});
|
||||
|
||||
cb (Reference () {
|
||||
data = null,
|
||||
key = key,
|
||||
loading = true
|
||||
});
|
||||
|
||||
items_in_progress.insert (key, message);
|
||||
}
|
||||
else {
|
||||
//info ("AWAIT: %s", key);
|
||||
ulong id = 0;
|
||||
id = message.finished.connect_after (() => {
|
||||
var it = items.@get (key);
|
||||
cb (Reference () {
|
||||
data = it.data,
|
||||
key = key,
|
||||
loading = false
|
||||
});
|
||||
it.references++;
|
||||
message.disconnect (id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
info ("PURGE");
|
||||
items.remove_all ();
|
||||
items_in_progress.remove_all ();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
public interface Tootle.IAccountListener : GLib.Object {
|
||||
|
||||
protected void connect_account () {
|
||||
accounts.notify["active"].connect (() => on_account_changed (accounts.active));
|
||||
accounts.saved.notify["size"].connect (() => on_accounts_changed (accounts.saved));
|
||||
on_account_changed (accounts.active);
|
||||
}
|
||||
|
||||
public virtual void on_account_changed (InstanceAccount? account) {}
|
||||
public virtual void on_accounts_changed (Gee.ArrayList<InstanceAccount> accounts) {}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
public interface Tootle.IStreamListener : GLib.Object {
|
||||
|
||||
public virtual void on_status_removed (int64 id) {}
|
||||
public virtual void on_status_added (API.Status s) {}
|
||||
public virtual void on_notification (API.Notification n) {}
|
||||
|
||||
public virtual bool accepts (ref string event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using Soup;
|
||||
using GLib;
|
||||
using Gdk;
|
||||
using Json;
|
||||
|
||||
public class Tootle.Network : GLib.Object {
|
||||
|
||||
public signal void started ();
|
||||
public signal void finished ();
|
||||
|
||||
public delegate void ErrorCallback (int32 code, string reason);
|
||||
public delegate void SuccessCallback (Session session, Message msg) throws Error;
|
||||
public delegate void NodeCallback (Json.Node node, Message msg) throws Error;
|
||||
public delegate void ObjectCallback (Json.Object node) throws Error;
|
||||
|
||||
private int requests_processing = 0;
|
||||
public Soup.Session session;
|
||||
|
||||
construct {
|
||||
session = new Soup.Session ();
|
||||
session.ssl_strict = true;
|
||||
session.ssl_use_system_ca_file = true;
|
||||
session.timeout = 15;
|
||||
session.max_conns = 30;
|
||||
session.request_unqueued.connect (msg => {
|
||||
requests_processing--;
|
||||
if (requests_processing <= 0)
|
||||
finished ();
|
||||
});
|
||||
}
|
||||
|
||||
// public void cancel_request (Soup.Message? msg) {
|
||||
// if (msg == null)
|
||||
// return;
|
||||
|
||||
// switch (msg.status_code) {
|
||||
// case Soup.Status.CANCELLED:
|
||||
// case Soup.Status.OK:
|
||||
// return;
|
||||
// }
|
||||
// session.cancel_message (msg, Soup.Status.CANCELLED);
|
||||
// }
|
||||
|
||||
public void queue (owned Soup.Message message, owned SuccessCallback? cb, owned ErrorCallback? errcb = null) {
|
||||
requests_processing++;
|
||||
started ();
|
||||
|
||||
session.queue_message (message, (sess, msg) => {
|
||||
var status = msg.status_code;
|
||||
if (status == Soup.Status.OK) {
|
||||
try {
|
||||
cb (session, msg);
|
||||
}
|
||||
catch (Error e) {
|
||||
warning ("Exception on network request: %s", e.message);
|
||||
if (errcb != null)
|
||||
errcb (Soup.Status.NONE, e.message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (errcb != null)
|
||||
errcb ((int32)status, describe_error ((int32)status));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string describe_error (int32 code) {
|
||||
var reason = Soup.Status.get_phrase (code);
|
||||
return @"$code: $reason";
|
||||
}
|
||||
|
||||
public void on_error (int32 code, string message) {
|
||||
warning (message);
|
||||
app.toast (message);
|
||||
}
|
||||
|
||||
public void on_show_error (int32 code, string message) {
|
||||
warning (message);
|
||||
app.error (_("Network Error"), message);
|
||||
}
|
||||
|
||||
public Json.Object parse (Soup.Message msg) throws Error {
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||
return parser.get_root ().get_object ();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using GLib;
|
||||
|
||||
public class Tootle.Settings : GLib.Settings {
|
||||
|
||||
public int current_account { get; set; }
|
||||
public bool notifications { get; set; }
|
||||
public bool always_online { get; set; }
|
||||
public int char_limit { get; set; }
|
||||
public bool live_updates { get; set; }
|
||||
public bool live_updates_public { get; set; }
|
||||
public bool dark_theme { get; set; }
|
||||
|
||||
public string watched_users { get; set; }
|
||||
public string watched_hashtags { get; set; }
|
||||
|
||||
public int window_x { get; set; }
|
||||
public int window_y { get; set; }
|
||||
public int window_w { get; set; }
|
||||
public int window_h { get; set; }
|
||||
|
||||
public Settings () {
|
||||
Object (schema_id: Build.DOMAIN);
|
||||
init ("current-account");
|
||||
init ("notifications");
|
||||
init ("always-online");
|
||||
init ("char-limit");
|
||||
init ("live-updates");
|
||||
init ("live-updates-public");
|
||||
init ("dark-theme");
|
||||
|
||||
init ("watched-users");
|
||||
init ("watched-hashtags");
|
||||
|
||||
init ("window-x");
|
||||
init ("window-y");
|
||||
init ("window-w");
|
||||
init ("window-h");
|
||||
}
|
||||
|
||||
void init (string key) {
|
||||
bind (key, this, key, SettingsBindFlags.DEFAULT);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
using GLib;
|
||||
using Soup;
|
||||
using Gee;
|
||||
|
||||
public class Tootle.Streams : Object {
|
||||
|
||||
public signal void notification (API.Notification n);
|
||||
public signal void status_removed (int64 id);
|
||||
|
||||
protected HashTable<string, Connection> connections {
|
||||
get;
|
||||
set;
|
||||
default = new HashTable<string, Connection> (GLib.str_hash, GLib.str_equal);
|
||||
}
|
||||
|
||||
protected class Connection : Object {
|
||||
public ArrayList<IStreamListener> subscribers;
|
||||
protected WebsocketConnection socket;
|
||||
protected Message msg;
|
||||
|
||||
protected bool closing = false;
|
||||
protected int timeout = 2;
|
||||
|
||||
public string name {
|
||||
owned get {
|
||||
var url = msg.get_uri ().to_string (false);
|
||||
return url.slice (0, url.last_index_of ("&access_token"));
|
||||
}
|
||||
}
|
||||
|
||||
public Connection (string url) {
|
||||
this.subscribers = new ArrayList<IStreamListener> ();
|
||||
this.msg = new Message ("GET", url);
|
||||
}
|
||||
|
||||
public bool start () {
|
||||
//info (@"Opening stream: $name");
|
||||
network.session.websocket_connect_async.begin (msg, null, null, null, (obj, res) => {
|
||||
socket = network.session.websocket_connect_async.end (res);
|
||||
socket.error.connect (on_error);
|
||||
socket.closed.connect (on_closed);
|
||||
socket.message.connect (on_message);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
public void add (IStreamListener s) {
|
||||
info ("%s > %s", get_subscriber_name (s), name);
|
||||
subscribers.add (s);
|
||||
}
|
||||
|
||||
public void remove (IStreamListener s) {
|
||||
if (subscribers.contains (s)) {
|
||||
info ("%s X %s", get_subscriber_name (s), name);
|
||||
subscribers.remove (s);
|
||||
}
|
||||
|
||||
if (subscribers.size <= 0) {
|
||||
info (@"Closing: $name");
|
||||
closing = true;
|
||||
socket.close (0, null);
|
||||
}
|
||||
}
|
||||
|
||||
void on_error (Error e) {
|
||||
if (!closing)
|
||||
warning (@"Error in $name: $(e.message)");
|
||||
}
|
||||
|
||||
void on_closed () {
|
||||
if (!closing) {
|
||||
warning (@"CLOSED: $name. Reconnecting in $timeout seconds.");
|
||||
GLib.Timeout.add_seconds (timeout, start);
|
||||
timeout = int.min (timeout*2, 30);
|
||||
}
|
||||
}
|
||||
|
||||
void on_message (int i, Bytes bytes) {
|
||||
try {
|
||||
emit (bytes, this);
|
||||
}
|
||||
catch (Error e) {
|
||||
warning (@"Couldn't handle websocket event. Reason: $(e.message)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribe (string? url, IStreamListener s, out string cookie) {
|
||||
if (url == null)
|
||||
return;
|
||||
|
||||
if (connections.contains (url)) {
|
||||
connections[url].add (s);
|
||||
}
|
||||
else {
|
||||
var con = new Connection (url);
|
||||
connections[url] = con;
|
||||
con.add (s);
|
||||
con.start ();
|
||||
}
|
||||
cookie = url;
|
||||
}
|
||||
|
||||
public void unsubscribe (string? cookie, IStreamListener s) {
|
||||
var url = cookie;
|
||||
if (url == null)
|
||||
return;
|
||||
|
||||
if (connections.contains (url))
|
||||
connections.@get (url).remove (s);
|
||||
}
|
||||
|
||||
static string get_subscriber_name (Object s) {
|
||||
return s.get_type ().name ();
|
||||
}
|
||||
|
||||
static void decode (Bytes bytes, out string event, out Json.Object root) throws Error {
|
||||
var msg = (string) bytes.get_data ();
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data (msg, -1);
|
||||
root = parser.get_root ().get_object ();
|
||||
event = root.get_string_member ("event");
|
||||
}
|
||||
|
||||
static Json.Object sanitize (Json.Object root) {
|
||||
var payload = root.get_string_member ("payload");
|
||||
var sanitized = Soup.URI.decode (payload);
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data (sanitized, -1);
|
||||
return parser.get_root ().get_object ();
|
||||
}
|
||||
|
||||
static void emit (Bytes bytes, Connection c) throws Error {
|
||||
if (!settings.live_updates)
|
||||
return;
|
||||
|
||||
string event;
|
||||
Json.Object root;
|
||||
decode (bytes, out event, out root);
|
||||
|
||||
// c.subscribers.@foreach (s => {
|
||||
// warning ("%s: %s for %s", c.name, event, get_subscriber_name (s));
|
||||
// return false;
|
||||
// });
|
||||
|
||||
switch (event) {
|
||||
case "update":
|
||||
var entity = new API.Status (sanitize (root));
|
||||
c.subscribers.@foreach (s => {
|
||||
if (s.accepts (ref event))
|
||||
s.on_status_added (entity);
|
||||
return false;
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
var id = int64.parse (root.get_string_member ("payload"));
|
||||
c.subscribers.@foreach (s => {
|
||||
if (s.accepts (ref event))
|
||||
s.on_status_removed (id);
|
||||
return false;
|
||||
});
|
||||
break;
|
||||
case "notification":
|
||||
var entity = new API.Notification (sanitize (root));
|
||||
c.subscribers.@foreach (s => {
|
||||
if (s.accepts (ref event))
|
||||
s.on_notification (entity);
|
||||
return false;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
warning (@"Unknown websocket event: \"$event\". Ignoring.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
public class Tootle.Settings : Granite.Services.Settings {
|
||||
|
||||
public int current_account { get; set; }
|
||||
public bool notifications { get; set; }
|
||||
public bool always_online { get; set; }
|
||||
public bool cache { get; set; }
|
||||
public int cache_size { get; set; }
|
||||
public int char_limit { get; set; }
|
||||
public bool live_updates { get; set; }
|
||||
public bool live_updates_public { get; set; }
|
||||
public bool dark_theme { get; set; }
|
||||
public string watched_users { get; set; }
|
||||
public string watched_hashtags { get; set; }
|
||||
|
||||
public int window_x { get; set; }
|
||||
public int window_y { get; set; }
|
||||
public int window_w { get; set; }
|
||||
public int window_h { get; set; }
|
||||
|
||||
public Settings () {
|
||||
base ("com.github.bleakgrey.tootle");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,651 @@
|
|||
/*
|
||||
* Copyright (C) 2014 PerfectCarl - https://github.com/PerfectCarl/vala-stacktrace
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
public class Stacktrace {
|
||||
|
||||
public class Frame {
|
||||
// Address used by addr2line
|
||||
public string address { get;private set;default = "";}
|
||||
|
||||
public string line { get;private set;default = "";}
|
||||
|
||||
public string line_number { get;private set;default = "";}
|
||||
|
||||
public string file_path { get;private set;default = "";}
|
||||
|
||||
public string file_short_path { get;private set;default = "";}
|
||||
|
||||
public string function { get;private set;default = "";}
|
||||
|
||||
public Frame (string address, string line, string function, string file_path, string file_short_path) {
|
||||
this._address = address;
|
||||
this._line = line;
|
||||
|
||||
this._file_path = file_path;
|
||||
this._file_short_path = file_short_path;
|
||||
this._function = function;
|
||||
this.line_number = extract_line (line);
|
||||
}
|
||||
|
||||
public string to_string () {
|
||||
var result = line;
|
||||
if (result == "")
|
||||
result = " C library at address [" + address + "]";
|
||||
return result + " [" + address + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum Style {
|
||||
RESET = 0,
|
||||
BRIGHT = 1,
|
||||
DIM = 2,
|
||||
UNDERLINE = 3,
|
||||
BLINK = 4,
|
||||
REVERSE = 7,
|
||||
HIDDEN = 8
|
||||
}
|
||||
|
||||
public enum CriticalHandler {
|
||||
IGNORE,
|
||||
PRINT_STACKTRACE,
|
||||
CRASH
|
||||
}
|
||||
|
||||
public enum Color {
|
||||
BLACK = 0,
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
YELLOW = 3,
|
||||
BLUE = 4,
|
||||
MAGENTA = 5,
|
||||
CYAN = 6,
|
||||
WHITE = 7
|
||||
}
|
||||
|
||||
public Gee.ArrayList<Frame> _frames = new Gee.ArrayList<Frame>();
|
||||
|
||||
private Frame first_vala = null;
|
||||
|
||||
private int max_file_name_length = 0;
|
||||
|
||||
private int max_line_number_length = 0;
|
||||
|
||||
private bool is_all_function_name_blank = true;
|
||||
|
||||
private bool is_all_file_name_blank = true;
|
||||
|
||||
private ProcessSignal sig;
|
||||
|
||||
public static bool enabled { get;set;default = true;}
|
||||
|
||||
public static bool hide_installed_libraries { get;set;default = false;}
|
||||
|
||||
public static Color default_highlight_color { get;set;default = Color.WHITE;}
|
||||
|
||||
public static Color default_error_background { get;set;default = Color.RED;}
|
||||
|
||||
public Color highlight_color { get;set;default = Color.WHITE;}
|
||||
|
||||
public Color error_background { get;set;default = Color.RED;}
|
||||
|
||||
public Gee.ArrayList<Frame> frames {
|
||||
get {
|
||||
return _frames;
|
||||
}
|
||||
}
|
||||
|
||||
public Stacktrace (GLib.ProcessSignal sig = GLib.ProcessSignal.TTOU) {
|
||||
this.sig = sig;
|
||||
error_background = default_error_background;
|
||||
highlight_color = default_highlight_color;
|
||||
//hide_installed_libraries = true;
|
||||
create_stacktrace ();
|
||||
}
|
||||
|
||||
private string get_module_name () {
|
||||
var path = new char[1024];
|
||||
Posix.readlink ("/proc/self/exe", path);
|
||||
string result = (string) path;
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO CARL convert this piece of code to vala conventions
|
||||
public static string get_relative_path (string p_fullDestinationPath, string p_startPath) {
|
||||
|
||||
string[] l_startPathParts = p_startPath.split ("/");
|
||||
string[] l_destinationPathParts = p_fullDestinationPath.split ("/");
|
||||
|
||||
int l_sameCounter = 0;
|
||||
while ((l_sameCounter < l_startPathParts.length) &&
|
||||
(l_sameCounter < l_destinationPathParts.length) &&
|
||||
l_startPathParts[l_sameCounter] == l_destinationPathParts[l_sameCounter]) {
|
||||
l_sameCounter++;
|
||||
}
|
||||
|
||||
if (l_sameCounter == 0) {
|
||||
return p_fullDestinationPath; // There is no relative link.
|
||||
}
|
||||
|
||||
StringBuilder l_builder = new StringBuilder ();
|
||||
for (int i = l_sameCounter ; i < l_startPathParts.length ; i++) {
|
||||
l_builder.append ("../");
|
||||
}
|
||||
|
||||
for (int i = l_sameCounter ; i < l_destinationPathParts.length ; i++) {
|
||||
l_builder.append (l_destinationPathParts[i] + "/");
|
||||
}
|
||||
|
||||
// CARL l_builder.Length--;
|
||||
// Remove the last /
|
||||
var result = l_builder.str;
|
||||
result = result.substring (0, result.length - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
private string extract_short_file_path (string file_path) {
|
||||
var path = Environment.get_current_dir ();
|
||||
/*var i = file_path.index_of ( path );
|
||||
if( i>=0 )
|
||||
return file_path.substring ( path.length, file_path.length - path.length );
|
||||
return file_path; */
|
||||
var result = get_relative_path (file_path, path);
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool is_custom {
|
||||
get {
|
||||
return sig == ProcessSignal.TTOU;
|
||||
}
|
||||
}
|
||||
// input : '/home/cran/Documents/Projects/elementary/noise/instant-beta/build/core/libnoise-core.so.0(noise_job_repository_create_job+0x309) [0x7ff60a021e69]'
|
||||
// ouput: 0x309
|
||||
private int extract_base_address (string line) {
|
||||
int result = 0 ;
|
||||
var start = line.last_index_of ("+");
|
||||
if (start >= 0) {
|
||||
var end = line.last_index_of (")");
|
||||
if( end > start ) {
|
||||
var text = line.substring (start+3,end-start-3) ;
|
||||
text.scanf("%x", &result);
|
||||
}
|
||||
}
|
||||
return result ;
|
||||
}
|
||||
|
||||
private void process_info_for_file (string full_line, string str ) {
|
||||
func = "" ;
|
||||
file_path = "";
|
||||
short_file_path = "";
|
||||
l = "";
|
||||
file_line = "";
|
||||
func_line = "";
|
||||
|
||||
var lines = full_line.split ("\n");
|
||||
|
||||
if (lines.length > 0)
|
||||
func_line = lines[0];
|
||||
|
||||
if (lines.length > 1)
|
||||
file_line = lines[1];
|
||||
if (file_line == "??:0" || file_line == "??:?")
|
||||
file_line = "";
|
||||
func = extract_function_name (str);
|
||||
|
||||
file_path = "";
|
||||
short_file_path = "";
|
||||
l = "";
|
||||
if (file_line != "") {
|
||||
if (func == "")
|
||||
func = extract_function_name_from_line (func_line);
|
||||
file_path = extract_file_path (file_line);
|
||||
short_file_path = extract_short_file_path (file_path);
|
||||
l = extract_line (file_line);
|
||||
}
|
||||
}
|
||||
|
||||
string func = "" ;
|
||||
string file_path = "";
|
||||
string short_file_path = "";
|
||||
string l = "";
|
||||
string file_line = "";
|
||||
string func_line = "";
|
||||
|
||||
private void process_info_from_lib (string file_path, string str) {
|
||||
var cmd2 = "nm %s".printf(file_path) ;
|
||||
var addr1_s = execute_command_sync_get_output (cmd2) ;
|
||||
MatchInfo info ;
|
||||
try {
|
||||
Regex regex = new Regex ("\\n[^ ]* T "+func);
|
||||
|
||||
if( regex.match (addr1_s, 0, out info) )
|
||||
{
|
||||
while( info.matches() ){
|
||||
var lll = info.fetch(0) ;
|
||||
//stdout.printf ( "lll '%s'\n", lll ) ;
|
||||
addr1_s = lll.substring(0, lll.index_of(" ")) ;
|
||||
info.next();
|
||||
}
|
||||
}
|
||||
} catch (RegexError e)
|
||||
{
|
||||
critical( "Error while processing regex %s", e.message ) ;
|
||||
}
|
||||
//stdout.printf ("addr1_s %s\n", addr1_s) ;
|
||||
int addr1 = 0 ;
|
||||
addr1_s.scanf("%x", &addr1);
|
||||
if( addr1 != 0 ) {
|
||||
int addr2 = extract_base_address (str) ;
|
||||
string addr3 = "%#08x".printf (addr1+addr2);
|
||||
var new_full_line = process_line (file_path, addr3);
|
||||
process_info_for_file (new_full_line, str ) ;
|
||||
}
|
||||
}
|
||||
|
||||
private void create_stacktrace () {
|
||||
int frame_count = 100;
|
||||
int skipped_frames_count = 5;
|
||||
// Stacktrace not due to a crash
|
||||
if (is_custom)
|
||||
skipped_frames_count = 3;
|
||||
|
||||
void *[] array = new void *[frame_count];
|
||||
|
||||
_frames.clear ();
|
||||
first_vala = null;
|
||||
max_file_name_length = 0;
|
||||
is_all_function_name_blank = true;
|
||||
is_all_file_name_blank = true;
|
||||
|
||||
#if VALA_0_26
|
||||
var size = Linux.Backtrace.@get (array);
|
||||
var strings = Linux.Backtrace.symbols (array);
|
||||
#else
|
||||
int size = Linux.backtrace (array, frame_count);
|
||||
unowned string[] strings = Linux.backtrace_symbols (array, size);
|
||||
// Needed because of some weird bug
|
||||
strings.length = size;
|
||||
#endif
|
||||
|
||||
int[] addresses = (int[])array;
|
||||
string module = get_module_name ();
|
||||
// First ones are the handler
|
||||
for (int i = skipped_frames_count ; i < size ; i++) {
|
||||
int address = addresses[i];
|
||||
string str = strings[i];
|
||||
var addr = extract_address (str);
|
||||
|
||||
var full_line = process_line (module, addr);
|
||||
process_info_for_file( full_line, str) ;
|
||||
if (file_line == "") {
|
||||
file_path = extract_file_path_from (str);
|
||||
|
||||
}
|
||||
if( file_path.has_suffix(".so.0")) {
|
||||
process_info_from_lib (file_path, str) ;
|
||||
}
|
||||
|
||||
//stdout.printf ("Building %d \n . addr: [%s]\n . full_line: '%s'\n . file_line: '%s'\n . func_line: '%s'\n . str : '%s'\n . func: '%s'\n . file: '%s'\n . line: '%s'\n",
|
||||
//i, addr, full_line, file_line, func_line, str, func, file_path, l);
|
||||
|
||||
if (func != "" && file_path.has_suffix (".vala") && is_all_function_name_blank)
|
||||
is_all_function_name_blank = false;
|
||||
|
||||
if (short_file_path != "" && is_all_file_name_blank)
|
||||
is_all_file_name_blank = false;
|
||||
|
||||
var frame = new Frame (addr, file_line, func, file_path, short_file_path);
|
||||
|
||||
if (first_vala == null && file_path.has_suffix (".vala"))
|
||||
first_vala = frame;
|
||||
|
||||
if (short_file_path.length > max_file_name_length)
|
||||
max_file_name_length = short_file_path.length;
|
||||
if (l.length > max_line_number_length)
|
||||
max_line_number_length = l.length;
|
||||
_frames.add (frame);
|
||||
}
|
||||
}
|
||||
|
||||
private string extract_function_name (string line) {
|
||||
if (line == "")
|
||||
return "";
|
||||
var start = line.index_of ("(");
|
||||
if (start >= 0) {
|
||||
var end = line.index_of ("+", start);
|
||||
if (end >= 0) {
|
||||
var result = line.substring (start + 1, end - start - 1);
|
||||
return result.strip ();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private string extract_function_name_from_line (string line) {
|
||||
return line.strip ();
|
||||
}
|
||||
|
||||
private string extract_file_path_from (string str) {
|
||||
if (str == "")
|
||||
return "";
|
||||
/*if( str.index_of("??") >= 0)
|
||||
//result = result.substring (4, line.length - 4 );
|
||||
stdout.printf ("ERR2?? : %s\n", str ) ; */
|
||||
var start = str.index_of ("(");
|
||||
if (start >= 0) {
|
||||
return str.substring (0, start).strip ();
|
||||
}
|
||||
return str.strip ();
|
||||
}
|
||||
|
||||
private string extract_file_path (string line) {
|
||||
var result = line;
|
||||
if (result == "")
|
||||
return "";
|
||||
if (result == "??:0??:0")
|
||||
return "";
|
||||
// For some reason, the file name can starts with ??:0
|
||||
if (result.has_prefix ("??:0"))
|
||||
result = result.substring (4, line.length - 4);
|
||||
// stdout.printf ("ERR1?? : %s\n", line ) ;
|
||||
var start = result.index_of (":");
|
||||
if (start >= 0) {
|
||||
result = result.substring (0, start);
|
||||
return result.strip ();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static string extract_line (string line) {
|
||||
var result = line;
|
||||
if (result == "")
|
||||
return "";
|
||||
if (result.has_prefix ("??:0"))
|
||||
result = result.substring (4, line.length - 4);
|
||||
var start = result.index_of (":");
|
||||
if (start >= 0) {
|
||||
result = result.substring (start + 1, line.length - start - 1);
|
||||
var end = result.index_of ("(");
|
||||
if (end >= 0) {
|
||||
result = result.substring (0, end);
|
||||
}
|
||||
return result.strip ();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private string extract_address (string line) {
|
||||
if (line == "")
|
||||
return "";
|
||||
var start = line.index_of ("[");
|
||||
if (start >= 0) {
|
||||
var end = line.index_of ("]", start);
|
||||
if (end >= 0) {
|
||||
var result = line.substring (start + 1, end - start - 1);
|
||||
return result.strip ();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private string execute_command_sync_get_output (string cmd) {
|
||||
try {
|
||||
int exitCode;
|
||||
string std_out;
|
||||
string std_err;
|
||||
Process.spawn_command_line_sync (cmd, out std_out, out std_err, out exitCode);
|
||||
return std_out;
|
||||
}
|
||||
catch (Error e) {
|
||||
warning (@"Error while executing '$cmd': $(e.message)");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Poor's man demangler. libunwind is another dep
|
||||
// TODO : Optimize this
|
||||
// module : app
|
||||
// address : 0x007f80
|
||||
// output : /home/cran/Projects/noise/noise-perf-instant-search/tests/errors.vala:87
|
||||
string process_line (string module, string address) {
|
||||
var cmd = "addr2line -f -e %s %s".printf (module, address);
|
||||
var result = execute_command_sync_get_output (cmd);
|
||||
//stdout.printf( "CMD %s\n", cmd) ;
|
||||
return result;
|
||||
}
|
||||
|
||||
private string get_reset_code () {
|
||||
// return get_color_code (Style.RESET, Colors.WHITE, Colors.BLACK);
|
||||
return "\x1b[0m";
|
||||
}
|
||||
|
||||
private string get_reset_style () {
|
||||
return get_color_code (Style.DIM, highlight_color, background_color);
|
||||
}
|
||||
|
||||
private string get_color_code (Style attr, Color fg, Color bg = background_color) {
|
||||
/* Command is the control command to the terminal */
|
||||
if (bg == Color.BLACK)
|
||||
return "%c[%d;%dm".printf (0x1B, (int) attr, (int) fg + 30);
|
||||
else
|
||||
return "%c[%d;%d;%dm".printf (0x1B, (int) attr, (int) fg + 30, (int) bg + 40);
|
||||
}
|
||||
|
||||
private string get_signal_name () {
|
||||
return sig.to_string ();
|
||||
}
|
||||
|
||||
private string get_highlight_code () {
|
||||
return get_color_code (Style.BRIGHT, highlight_color);
|
||||
}
|
||||
|
||||
private string get_printable_function (Frame frame, int padding = 0) {
|
||||
var result = "";
|
||||
var is_unknown = false;
|
||||
if (frame.function == "") {
|
||||
result = "<unknown> " + frame.address;
|
||||
is_unknown = true;
|
||||
} else {
|
||||
var s = "";
|
||||
int count = padding - get_signal_name ().length;
|
||||
if (padding != 0 && count > 0)
|
||||
s = string.nfill (count, ' ');
|
||||
result = "'" + frame.function + "'" + s;
|
||||
}
|
||||
if (is_unknown)
|
||||
return result + get_reset_code ();
|
||||
else
|
||||
return get_highlight_code () + result + get_reset_code ();
|
||||
}
|
||||
|
||||
private string get_printable_line_number (Frame frame, bool pad = true) {
|
||||
var path = frame.line_number;
|
||||
var result = "";
|
||||
var color = get_highlight_code ();
|
||||
if (path.length >= max_line_number_length || !pad)
|
||||
result = color + path + get_reset_style ();
|
||||
else {
|
||||
result = color + path + get_reset_style ();
|
||||
result = string.nfill (max_line_number_length - path.length, ' ') + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private string get_printable_file_short_path (Frame frame, bool pad = true) {
|
||||
var path = frame.file_short_path;
|
||||
var result = "";
|
||||
var color = get_highlight_code ();
|
||||
if (path.length >= max_file_name_length || !pad)
|
||||
result = color + path + get_reset_style ();
|
||||
else {
|
||||
result = color + path + get_reset_style ();
|
||||
result = result + string.nfill (max_file_name_length - path.length, ' ');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Color background_color = Color.BLACK;
|
||||
int title_length = 0;
|
||||
|
||||
private string get_printable_title () {
|
||||
var c = get_color_code (Style.DIM, highlight_color, background_color);
|
||||
var color = get_highlight_code ();
|
||||
|
||||
var result = "" ;
|
||||
|
||||
if( is_custom)
|
||||
result = "%sA function was called in %s".printf (
|
||||
c,
|
||||
get_reset_style ());
|
||||
else
|
||||
result = "%sAn error occured %s(%s)%s".printf (
|
||||
c,
|
||||
color,
|
||||
get_signal_name (),
|
||||
get_reset_style ());
|
||||
|
||||
title_length = get_signal_name ().length;
|
||||
return result;
|
||||
}
|
||||
|
||||
private string get_reason () {
|
||||
// var c = get_reset_code();
|
||||
var color = get_highlight_code ();
|
||||
if (sig == ProcessSignal.TRAP) {
|
||||
return "The reason is likely %san uncaught error%s".printf (
|
||||
color, get_reset_code ());
|
||||
}
|
||||
if (sig == ProcessSignal.ABRT) {
|
||||
return "The reason is likely %sa failed assertion (assert...)%s".printf (
|
||||
color, get_reset_code ());
|
||||
}
|
||||
if (sig == ProcessSignal.SEGV) {
|
||||
return "The reason is likely %sa null reference being used%s".printf (
|
||||
color, get_reset_code ());
|
||||
}
|
||||
return "Unknown reason";
|
||||
}
|
||||
|
||||
public void print () {
|
||||
stdout.printf ("\n");
|
||||
background_color = error_background;
|
||||
var header = "%s%s\n".printf (get_printable_title (),
|
||||
get_reset_code ());
|
||||
|
||||
if (first_vala != null) {
|
||||
header = "%s in %s, line %s in %s\n".printf (
|
||||
get_printable_title (),
|
||||
get_printable_file_short_path (first_vala, false),
|
||||
get_printable_line_number (first_vala, false),
|
||||
get_printable_function (first_vala) + get_reset_code ());
|
||||
title_length += first_vala.line_number.length +
|
||||
first_vala.function.length +
|
||||
first_vala.file_short_path.length;
|
||||
}
|
||||
stdout.printf (header);
|
||||
background_color = Color.BLACK;
|
||||
if( !is_custom) {
|
||||
var reason = get_reason ();
|
||||
stdout.printf ("%s\n", reason);
|
||||
}
|
||||
|
||||
// Has the user forgot to compile with -g -X -rdynamic flag ?
|
||||
if (is_all_file_name_blank) {
|
||||
//var advice = " %sNote%s: no file path and line numbers can be retrieved. Are you sure %syou added -g -X -rdynamic%s to valac command line?\n";
|
||||
var advice = "%sNote%s: no vala function name can be retrieved.";
|
||||
var color = get_highlight_code ();
|
||||
stdout.printf (advice, color, get_reset_code (), color, get_reset_code ());
|
||||
}
|
||||
|
||||
// Has the user forgot to compile with rdynamic flag ?
|
||||
if (is_all_function_name_blank && !is_all_file_name_blank) {
|
||||
var advice = "%sNote%s: no vala function name can be retrieved.";
|
||||
//var advice = " %sNote%s: no vala function name can be retrieved. Are you sure %syou added -X -rdynamic%s to valac command line?\n";
|
||||
var color = get_highlight_code ();
|
||||
stdout.printf (advice, color, get_reset_code (), color, get_reset_code ());
|
||||
}
|
||||
|
||||
stdout.printf ("\n");
|
||||
int i = 1;
|
||||
bool has_displayed_first_vala = false;
|
||||
foreach (var frame in _frames) {
|
||||
var show_frame = frame.function != "" || frame.file_path.has_suffix (".vala") || frame.file_path.has_suffix (".c");
|
||||
if (hide_installed_libraries && has_displayed_first_vala)
|
||||
show_frame = show_frame && frame.file_short_path != "";
|
||||
|
||||
// Ignore glib tracing code if displayed before the first vala frame
|
||||
if ((frame.function == "g_logv" || frame.function == "g_log") && !has_displayed_first_vala)
|
||||
show_frame = false;
|
||||
if (show_frame) {
|
||||
// #2 ./OtherModule.c line 80 in 'other_module_do_it'
|
||||
// at /home/cran/Projects/noise/noise-perf-instant-search/tests/errors/module/OtherModule.vala:10
|
||||
var str = " %s #%d %s line %s in %s\n";
|
||||
background_color = Color.BLACK;
|
||||
var lead = " ";
|
||||
var function_padding = 0;
|
||||
if (frame == first_vala) {
|
||||
has_displayed_first_vala = true;
|
||||
lead = "*";
|
||||
background_color = error_background;
|
||||
function_padding = 22;
|
||||
}
|
||||
var l_number = "";
|
||||
if (frame.line_number == "") {
|
||||
str = " %s #%d <unknown> %s in %s\n";
|
||||
var func_name = get_printable_function (frame);
|
||||
var fill_len = int.max (max_file_name_length + max_line_number_length - 1, 0);
|
||||
str = str.printf (
|
||||
lead,
|
||||
i,
|
||||
string.nfill (fill_len, ' '),
|
||||
func_name);
|
||||
} else {
|
||||
str = str.printf (
|
||||
lead,
|
||||
i,
|
||||
get_printable_file_short_path (frame),
|
||||
get_printable_line_number (frame),
|
||||
get_printable_function (frame, function_padding));
|
||||
l_number = ":" + frame.line_number;
|
||||
}
|
||||
stdout.printf (str);
|
||||
str = " at %s%s\n".printf (
|
||||
frame.file_path, l_number);
|
||||
stdout.printf (str);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void register_handlers () {
|
||||
Process.@signal (ProcessSignal.SEGV, handler);
|
||||
Process.@signal (ProcessSignal.ABRT, handler);
|
||||
Process.@signal (ProcessSignal.TRAP, handler);
|
||||
}
|
||||
|
||||
public static CriticalHandler critical_handling { get;set;default = CriticalHandler.PRINT_STACKTRACE;}
|
||||
|
||||
public static void handler (int sig) {
|
||||
Stacktrace stack = new Stacktrace ((ProcessSignal) sig);
|
||||
stack.print ();
|
||||
if (sig != ProcessSignal.TRAP ||
|
||||
(sig == ProcessSignal.TRAP && critical_handling == CriticalHandler.CRASH))
|
||||
Process.exit (1);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
public class Tootle.Utils {
|
||||
|
||||
public static void merge (GLib.Object what, GLib.Object with) {
|
||||
var props = with.get_class ().list_properties ();
|
||||
foreach (var prop in props) {
|
||||
var name = prop.get_name ();
|
||||
var defined = what.get_class ().find_property (name) != null;
|
||||
if (defined) {
|
||||
var val = Value (prop.value_type);
|
||||
with.get_property (name, ref val);
|
||||
what.set_property (name, val) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
using Gtk;
|
||||
|
||||
public abstract class Tootle.Views.Abstract : ScrolledWindow {
|
||||
|
||||
public bool current = false;
|
||||
public int stack_pos = -1;
|
||||
public Image? image;
|
||||
public Box view;
|
||||
protected Box? empty;
|
||||
protected Grid? header;
|
||||
|
||||
construct {
|
||||
view = new Box (Orientation.VERTICAL, 0);
|
||||
view.valign = Align.START;
|
||||
add (view);
|
||||
|
||||
hscrollbar_policy = PolicyType.NEVER;
|
||||
edge_reached.connect (pos => {
|
||||
if (pos == PositionType.BOTTOM)
|
||||
on_bottom_reached ();
|
||||
});
|
||||
}
|
||||
|
||||
public Abstract () {
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public virtual string get_icon () {
|
||||
return "null";
|
||||
}
|
||||
|
||||
public virtual string get_name () {
|
||||
return "unnamed";
|
||||
}
|
||||
|
||||
public virtual void clear (){
|
||||
view.forall (widget => {
|
||||
if (widget != header)
|
||||
widget.destroy ();
|
||||
});
|
||||
}
|
||||
|
||||
public virtual void on_bottom_reached () {}
|
||||
public virtual void on_set_current () {}
|
||||
|
||||
public virtual bool is_empty () {
|
||||
return view.get_children ().length () <= 1;
|
||||
}
|
||||
|
||||
public virtual bool empty_state () {
|
||||
if (empty != null)
|
||||
empty.destroy ();
|
||||
if (!is_empty ())
|
||||
return false;
|
||||
|
||||
empty = new Box (Orientation.VERTICAL, 0);
|
||||
empty.margin = 64;
|
||||
var image = new Image.from_resource ("/com/github/bleakgrey/tootle/empty_state");
|
||||
var text = new Label (_("Nothing to see here"));
|
||||
text.get_style_context ().add_class ("h2");
|
||||
text.opacity = 0.5;
|
||||
empty.hexpand = true;
|
||||
empty.vexpand = true;
|
||||
empty.valign = Align.FILL;
|
||||
empty.pack_start (image, false, false, 0);
|
||||
empty.pack_start (text, false, false, 12);
|
||||
empty.show_all ();
|
||||
view.pack_start (empty, false, false, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
using Gtk;
|
||||
|
||||
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/views/base.ui")]
|
||||
public class Tootle.Views.Base : Box {
|
||||
|
||||
public static string STATUS_EMPTY = _("Nothing to see here");
|
||||
public static string STATUS_LOADING = " ";
|
||||
|
||||
public bool current = false;
|
||||
public int stack_pos = -1;
|
||||
public Image? image;
|
||||
|
||||
[GtkChild]
|
||||
protected ScrolledWindow scrolled;
|
||||
[GtkChild]
|
||||
protected Box view;
|
||||
[GtkChild]
|
||||
protected Stack states;
|
||||
[GtkChild]
|
||||
protected Box content;
|
||||
[GtkChild]
|
||||
private Label status_message_label;
|
||||
[GtkChild]
|
||||
protected Button status_button;
|
||||
[GtkChild]
|
||||
private Stack status_stack;
|
||||
|
||||
public string state { get; set; default = "status"; }
|
||||
public string status_message { get; set; default = STATUS_EMPTY; }
|
||||
public bool allow_closing { get; set; default = true; }
|
||||
|
||||
public bool empty {
|
||||
get {
|
||||
return content.get_children ().length () <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
construct {
|
||||
status_button.label = _("Reload");
|
||||
bind_property ("state", states, "visible-child-name", BindingFlags.SYNC_CREATE);
|
||||
scrolled.edge_reached.connect (pos => {
|
||||
if (pos == PositionType.BOTTOM)
|
||||
on_bottom_reached ();
|
||||
});
|
||||
content.remove.connect (() => on_content_changed ());
|
||||
|
||||
notify["status-message"].connect (() => {
|
||||
status_message_label.label = @"<span size='large'>$status_message</span>";
|
||||
status_stack.visible_child_name = status_message == STATUS_LOADING ? "spinner" : "message";
|
||||
});
|
||||
}
|
||||
|
||||
public virtual string get_icon () {
|
||||
return "null";
|
||||
}
|
||||
|
||||
public virtual string get_name () {
|
||||
return "unnamed";
|
||||
}
|
||||
|
||||
public virtual void clear (){
|
||||
content.forall (widget => {
|
||||
widget.destroy ();
|
||||
});
|
||||
state = "status";
|
||||
}
|
||||
|
||||
public virtual void on_bottom_reached () {}
|
||||
public virtual void on_set_current () {}
|
||||
|
||||
public virtual void on_content_changed () {
|
||||
if (empty) {
|
||||
status_message = STATUS_EMPTY;
|
||||
state = "status";
|
||||
}
|
||||
else {
|
||||
state = "content";
|
||||
}
|
||||
check_resize ();
|
||||
}
|
||||
|
||||
public virtual void on_error (int32 code, string reason) {
|
||||
status_message = reason;
|
||||
status_button.visible = true;
|
||||
status_button.sensitive = true;
|
||||
state = "status";
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
public class Tootle.Views.Direct : Views.Timeline {
|
||||
|
||||
public Direct () {
|
||||
base ("direct");
|
||||
Object (timeline: "direct");
|
||||
}
|
||||
|
||||
public override string get_icon () {
|
||||
|
@ -12,9 +12,8 @@ public class Tootle.Views.Direct : Views.Timeline {
|
|||
return _("Direct Messages");
|
||||
}
|
||||
|
||||
public override Soup.Message? get_stream () {
|
||||
var url = "%s/api/v1/streaming/?stream=direct&access_token=%s".printf (accounts.formal.instance, accounts.formal.token);
|
||||
return new Soup.Message("GET", url);
|
||||
public override string? get_stream_url () {
|
||||
return @"/api/v1/streaming/?stream=direct&access_token=$(accounts.active.token)";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,111 +1,91 @@
|
|||
using Gtk;
|
||||
|
||||
public class Tootle.Views.ExpandedStatus : Views.Abstract {
|
||||
public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
||||
|
||||
private API.Status root_status;
|
||||
private bool last_status_was_root = false;
|
||||
private bool sensitive_visible = false;
|
||||
public API.Status root_status { get; construct set; }
|
||||
protected InstanceAccount? account = null;
|
||||
protected Widgets.Status root_widget;
|
||||
|
||||
public ExpandedStatus (API.Status status) {
|
||||
base ();
|
||||
root_status = status;
|
||||
Object (root_status: status, state: "content");
|
||||
|
||||
root_widget = append (status);
|
||||
root_widget.avatar.button_press_event.connect (root_widget.on_avatar_clicked);
|
||||
root_widget.get_style_context ().add_class ("card");
|
||||
root_widget.get_style_context ().add_class ("highlight");
|
||||
|
||||
connect_account ();
|
||||
}
|
||||
|
||||
public override void on_account_changed (InstanceAccount? acc) {
|
||||
account = acc;
|
||||
request ();
|
||||
|
||||
window.button_reveal.clicked.connect (on_reveal_toggle);
|
||||
}
|
||||
|
||||
~ExpandedStatus () {
|
||||
if (window != null) {
|
||||
window.button_reveal.clicked.disconnect (on_reveal_toggle);
|
||||
window.button_reveal.hide ();
|
||||
}
|
||||
}
|
||||
|
||||
private void prepend (API.Status status, bool is_root = false){
|
||||
var separator = new Separator (Orientation.HORIZONTAL);
|
||||
separator.show ();
|
||||
|
||||
private Widgets.Status prepend (API.Status status, bool to_end = false){
|
||||
var widget = new Widgets.Status (status);
|
||||
widget.avatar.button_press_event.connect (widget.on_avatar_clicked);
|
||||
if (!is_root)
|
||||
widget.button_press_event.connect (widget.open);
|
||||
else
|
||||
widget.highlight ();
|
||||
widget.revealer.reveal_child = true;
|
||||
|
||||
if (!last_status_was_root) {
|
||||
widget.separator = separator;
|
||||
view.pack_start (separator, false, false, 0);
|
||||
content.pack_start (widget, false, false, 0);
|
||||
if (!to_end)
|
||||
content.reorder_child (widget, 0);
|
||||
|
||||
check_resize ();
|
||||
return widget;
|
||||
}
|
||||
view.pack_start (widget, false, false, 0);
|
||||
last_status_was_root = is_root;
|
||||
|
||||
if (status.has_spoiler ())
|
||||
window.button_reveal.show ();
|
||||
if (sensitive_visible)
|
||||
reveal_sensitive (widget);
|
||||
private Widgets.Status append (API.Status status) {
|
||||
return prepend (status, true);
|
||||
}
|
||||
|
||||
public Soup.Message request (){
|
||||
var url = "%s/api/v1/statuses/%lld/context".printf (accounts.formal.instance, root_status.id);
|
||||
var msg = new Soup.Message ("GET", url);
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
public void request () {
|
||||
new Request.GET (@"/api/v1/statuses/$(root_status.id)/context")
|
||||
.with_account (account)
|
||||
.then_parse_obj (root => {
|
||||
if (scrolled == null) return;
|
||||
|
||||
var ancestors = root.get_array_member ("ancestors");
|
||||
ancestors.foreach_element ((array, i, node) => {
|
||||
var object = node.get_object ();
|
||||
if (object != null) {
|
||||
var status = API.Status.parse (object);
|
||||
var status = new API.Status (object);
|
||||
prepend (status);
|
||||
}
|
||||
});
|
||||
|
||||
prepend (root_status, true);
|
||||
|
||||
var descendants = root.get_array_member ("descendants");
|
||||
descendants.foreach_element ((array, i, node) => {
|
||||
var object = node.get_object ();
|
||||
if (object != null) {
|
||||
var status = API.Status.parse (object);
|
||||
prepend (status);
|
||||
var status = new API.Status (object);
|
||||
append (status);
|
||||
}
|
||||
});
|
||||
});
|
||||
return msg;
|
||||
|
||||
int x,y;
|
||||
translate_coordinates (root_widget, 0, 0, out x, out y);
|
||||
scrolled.vadjustment.value = (double)(y*-1); //TODO: Animate scrolling?
|
||||
})
|
||||
.exec ();
|
||||
}
|
||||
|
||||
public static void open_from_link (string q){
|
||||
var url = "%s/api/v1/search?q=%s&resolve=true".printf (accounts.formal.instance, q);
|
||||
var msg = new Soup.Message ("GET", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
public static void open_from_link (string q) {
|
||||
new Request.GET ("/api/v1/search")
|
||||
.with_account ()
|
||||
.with_param ("q", q)
|
||||
.with_param ("resolve", "true")
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
var statuses = root.get_array_member ("statuses");
|
||||
var object = statuses.get_element (0).get_object ();
|
||||
if (object != null){
|
||||
var st = API.Status.parse (object);
|
||||
window.open_view (new Views.ExpandedStatus (st));
|
||||
var status = new API.Status (object);
|
||||
window.open_view (new Views.ExpandedStatus (status));
|
||||
}
|
||||
else
|
||||
Desktop.open_uri (q);
|
||||
});
|
||||
}
|
||||
|
||||
private void on_reveal_toggle () {
|
||||
sensitive_visible = !sensitive_visible;
|
||||
view.forall (w => {
|
||||
if (!(w is Widgets.Status))
|
||||
return;
|
||||
|
||||
var widget = w as Widgets.Status;
|
||||
reveal_sensitive (widget);
|
||||
});
|
||||
}
|
||||
|
||||
private void reveal_sensitive (Widgets.Status widget) {
|
||||
if (widget.status.has_spoiler ())
|
||||
widget.revealer.reveal_child = sensitive_visible;
|
||||
})
|
||||
.exec ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
public class Tootle.Views.Favorites : Views.Timeline {
|
||||
|
||||
public Favorites () {
|
||||
base ("favorites");
|
||||
Object (timeline: "favorites");
|
||||
}
|
||||
|
||||
public override string get_url (){
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/favourites/?limit=%i".printf (accounts.formal.instance, this.limit);
|
||||
return url;
|
||||
return @"/api/v1/favourites";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
public class Tootle.Views.Federated : Views.Timeline {
|
||||
|
||||
public Federated () {
|
||||
base ("public");
|
||||
Object (timeline: "public", is_public: true);
|
||||
}
|
||||
|
||||
public override string get_icon () {
|
||||
|
@ -12,13 +12,8 @@ public class Tootle.Views.Federated : Views.Timeline {
|
|||
return _("Federated Timeline");
|
||||
}
|
||||
|
||||
protected override bool is_public () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Soup.Message? get_stream () {
|
||||
var url = "%s/api/v1/streaming/?stream=public&access_token=%s".printf (accounts.formal.instance, accounts.formal.token);
|
||||
return new Soup.Message("GET", url);
|
||||
public override string? get_stream_url () {
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public&access_token=$(account.token)" : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
using Gtk;
|
||||
|
||||
public class Tootle.Views.Followers : Views.Timeline {
|
||||
|
||||
public Followers (API.Account account) {
|
||||
base (account.id.to_string ());
|
||||
}
|
||||
|
||||
public new void append (API.Account account){
|
||||
if (empty != null)
|
||||
empty.destroy ();
|
||||
|
||||
var separator = new Separator (Orientation.HORIZONTAL);
|
||||
separator.show ();
|
||||
|
||||
var widget = new Widgets.Account (account);
|
||||
widget.separator = separator;
|
||||
view.pack_start (separator, false, false, 0);
|
||||
view.pack_start (widget, false, false, 0);
|
||||
}
|
||||
|
||||
public override string get_url (){
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/accounts/%s/followers".printf (accounts.formal.instance, this.timeline);
|
||||
return url;
|
||||
}
|
||||
|
||||
public override void request (){
|
||||
var msg = new Soup.Message("GET", get_url ());
|
||||
msg.finished.connect (() => empty_state ());
|
||||
network.queue (msg, (sess, mess) => {
|
||||
try {
|
||||
network.parse_array (mess).foreach_element ((array, i, node) => {
|
||||
var object = node.get_object ();
|
||||
if (object != null){
|
||||
var status = API.Account.parse (object);
|
||||
append (status);
|
||||
}
|
||||
});
|
||||
|
||||
get_pages (mess.response_headers.get_one ("Link"));
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning ("Can't get account follow info:");
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
public class Tootle.Views.Following : Views.Followers {
|
||||
|
||||
public Following (API.Account account) {
|
||||
base (account);
|
||||
|
||||
}
|
||||
|
||||
public override string get_url (){
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/accounts/%s/following".printf (accounts.formal.instance, this.timeline);
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +1,12 @@
|
|||
public class Tootle.Views.Hashtag : Views.Timeline {
|
||||
|
||||
public Hashtag (string hashtag) {
|
||||
base ("tag/" + hashtag);
|
||||
public Hashtag (string tag) {
|
||||
Object (timeline: @"tag/$tag");
|
||||
}
|
||||
|
||||
public string get_hashtag () {
|
||||
return this.timeline.substring (4);
|
||||
}
|
||||
|
||||
public override string get_name () {
|
||||
return get_hashtag ();
|
||||
}
|
||||
|
||||
public override Soup.Message? get_stream () {
|
||||
var url = "%s/api/v1/streaming/?stream=hashtag&tag=%s&access_token=%s".printf (accounts.formal.instance, get_hashtag (), accounts.formal.token);
|
||||
return new Soup.Message("GET", url);
|
||||
public override string? get_stream_url () {
|
||||
var tag = timeline.substring (4);
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=hashtag&tag=$tag&access_token=$(account.token)" : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
public class Tootle.Views.Home : Views.Timeline {
|
||||
|
||||
public Home () {
|
||||
base ("home");
|
||||
Object (timeline: "home");
|
||||
}
|
||||
|
||||
public override string get_icon () {
|
||||
|
@ -12,8 +12,8 @@ public class Tootle.Views.Home : Views.Timeline {
|
|||
return _("Home");
|
||||
}
|
||||
|
||||
public override Soup.Message? get_stream () {
|
||||
return accounts.formal.get_stream ();
|
||||
public override string? get_stream_url () {
|
||||
return account.get_stream_url () ?? null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
public class Tootle.Views.Local : Views.Timeline {
|
||||
|
||||
public Local () {
|
||||
base ("public");
|
||||
}
|
||||
public class Tootle.Views.Local : Views.Federated {
|
||||
|
||||
public override string get_icon () {
|
||||
return Desktop.fallback_icon ("system-users-symbolic", "document-open-recent-symbolic");
|
||||
|
@ -12,19 +8,14 @@ public class Tootle.Views.Local : Views.Timeline {
|
|||
return _("Local Timeline");
|
||||
}
|
||||
|
||||
public override string get_url (){
|
||||
var url = base.get_url ();
|
||||
url += "&local=true";
|
||||
return url;
|
||||
public override Request append_params (Request req) {
|
||||
req.with_param ("local", "true");
|
||||
req.with_param ("limit", limit.to_string ());
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override bool is_public () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Soup.Message? get_stream () {
|
||||
var url = "%s/api/v1/streaming/?stream=public:local&access_token=%s".printf (accounts.formal.instance, accounts.formal.token);
|
||||
return new Soup.Message("GET", url);
|
||||
public override string? get_stream_url () {
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public:local&access_token=$(account.token)" : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
using Gtk;
|
||||
|
||||
public class Tootle.Views.NewAccount : Views.Base {
|
||||
|
||||
private string? instance { get; set; }
|
||||
private string? code { get; set; }
|
||||
private string scopes = "read%20write%20follow";
|
||||
|
||||
private string? client_id { get; set; }
|
||||
private string? client_secret { get; set; }
|
||||
private string? access_token { get; set; }
|
||||
private string redirect_uri { get; set; default = "urn:ietf:wg:oauth:2.0:oob"; } //TODO: Investigate URI handling for automatic token getting
|
||||
private InstanceAccount account;
|
||||
|
||||
private Button next_button;
|
||||
private Entry instance_entry;
|
||||
private Entry code_entry;
|
||||
private Label reset_label;
|
||||
|
||||
private Stack stack;
|
||||
private Widget step1;
|
||||
private Widget step2;
|
||||
|
||||
public NewAccount (bool allow_closing = true) {
|
||||
Object (allow_closing: allow_closing);
|
||||
|
||||
var builder = new Builder.from_resource (@"$(Build.RESOURCES)ui/views/new_account.ui");
|
||||
content.pack_start (builder.get_object ("wizard") as Grid);
|
||||
state = "content";
|
||||
next_button = builder.get_object ("next") as Button;
|
||||
reset_label = builder.get_object ("reset") as Label;
|
||||
instance_entry = builder.get_object ("instance_entry") as Entry;
|
||||
code_entry = builder.get_object ("code_entry") as Entry;
|
||||
|
||||
stack = builder.get_object ("stack") as Stack;
|
||||
step1 = builder.get_object ("step1") as Widget;
|
||||
step2 = builder.get_object ("step2") as Widget;
|
||||
|
||||
next_button.clicked.connect (on_next_clicked);
|
||||
reset_label.activate_link.connect (reset);
|
||||
instance_entry.text = "https://mastodon.social/"; //TODO: REMOVE ME
|
||||
info ("New account view was requested");
|
||||
}
|
||||
|
||||
private bool reset () {
|
||||
info ("State invalidated");
|
||||
instance = code = client_id = client_secret = access_token = null;
|
||||
instance_entry.sensitive = true;
|
||||
stack.visible_child = step1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void oopsie (string message) {
|
||||
warning (message);
|
||||
}
|
||||
|
||||
private void on_next_clicked () {
|
||||
try {
|
||||
step ();
|
||||
}
|
||||
catch (Oopsie e) {
|
||||
oopsie (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void step () throws Error {
|
||||
if (instance == null)
|
||||
setup_instance ();
|
||||
|
||||
if (client_secret == null || client_id == null) {
|
||||
register_client ();
|
||||
return;
|
||||
}
|
||||
|
||||
code = code_entry.text;
|
||||
request_token ();
|
||||
}
|
||||
|
||||
private void setup_instance () throws Error {
|
||||
info ("Checking instance URL");
|
||||
|
||||
var str = instance_entry.text
|
||||
.replace ("/", "")
|
||||
.replace (":", "")
|
||||
.replace ("https", "")
|
||||
.replace ("http", "");
|
||||
instance = "https://"+str;
|
||||
instance_entry.text = str;
|
||||
|
||||
if (str.char_count () <= 0 || !("." in instance))
|
||||
throw new Oopsie.USER (_("Instance URL is invalid"));
|
||||
}
|
||||
|
||||
private void register_client () throws Error {
|
||||
info ("Registering client");
|
||||
instance_entry.sensitive = false;
|
||||
|
||||
account = new InstanceAccount.empty (instance);
|
||||
|
||||
new Request.POST (@"/api/v1/apps")
|
||||
.with_param ("client_name", Build.NAME)
|
||||
.with_param ("website", Build.WEBSITE)
|
||||
.with_param ("scopes", scopes)
|
||||
.with_param ("redirect_uris", redirect_uri)
|
||||
.with_account (account)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
client_id = root.get_string_member ("client_id");
|
||||
client_secret = root.get_string_member ("client_secret");
|
||||
info ("OK: instance registered client");
|
||||
stack.visible_child = step2;
|
||||
|
||||
open_confirmation_page ();
|
||||
})
|
||||
.on_error ((status, reason) => {
|
||||
oopsie (reason);
|
||||
instance_entry.sensitive = true;
|
||||
})
|
||||
.exec ();
|
||||
}
|
||||
|
||||
private void open_confirmation_page () {
|
||||
info ("Opening permission request page");
|
||||
|
||||
var pars = @"scope=$scopes&response_type=code&redirect_uri=$redirect_uri&client_id=$client_id";
|
||||
var url = @"$instance/oauth/authorize?$pars";
|
||||
Desktop.open_uri (url);
|
||||
}
|
||||
|
||||
private void request_token () throws Error {
|
||||
if (code.char_count () <= 10)
|
||||
throw new Oopsie.USER (_("Please paste a valid authorization code"));
|
||||
|
||||
info ("Requesting access token");
|
||||
new Request.POST (@"/oauth/token")
|
||||
.with_account (account)
|
||||
.with_param ("client_id", client_id)
|
||||
.with_param ("client_secret", client_secret)
|
||||
.with_param ("redirect_uri", redirect_uri)
|
||||
.with_param ("grant_type", "authorization_code")
|
||||
.with_param ("code", code)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
access_token = root.get_string_member ("access_token");
|
||||
account.token = access_token;
|
||||
account.id = 0;
|
||||
info ("OK: received access token");
|
||||
request_profile ();
|
||||
})
|
||||
.on_error ((code, reason) => oopsie (reason))
|
||||
.exec ();
|
||||
}
|
||||
|
||||
private void request_profile () throws Error {
|
||||
info ("Testing received access token");
|
||||
new Request.GET ("/api/v1/accounts/verify_credentials")
|
||||
.with_account (account)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
var account = new API.Account (root);
|
||||
info ("OK: received user profile");
|
||||
save (account);
|
||||
})
|
||||
.on_error ((status, reason) => {
|
||||
reset ();
|
||||
oopsie (reason);
|
||||
})
|
||||
.exec ();
|
||||
}
|
||||
|
||||
private void save (API.Account profile) {
|
||||
info ("Account validated. Saving...");
|
||||
account.patch (profile);
|
||||
account.instance = instance;
|
||||
account.client_id = client_id;
|
||||
account.client_secret = client_secret;
|
||||
account.token = access_token;
|
||||
accounts.add (account);
|
||||
|
||||
destroy ();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +1,20 @@
|
|||
using Gtk;
|
||||
using Gdk;
|
||||
|
||||
public class Tootle.Views.Notifications : Views.Abstract {
|
||||
public class Tootle.Views.Notifications : Views.Base, IAccountListener {
|
||||
|
||||
private int64 last_id = 0;
|
||||
private bool force_dot = false;
|
||||
protected InstanceAccount? account = null;
|
||||
protected int64 last_id = 0;
|
||||
protected bool force_dot = false;
|
||||
|
||||
public Notifications () {
|
||||
base ();
|
||||
view.remove.connect (on_remove);
|
||||
accounts.switched.connect (on_account_changed);
|
||||
app.refresh.connect (on_refresh);
|
||||
network.notification.connect (prepend);
|
||||
|
||||
request ();
|
||||
status_button.clicked.connect (on_refresh);
|
||||
streams.notification.connect (prepend);
|
||||
connect_account ();
|
||||
}
|
||||
|
||||
private bool has_unread () {
|
||||
var account = accounts.formal;
|
||||
if (account == null)
|
||||
return false;
|
||||
return last_id > account.last_seen_notification || force_dot;
|
||||
|
@ -39,27 +36,21 @@ public class Tootle.Views.Notifications : Views.Abstract {
|
|||
}
|
||||
|
||||
public void append (API.Notification notification, bool reverse = false) {
|
||||
if (empty != null)
|
||||
empty.destroy ();
|
||||
|
||||
var separator = new Separator (Orientation.HORIZONTAL);
|
||||
separator.show ();
|
||||
|
||||
GLib.Idle.add (() => {
|
||||
var widget = new Widgets.Notification (notification);
|
||||
widget.separator = separator;
|
||||
view.pack_start (separator, false, false, 0);
|
||||
view.pack_start (widget, false, false, 0);
|
||||
content.pack_start (widget, false, false, 0);
|
||||
|
||||
if (reverse) {
|
||||
view.reorder_child (widget, 0);
|
||||
view.reorder_child (separator, 0);
|
||||
content.reorder_child (widget, 0);
|
||||
|
||||
if (!current) {
|
||||
force_dot = true;
|
||||
accounts.formal.has_unread_notifications = force_dot;
|
||||
accounts.active.has_unread_notifications = force_dot;
|
||||
}
|
||||
}
|
||||
|
||||
on_content_changed ();
|
||||
|
||||
if (notification.id > last_id)
|
||||
last_id = notification.id;
|
||||
|
||||
|
@ -67,10 +58,11 @@ public class Tootle.Views.Notifications : Views.Abstract {
|
|||
accounts.save ();
|
||||
image.icon_name = get_icon ();
|
||||
}
|
||||
return GLib.Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
public override void on_set_current () {
|
||||
var account = accounts.formal;
|
||||
if (has_unread ()) {
|
||||
force_dot = false;
|
||||
account.has_unread_notifications = force_dot;
|
||||
|
@ -80,73 +72,58 @@ public class Tootle.Views.Notifications : Views.Abstract {
|
|||
}
|
||||
}
|
||||
|
||||
public virtual void on_remove (Widget widget) {
|
||||
if (!(widget is Widgets.Notification))
|
||||
return;
|
||||
|
||||
empty_state ();
|
||||
}
|
||||
|
||||
public override bool empty_state () {
|
||||
var is_empty = base.empty_state ();
|
||||
if (image != null && is_empty)
|
||||
public override void on_content_changed () {
|
||||
base.on_content_changed ();
|
||||
if (image != null && empty)
|
||||
image.icon_name = get_icon ();
|
||||
|
||||
return is_empty;
|
||||
}
|
||||
|
||||
public virtual void on_refresh () {
|
||||
clear ();
|
||||
request ();
|
||||
GLib.Idle.add (request);
|
||||
}
|
||||
|
||||
public virtual void on_account_changed (API.Account? account) {
|
||||
if (account == null)
|
||||
return;
|
||||
|
||||
last_id = accounts.formal.last_seen_notification;
|
||||
force_dot = accounts.formal.has_unread_notifications;
|
||||
public virtual void on_account_changed (InstanceAccount? acc) {
|
||||
account = acc;
|
||||
if (account == null) {
|
||||
last_id = 0;
|
||||
force_dot = false;
|
||||
}
|
||||
else {
|
||||
last_id = account.last_seen_notification;
|
||||
force_dot = account.has_unread_notifications;
|
||||
}
|
||||
on_refresh ();
|
||||
}
|
||||
|
||||
public void request () {
|
||||
if (accounts.current == null) {
|
||||
empty_state ();
|
||||
return;
|
||||
}
|
||||
|
||||
accounts.formal.cached_notifications.@foreach (notification => {
|
||||
public bool request () {
|
||||
if (account != null) {
|
||||
account.cached_notifications.@foreach (notification => {
|
||||
append (notification);
|
||||
return true;
|
||||
});
|
||||
|
||||
var url = "%s/api/v1/follow_requests".printf (accounts.formal.instance);
|
||||
var msg = new Soup.Message ("GET", url);
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, mess) => {
|
||||
network.parse_array (mess).foreach_element ((array, i, node) => {
|
||||
var obj = node.get_object ();
|
||||
if (obj != null){
|
||||
var notification = API.Notification.parse_follow_request (obj);
|
||||
append (notification);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var url2 = "%s/api/v1/notifications?limit=30".printf (accounts.formal.instance);
|
||||
var msg2 = new Soup.Message ("GET", url2);
|
||||
network.inject (msg2, Network.INJECT_TOKEN);
|
||||
network.queue (msg2, (sess, mess) => {
|
||||
network.parse_array (mess).foreach_element ((array, i, node) => {
|
||||
var obj = node.get_object ();
|
||||
if (obj != null){
|
||||
var notification = API.Notification.parse (obj);
|
||||
// new Request.GET ("/api/v1/follow_requests") //TODO: this
|
||||
// .with_account ()
|
||||
// .then_parse_array (node => {
|
||||
// var notification = API.Notification.parse_follow_request (node.get_object ());
|
||||
// append (notification);
|
||||
// })
|
||||
// .on_error (on_error)
|
||||
// .exec ();
|
||||
|
||||
new Request.GET ("/api/v1/notifications")
|
||||
.with_account (account)
|
||||
.with_param ("limit", "30")
|
||||
.then_parse_array (node => {
|
||||
var notification = new API.Notification (node.get_object ());
|
||||
append (notification);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.on_error (on_error)
|
||||
.exec ();
|
||||
|
||||
empty_state ();
|
||||
return GLib.Source.REMOVE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,249 +1,190 @@
|
|||
using Gtk;
|
||||
using Granite;
|
||||
|
||||
public class Tootle.Views.Profile : Views.Timeline {
|
||||
|
||||
const int AVATAR_SIZE = 128;
|
||||
protected API.Account account;
|
||||
public API.Account profile { get; construct set; }
|
||||
|
||||
protected RadioButton filter_all;
|
||||
protected RadioButton filter_replies;
|
||||
protected RadioButton filter_media;
|
||||
|
||||
protected Grid header_image;
|
||||
protected Box header_info;
|
||||
protected Granite.Widgets.Avatar avatar;
|
||||
protected Widgets.RichLabel display_name;
|
||||
protected Label username;
|
||||
protected Label relationship;
|
||||
protected Widgets.RichLabel note;
|
||||
protected Grid counters;
|
||||
protected Box actions;
|
||||
protected Button button_follow;
|
||||
|
||||
protected Gtk.Menu menu;
|
||||
protected Gtk.MenuItem menu_edit;
|
||||
protected Gtk.MenuItem menu_mention;
|
||||
protected Gtk.MenuItem menu_mute;
|
||||
protected Gtk.MenuItem menu_block;
|
||||
protected Gtk.MenuItem menu_report;
|
||||
protected Gtk.MenuButton button_menu;
|
||||
protected Button follow_button;
|
||||
protected MenuButton options_button;
|
||||
|
||||
protected Label posts_label;
|
||||
protected Label following_label;
|
||||
protected Label followers_label;
|
||||
protected RadioButton posts_tab;
|
||||
protected RadioButton following_tab;
|
||||
protected RadioButton followers_tab;
|
||||
|
||||
construct {
|
||||
header = new Grid ();
|
||||
header_info = new Box (Orientation.VERTICAL, 0);
|
||||
header_info.margin = 12;
|
||||
actions = new Box (Orientation.HORIZONTAL, 0);
|
||||
actions.hexpand = false;
|
||||
actions.halign = Align.END;
|
||||
actions.vexpand = false;
|
||||
actions.valign = Align.START;
|
||||
actions.margin = 12;
|
||||
profile.notify["rs"].connect (on_rs_updated);
|
||||
|
||||
relationship = new Label ("");
|
||||
relationship.get_style_context ().add_class ("relationship");
|
||||
relationship.halign = Align.START;
|
||||
relationship.valign = Align.START;
|
||||
relationship.margin = 12;
|
||||
header.attach (relationship, 0, 0, 1, 1);
|
||||
var builder = new Builder.from_resource (@"$(Build.RESOURCES)ui/views/profile_header.ui");
|
||||
view.pack_start (builder.get_object ("grid") as Grid, false, false, 0);
|
||||
|
||||
avatar = new Granite.Widgets.Avatar.with_default_icon (AVATAR_SIZE);
|
||||
avatar.hexpand = true;
|
||||
avatar.margin_bottom = 6;
|
||||
header_info.pack_start (avatar, false, false, 0);
|
||||
var avatar = builder.get_object ("avatar") as Widgets.Avatar;
|
||||
avatar.url = profile.avatar;
|
||||
|
||||
display_name = new Widgets.RichLabel ("");
|
||||
display_name.get_style_context ().add_class (Granite.STYLE_CLASS_H2_LABEL);
|
||||
header_info.pack_start (display_name, false, false, 0);
|
||||
var name = builder.get_object ("name") as Widgets.RichLabel;
|
||||
profile.bind_property ("display-name", name, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
var label = (string) src;
|
||||
target.set_string (@"<span size='x-large' weight='bold'>$label</span>");
|
||||
return true;
|
||||
});
|
||||
|
||||
username = new Label ("");
|
||||
header_info.pack_start (username, false, false, 0);
|
||||
var handle = builder.get_object ("handle") as Widgets.RichLabel;
|
||||
profile.bind_property ("acct", handle, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
target.set_string ("@" + (string) src);
|
||||
return true;
|
||||
});
|
||||
|
||||
note = new Widgets.RichLabel ("");
|
||||
note.set_line_wrap (true);
|
||||
note.selectable = true;
|
||||
note.margin_top = 12;
|
||||
note.can_focus = false;
|
||||
note.justify = Justification.CENTER;
|
||||
header_info.pack_start (note, false, false, 0);
|
||||
header_info.show_all ();
|
||||
header.attach (header_info, 0, 0, 1, 1);
|
||||
var note = builder.get_object ("note") as Widgets.RichLabel;
|
||||
profile.bind_property ("note", note, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
target.set_string (Html.simplify ((string) src));
|
||||
return true;
|
||||
});
|
||||
|
||||
counters = new Grid ();
|
||||
counters.column_homogeneous = true;
|
||||
counters.get_style_context ().add_class ("header-counters");
|
||||
header.attach (counters, 0, 1, 1, 1);
|
||||
actions = builder.get_object ("actions") as Box;
|
||||
follow_button = builder.get_object ("follow_button") as Button;
|
||||
follow_button.clicked.connect (on_follow_button_clicked);
|
||||
options_button = builder.get_object ("options_button") as MenuButton;
|
||||
relationship = builder.get_object ("relationship") as Label;
|
||||
|
||||
header_image = new Grid ();
|
||||
header_image.get_style_context ().add_class ("header");
|
||||
header.attach (header_image, 0, 0, 2, 2);
|
||||
posts_label = builder.get_object ("posts_label") as Label;
|
||||
profile.bind_property ("posts_count", posts_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
var val = (int64) src;
|
||||
target.set_string (_("%s Posts").printf (@"<b>$val</b>"));
|
||||
return true;
|
||||
});
|
||||
following_label = builder.get_object ("following_label") as Label;
|
||||
profile.bind_property ("following_count", following_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
var val = (int64) src;
|
||||
target.set_string (_("%s Follows").printf (@"<b>$val</b>"));
|
||||
return true;
|
||||
});
|
||||
followers_label = builder.get_object ("followers_label") as Label;
|
||||
profile.bind_property ("followers_count", followers_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
var val = (int64) src;
|
||||
target.set_string (_("%s Followers").printf (@"<b>$val</b>"));
|
||||
return true;
|
||||
});
|
||||
|
||||
menu = new Gtk.Menu ();
|
||||
menu_edit = new Gtk.MenuItem.with_label (_("Edit Profile"));
|
||||
menu_mention = new Gtk.MenuItem.with_label (_("Mention"));
|
||||
menu_report = new Gtk.MenuItem.with_label (_("Report"));
|
||||
menu_mute = new Gtk.MenuItem.with_label (_("Mute"));
|
||||
menu_block = new Gtk.MenuItem.with_label (_("Block"));
|
||||
menu.add (menu_mention);
|
||||
//menu.add (new Gtk.SeparatorMenuItem ());
|
||||
menu.add (menu_mute);
|
||||
menu.add (menu_block);
|
||||
//menu.add (menu_report); //TODO: Report users
|
||||
//menu.add (menu_edit); //TODO: Edit profile
|
||||
menu.show_all ();
|
||||
filter_all = builder.get_object ("filter_all") as RadioButton;
|
||||
filter_all.toggled.connect (on_refresh);
|
||||
filter_replies = builder.get_object ("filter_replies") as RadioButton;
|
||||
filter_replies.toggled.connect (on_refresh);
|
||||
filter_media = builder.get_object ("filter_media") as RadioButton;
|
||||
filter_media.toggled.connect (on_refresh);
|
||||
|
||||
button_follow = add_counter ("contact-new-symbolic");
|
||||
button_menu = new MenuButton ();
|
||||
button_menu.image = new Image.from_icon_name ("view-more-symbolic", IconSize.LARGE_TOOLBAR);
|
||||
button_menu.tooltip_text = _("More Actions");
|
||||
button_menu.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||
(button_menu as Widget).set_focus_on_click (false);
|
||||
button_menu.can_default = false;
|
||||
button_menu.can_focus = false;
|
||||
button_menu.popup = menu;
|
||||
actions.pack_end(button_menu, false, false, 0);
|
||||
actions.pack_end(button_follow, false, false, 0);
|
||||
button_menu.hide ();
|
||||
button_follow.hide ();
|
||||
header.attach (actions, 0, 0, 2, 2);
|
||||
|
||||
view.pack_start (header, false, false, 0);
|
||||
posts_tab = builder.get_object ("posts_tab") as RadioButton;
|
||||
posts_tab.toggled.connect (() => {
|
||||
if (posts_tab.active) on_refresh ();
|
||||
});
|
||||
following_tab = builder.get_object ("following_tab") as RadioButton;
|
||||
following_tab.toggled.connect (() => {
|
||||
if (following_tab.active) on_refresh ();
|
||||
});
|
||||
followers_tab = builder.get_object ("followers_tab") as RadioButton;
|
||||
followers_tab.toggled.connect (() => {
|
||||
if (followers_tab.active) on_refresh ();
|
||||
});
|
||||
}
|
||||
|
||||
public Profile (API.Account acc) {
|
||||
base ("");
|
||||
account = acc;
|
||||
account.updated.connect (rebind);
|
||||
|
||||
add_counter (_("Toots"), 1, account.statuses_count);
|
||||
add_counter (_("Follows"), 2, account.following_count).clicked.connect (() => {
|
||||
var view = new Views.Following (account);
|
||||
window.open_view (view);
|
||||
});
|
||||
add_counter (_("Followers"), 3, account.followers_count).clicked.connect (() => {
|
||||
var view = new Views.Followers (account);
|
||||
window.open_view (view);
|
||||
});
|
||||
|
||||
show_all ();
|
||||
|
||||
//TODO: Has this thing always been synchronous???
|
||||
//var stylesheet = ".header{background-image: url(\"%s\")}".printf (account.header);
|
||||
//var css_provider = Granite.Widgets.Utils.get_css_provider (stylesheet);
|
||||
//header_image.get_style_context ().add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
menu_mention.activate.connect (() => Dialogs.Compose.open ("@%s ".printf (account.acct)));
|
||||
menu_mute.activate.connect (() => account.set_muted (!account.rs.muting));
|
||||
menu_block.activate.connect (() => account.set_blocked (!account.rs.blocking));
|
||||
button_follow.clicked.connect (() => account.set_following (!account.rs.following));
|
||||
|
||||
rebind ();
|
||||
account.get_relationship ();
|
||||
request ();
|
||||
Object (profile: acc);
|
||||
profile.get_relationship ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void rebind (){
|
||||
display_name.set_label ("<b>%s</b>".printf (account.display_name));
|
||||
username.label = "@" + account.acct;
|
||||
note.set_label (account.note);
|
||||
button_follow.visible = !account.is_self ();
|
||||
network.load_avatar (account.avatar, avatar, 128);
|
||||
|
||||
menu_edit.visible = account.is_self ();
|
||||
|
||||
if (account.rs != null && !account.is_self ()) {
|
||||
button_follow.show ();
|
||||
if (account.rs.following) {
|
||||
button_follow.tooltip_text = _("Unfollow");
|
||||
(button_follow.get_image () as Image).icon_name = "close-symbolic";
|
||||
}
|
||||
else{
|
||||
button_follow.tooltip_text = _("Follow");
|
||||
(button_follow.get_image () as Image).icon_name = "contact-new-symbolic";
|
||||
}
|
||||
protected void on_follow_button_clicked () {
|
||||
actions.sensitive = false;
|
||||
profile.set_following (!profile.rs.following);
|
||||
}
|
||||
|
||||
if (account.rs != null){
|
||||
button_menu.show ();
|
||||
menu_block.label = account.rs.blocking ? _("Unblock") : _("Block");
|
||||
menu_mute.label = account.rs.muting ? _("Unmute") : _("Mute");
|
||||
menu_report.visible = menu_mute.visible = menu_block.visible = !account.is_self ();
|
||||
protected void on_rs_updated () {
|
||||
var rs = profile.rs;
|
||||
var label = "";
|
||||
if (actions.sensitive = rs != null) {
|
||||
if (rs.requested)
|
||||
label = _("Sent follow request");
|
||||
else if (rs.followed_by && rs.following)
|
||||
label = _("Mutually follows you");
|
||||
else if (rs.followed_by)
|
||||
label = _("Follows you");
|
||||
|
||||
var rs_label = get_relationship_label ();
|
||||
if (rs_label != null) {
|
||||
relationship.label = rs_label;
|
||||
relationship.show ();
|
||||
foreach (Widget w in new Widget[] { follow_button, options_button }) {
|
||||
var ctx = w.get_style_context ();
|
||||
ctx.remove_class (STYLE_CLASS_SUGGESTED_ACTION);
|
||||
ctx.remove_class (STYLE_CLASS_DESTRUCTIVE_ACTION);
|
||||
ctx.add_class (rs.following ? STYLE_CLASS_DESTRUCTIVE_ACTION : STYLE_CLASS_SUGGESTED_ACTION);
|
||||
}
|
||||
|
||||
var label2 = "";
|
||||
if (rs.followed_by && !rs.following)
|
||||
label2 = _("Follow back");
|
||||
else if (rs.following)
|
||||
label2 = _("Unfollow");
|
||||
else
|
||||
relationship.hide ();
|
||||
}
|
||||
else
|
||||
relationship.hide ();
|
||||
label2 = _("Follow");
|
||||
|
||||
follow_button.label = label2;
|
||||
}
|
||||
|
||||
public override bool is_status_owned (API.Status status) {
|
||||
return status.is_owned ();
|
||||
}
|
||||
|
||||
private Button add_counter (string name, int? i = null, int64? val = null) {
|
||||
Button btn;
|
||||
if (val != null){
|
||||
btn = new Button ();
|
||||
var label = new Label ("<b>%s</b>\n%s".printf (name.up (), val.to_string ()));
|
||||
label.justify = Justification.CENTER;
|
||||
label.use_markup = true;
|
||||
label.margin = 8;
|
||||
btn.add (label);
|
||||
}
|
||||
else
|
||||
btn = new Button.from_icon_name (name, IconSize.LARGE_TOOLBAR);
|
||||
|
||||
btn.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||
(btn as Widget).set_focus_on_click (false);
|
||||
btn.can_default = false;
|
||||
btn.can_focus = false;
|
||||
|
||||
if (i != null)
|
||||
counters.attach (btn, i, 1, 1, 1);
|
||||
return btn;
|
||||
}
|
||||
|
||||
public override bool is_empty () {
|
||||
return view.get_children ().length () <= 2;
|
||||
relationship.label = label;
|
||||
}
|
||||
|
||||
public override string get_url () {
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/accounts/%lld/statuses?limit=%i".printf (accounts.formal.instance, account.id, this.limit);
|
||||
return url;
|
||||
}
|
||||
|
||||
public override void request () {
|
||||
if (account != null)
|
||||
base.request ();
|
||||
}
|
||||
|
||||
private string? get_relationship_label () {
|
||||
if (account.rs.requested)
|
||||
return _("Sent follow request");
|
||||
else if (account.rs.blocking)
|
||||
return _("Blocked");
|
||||
else if (account.rs.followed_by)
|
||||
return _("Follows you");
|
||||
else if (account.rs.domain_blocking)
|
||||
return _("Blocking this instance");
|
||||
if (following_tab.active)
|
||||
return @"/api/v1/accounts/$(profile.id)/following";
|
||||
else if (followers_tab.active)
|
||||
return @"/api/v1/accounts/$(profile.id)/followers";
|
||||
else
|
||||
return null;
|
||||
return @"/api/v1/accounts/$(profile.id)/statuses";
|
||||
}
|
||||
|
||||
public override Request append_params (Request req) {
|
||||
req.with_param ("exclude_replies", (!filter_replies.active).to_string ());
|
||||
req.with_param ("only_media", filter_media.active.to_string ());
|
||||
return base.append_params (req);
|
||||
}
|
||||
|
||||
public override bool request () {
|
||||
append_params (new Request.GET (get_url ()))
|
||||
.with_account (account)
|
||||
.then_parse_array ((node, msg) => {
|
||||
var obj = node.get_object ();
|
||||
if (obj != null) {
|
||||
API.Status status;
|
||||
if (posts_tab.active)
|
||||
status = new API.Status (obj);
|
||||
else {
|
||||
var account = new API.Account (obj);
|
||||
status = new API.Status.from_account (account);
|
||||
}
|
||||
|
||||
append (status);
|
||||
}
|
||||
get_pages (msg.response_headers.get_one ("Link"));
|
||||
})
|
||||
.on_error (on_error)
|
||||
.exec ();
|
||||
|
||||
return GLib.Source.REMOVE;
|
||||
}
|
||||
|
||||
public static void open_from_id (int64 id){
|
||||
var url = "%s/api/v1/accounts/%lld".printf (accounts.formal.instance, id);
|
||||
var url = "%s/api/v1/accounts/%lld".printf (accounts.active.instance, id);
|
||||
var msg = new Soup.Message ("GET", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.queue (msg, (sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
var acc = API.Account.parse (root);
|
||||
var acc = new API.Account (root);
|
||||
window.open_view (new Views.Profile (acc));
|
||||
}, (status, reason) => {
|
||||
network.on_error (status, reason);
|
||||
|
|
|
@ -1,61 +1,64 @@
|
|||
using Gtk;
|
||||
|
||||
public class Tootle.Views.Search : Views.Abstract {
|
||||
public class Tootle.Views.Search : Views.Base {
|
||||
|
||||
private string query = "";
|
||||
private Entry entry;
|
||||
private SearchBar bar;
|
||||
private SearchEntry entry;
|
||||
|
||||
construct {
|
||||
view.margin_bottom = 6;
|
||||
bar = new SearchBar ();
|
||||
bar.search_mode_enabled = true;
|
||||
bar.show ();
|
||||
pack_start (bar, false, false, 0);
|
||||
|
||||
entry = new Entry ();
|
||||
entry.placeholder_text = _("Search");
|
||||
entry.secondary_icon_name = "system-search-symbolic";
|
||||
entry = new SearchEntry ();
|
||||
entry.width_chars = 25;
|
||||
entry.text = query;
|
||||
entry.valign = Align.CENTER;
|
||||
entry.show ();
|
||||
window.header.pack_start (entry);
|
||||
bar.add (entry);
|
||||
bar.connect_entry (entry);
|
||||
|
||||
destroy.connect (() => entry.destroy ());
|
||||
entry.activate.connect (() => request ());
|
||||
entry.icon_press.connect (() => request ());
|
||||
}
|
||||
|
||||
public Search () {
|
||||
entry.grab_focus_without_selecting ();
|
||||
status_button.clicked.connect (request);
|
||||
}
|
||||
|
||||
private void append_account (API.Account acc) {
|
||||
var widget = new Widgets.Account (acc);
|
||||
view.pack_start (widget, false, false, 0);
|
||||
var status = new API.Status.from_account (acc);
|
||||
var widget = new Widgets.Status (status);
|
||||
widget.button_press_event.connect (widget.on_avatar_clicked);
|
||||
content.pack_start (widget, false, false, 0);
|
||||
on_content_changed ();
|
||||
}
|
||||
|
||||
private void append_status (API.Status status) {
|
||||
var widget = new Widgets.Status (status);
|
||||
widget.button_press_event.connect (widget.on_avatar_clicked);
|
||||
view.pack_start (widget, false, false, 0);
|
||||
content.pack_start (widget, false, false, 0);
|
||||
on_content_changed ();
|
||||
}
|
||||
|
||||
private void append_header (string name) {
|
||||
var widget = new Label (name);
|
||||
widget.get_style_context ().add_class ("h4");
|
||||
var widget = new Label (@"<span weight='bold' size='medium'>$name</span>");
|
||||
widget.halign = Align.START;
|
||||
widget.margin = 6;
|
||||
widget.margin_bottom = 0;
|
||||
widget.margin = 8;
|
||||
widget.use_markup = true;
|
||||
widget.show ();
|
||||
view.pack_start (widget, false, false, 0);
|
||||
content.pack_start (widget, false, false, 0);
|
||||
on_content_changed ();
|
||||
}
|
||||
|
||||
private void append_hashtag (string name) {
|
||||
var text = "<a href=\"%s/tags/%s\">#%s</a>".printf (accounts.formal.instance, Soup.URI.encode (name, null), name);
|
||||
var widget = new Widgets.RichLabel (text);
|
||||
var encoded = Soup.URI.encode (name, null);
|
||||
var widget = new Widgets.RichLabel (@"<a href=\"$(accounts.active.instance)/tags/$encoded\">#$name</a>");
|
||||
widget.use_markup = true;
|
||||
widget.halign = Align.START;
|
||||
widget.margin = 6;
|
||||
widget.margin_bottom = 0;
|
||||
widget.show ();
|
||||
view.pack_start (widget, false, false, 0);
|
||||
content.pack_start (widget, false, false, 0);
|
||||
}
|
||||
|
||||
private void request () {
|
||||
|
@ -64,25 +67,31 @@ public class Tootle.Views.Search : Views.Abstract {
|
|||
clear ();
|
||||
return;
|
||||
}
|
||||
window.reopen_view (this.stack_pos);
|
||||
|
||||
var query_encoded = Soup.URI.encode (query, null);
|
||||
var url = "%s/api/v1/search?q=%s&resolve=true".printf (accounts.formal.instance, query_encoded);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
new Request.GET ("/api/v2/search")
|
||||
.with_account (accounts.active)
|
||||
.with_param ("resolve", "true")
|
||||
.with_param ("q", Soup.URI.encode (query, null))
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
var accounts = root.get_array_member ("accounts");
|
||||
var statuses = root.get_array_member ("statuses");
|
||||
var hashtags = root.get_array_member ("hashtags");
|
||||
|
||||
clear ();
|
||||
|
||||
if (hashtags.get_length () > 0) {
|
||||
append_header (_("Hashtags"));
|
||||
hashtags.foreach_element ((array, i, node) => {
|
||||
append_hashtag (node.get_object ().get_string_member ("name"));
|
||||
});
|
||||
}
|
||||
|
||||
if (accounts.get_length () > 0) {
|
||||
append_header (_("Accounts"));
|
||||
accounts.foreach_element ((array, i, node) => {
|
||||
var obj = node.get_object ();
|
||||
var acc = API.Account.parse (obj);
|
||||
var acc = new API.Account (obj);
|
||||
append_account (acc);
|
||||
});
|
||||
}
|
||||
|
@ -91,20 +100,13 @@ public class Tootle.Views.Search : Views.Abstract {
|
|||
append_header (_("Statuses"));
|
||||
statuses.foreach_element ((array, i, node) => {
|
||||
var obj = node.get_object ();
|
||||
var status = API.Status.parse (obj);
|
||||
var status = new API.Status (obj);
|
||||
append_status (status);
|
||||
});
|
||||
}
|
||||
|
||||
if (hashtags.get_length () > 0) {
|
||||
append_header (_("Hashtags"));
|
||||
hashtags.foreach_element ((array, i, node) => {
|
||||
append_hashtag (node.get_string ());
|
||||
});
|
||||
}
|
||||
|
||||
empty_state ();
|
||||
});
|
||||
})
|
||||
.on_error (on_error)
|
||||
.exec ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
using Gtk;
|
||||
using Gdk;
|
||||
|
||||
public class Tootle.Views.Timeline : Views.Abstract {
|
||||
public class Tootle.Views.Timeline : Views.Base, IAccountListener, IStreamListener {
|
||||
|
||||
protected string timeline;
|
||||
protected string pars;
|
||||
public string timeline { get; construct set; }
|
||||
public bool is_public { get; construct set; default = false; }
|
||||
|
||||
protected InstanceAccount? account = null;
|
||||
protected int limit = 25;
|
||||
protected bool is_last_page = false;
|
||||
protected string? page_next;
|
||||
protected string? page_prev;
|
||||
protected string? stream;
|
||||
|
||||
protected Notificator? notificator;
|
||||
|
||||
public Timeline (string timeline, string pars = "") {
|
||||
base ();
|
||||
this.timeline = timeline;
|
||||
this.pars = pars;
|
||||
|
||||
accounts.switched.connect (on_account_changed);
|
||||
construct {
|
||||
app.refresh.connect (on_refresh);
|
||||
destroy.connect (() => {
|
||||
if (notificator != null)
|
||||
notificator.close ();
|
||||
});
|
||||
|
||||
setup_notificator ();
|
||||
request ();
|
||||
status_button.clicked.connect (on_refresh);
|
||||
connect_account ();
|
||||
}
|
||||
~Timeline () {
|
||||
streams.unsubscribe (stream, this);
|
||||
}
|
||||
|
||||
public override string get_icon () {
|
||||
|
@ -36,38 +30,32 @@ public class Tootle.Views.Timeline : Views.Abstract {
|
|||
return _("Home");
|
||||
}
|
||||
|
||||
public virtual void on_status_added (API.Status status) {
|
||||
public override void on_status_added (API.Status status) {
|
||||
prepend (status);
|
||||
}
|
||||
|
||||
public virtual bool is_status_owned (API.Status status) {
|
||||
return false;
|
||||
return status.is_owned ();
|
||||
}
|
||||
|
||||
public void prepend (API.Status status) {
|
||||
append (status, true);
|
||||
}
|
||||
|
||||
public void append (API.Status status, bool first = false){
|
||||
if (empty != null)
|
||||
empty.destroy ();
|
||||
|
||||
var separator = new Separator (Orientation.HORIZONTAL);
|
||||
separator.show ();
|
||||
|
||||
var widget = new Widgets.Status (status);
|
||||
widget.separator = separator;
|
||||
widget.button_press_event.connect (widget.open);
|
||||
public void append (API.Status status, bool first = false) {
|
||||
GLib.Idle.add (() => {
|
||||
var w = new Widgets.Status (status);
|
||||
w.button_press_event.connect (w.open);
|
||||
if (!is_status_owned (status))
|
||||
widget.avatar.button_press_event.connect (widget.on_avatar_clicked);
|
||||
view.pack_start (separator, false, false, 0);
|
||||
view.pack_start (widget, false, false, 0);
|
||||
w.avatar.button_press_event.connect (w.on_avatar_clicked);
|
||||
|
||||
if (first || status.pinned) {
|
||||
var new_index = header == null ? 1 : 0;
|
||||
view.reorder_child (separator, new_index);
|
||||
view.reorder_child (widget, new_index);
|
||||
}
|
||||
content.pack_start (w, false, false, 0);
|
||||
if (first || status.pinned)
|
||||
content.reorder_child (w, 0);
|
||||
|
||||
on_content_changed ();
|
||||
return GLib.Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
public override void clear () {
|
||||
|
@ -102,82 +90,51 @@ public class Tootle.Views.Timeline : Views.Abstract {
|
|||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/timelines/%s?limit=%i".printf (accounts.formal.instance, this.timeline, this.limit);
|
||||
url += this.pars;
|
||||
return url;
|
||||
return @"/api/v1/timelines/$timeline";
|
||||
}
|
||||
|
||||
public virtual void request (){
|
||||
if (accounts.current == null) {
|
||||
empty_state ();
|
||||
return;
|
||||
public virtual Request append_params (Request req) {
|
||||
return req.with_param ("limit", limit.to_string ());
|
||||
}
|
||||
|
||||
var msg = new Soup.Message ("GET", get_url ());
|
||||
network.inject (msg, Network.INJECT_TOKEN);
|
||||
network.queue (msg, (sess, mess) => {
|
||||
network.parse_array (mess).foreach_element ((array, i, node) => {
|
||||
var object = node.get_object ();
|
||||
if (object != null) {
|
||||
var status = API.Status.parse (object);
|
||||
public virtual bool request () {
|
||||
append_params (new Request.GET (get_url ()))
|
||||
.with_account (account)
|
||||
.then_parse_array ((node, msg) => {
|
||||
var obj = node.get_object ();
|
||||
if (obj != null) {
|
||||
var status = new API.Status (obj);
|
||||
append (status);
|
||||
}
|
||||
});
|
||||
get_pages (mess.response_headers.get_one ("Link"));
|
||||
empty_state ();
|
||||
},
|
||||
network.on_error);
|
||||
get_pages (msg.response_headers.get_one ("Link"));
|
||||
})
|
||||
.on_error (on_error)
|
||||
.exec ();
|
||||
|
||||
return GLib.Source.REMOVE;
|
||||
}
|
||||
|
||||
public virtual void on_refresh (){
|
||||
public virtual void on_refresh () {
|
||||
status_button.sensitive = false;
|
||||
clear ();
|
||||
request ();
|
||||
status_message = STATUS_LOADING;
|
||||
GLib.Idle.add (request);
|
||||
}
|
||||
|
||||
public virtual Soup.Message? get_stream (){
|
||||
public virtual string? get_stream_url () {
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void on_account_changed (API.Account? account){
|
||||
if(account == null)
|
||||
return;
|
||||
|
||||
var stream = get_stream ();
|
||||
if (notificator != null && stream != null) {
|
||||
var old_url = notificator.get_url ();
|
||||
var new_url = stream.get_uri ().to_string (false);
|
||||
if (old_url != new_url) {
|
||||
info ("Updating notificator %s", notificator.get_name ());
|
||||
setup_notificator ();
|
||||
}
|
||||
}
|
||||
|
||||
public override void on_account_changed (InstanceAccount? acc) {
|
||||
account = acc;
|
||||
streams.unsubscribe (stream, this);
|
||||
streams.subscribe (get_stream_url (), this, out stream);
|
||||
on_refresh ();
|
||||
}
|
||||
|
||||
protected void setup_notificator () {
|
||||
if (notificator != null)
|
||||
notificator.close ();
|
||||
|
||||
var stream = get_stream ();
|
||||
if (stream == null)
|
||||
return;
|
||||
|
||||
notificator = new Notificator (stream);
|
||||
notificator.status_added.connect ((status) => {
|
||||
if (can_stream ())
|
||||
on_status_added (status);
|
||||
});
|
||||
notificator.start ();
|
||||
}
|
||||
|
||||
protected virtual bool is_public () {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual bool can_stream () {
|
||||
protected override bool accepts (ref string event) {
|
||||
var allowed_public = true;
|
||||
if (is_public ())
|
||||
if (is_public)
|
||||
allowed_public = settings.live_updates_public;
|
||||
|
||||
return settings.live_updates && allowed_public;
|
||||
|
@ -185,7 +142,7 @@ public class Tootle.Views.Timeline : Views.Abstract {
|
|||
|
||||
protected override void on_bottom_reached () {
|
||||
if (is_last_page) {
|
||||
debug ("Last page reached");
|
||||
info ("Last page reached");
|
||||
return;
|
||||
}
|
||||
request ();
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
using GLib;
|
||||
using Gee;
|
||||
|
||||
public class Tootle.Watchlist : Object {
|
||||
|
||||
public ArrayList<string> users = new ArrayList<string> ();
|
||||
public ArrayList<string> hashtags = new ArrayList<string> ();
|
||||
public ArrayList<Notificator> notificators = new ArrayList<Notificator> ();
|
||||
|
||||
construct {
|
||||
accounts.switched.connect (on_account_changed);
|
||||
}
|
||||
|
||||
public Watchlist () {}
|
||||
|
||||
public virtual void on_account_changed (API.Account? account){
|
||||
if (account != null)
|
||||
reload ();
|
||||
}
|
||||
|
||||
private void reload () {
|
||||
info ("Reloading");
|
||||
|
||||
notificators.@foreach (notificator => {
|
||||
notificator.close ();
|
||||
return true;
|
||||
});
|
||||
notificators.clear ();
|
||||
users.clear ();
|
||||
hashtags.clear ();
|
||||
|
||||
load ();
|
||||
info ("Watching for %i users and %i hashtags", users.size, hashtags.size);
|
||||
}
|
||||
|
||||
private void load () {
|
||||
var users_array = settings.watched_users.split (",");
|
||||
foreach (string item in users_array)
|
||||
add (item, false);
|
||||
|
||||
var hashtags_array = settings.watched_hashtags.split (",");
|
||||
foreach (string item in hashtags_array)
|
||||
add (item, true);
|
||||
}
|
||||
|
||||
public void save () {
|
||||
var serialized_users = "";
|
||||
users.@foreach (item => {
|
||||
serialized_users += item + ",";
|
||||
return true;
|
||||
});
|
||||
serialized_users = remove_last_delimiter (serialized_users);
|
||||
settings.watched_users = serialized_users;
|
||||
|
||||
var serialized_hashtags = "";
|
||||
hashtags.@foreach (item => {
|
||||
serialized_hashtags += item + ",";
|
||||
return true;
|
||||
});
|
||||
serialized_hashtags = remove_last_delimiter (serialized_hashtags);
|
||||
settings.watched_hashtags = serialized_hashtags;
|
||||
|
||||
info ("Saved");
|
||||
}
|
||||
|
||||
private string remove_last_delimiter (string str) {
|
||||
var i = str.last_index_of (",");
|
||||
if (i > -1)
|
||||
return str.substring (0, i);
|
||||
else
|
||||
return str;
|
||||
}
|
||||
|
||||
private Notificator get_notificator (string hashtag) {
|
||||
var url = "%s/api/v1/streaming/?stream=hashtag&tag=%s&access_token=%s".printf (accounts.formal.instance, hashtag, accounts.formal.token);
|
||||
var msg = new Soup.Message ("GET", url);
|
||||
var notificator = new Notificator (msg);
|
||||
notificator.status_added.connect (on_status_added);
|
||||
return notificator;
|
||||
}
|
||||
|
||||
private void on_status_added (API.Status status) {
|
||||
var obj = new API.Notification (-1);
|
||||
obj.type = API.NotificationType.WATCHLIST;
|
||||
obj.account = status.account;
|
||||
obj.status = status;
|
||||
accounts.formal.notification (obj);
|
||||
}
|
||||
|
||||
public void add (string entity, bool is_hashtag) {
|
||||
if (entity == "")
|
||||
return;
|
||||
|
||||
if (is_hashtag) {
|
||||
hashtags.add (entity);
|
||||
var notificator = get_notificator (entity);
|
||||
notificator.start ();
|
||||
notificators.add (notificator);
|
||||
info ("Added #%s", entity);
|
||||
}
|
||||
else {
|
||||
users.add (entity);
|
||||
info ("Added @%s", entity);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove (string entity, bool is_hashtag) {
|
||||
if (entity == "")
|
||||
return;
|
||||
|
||||
if (is_hashtag) {
|
||||
var i = hashtags.index_of (entity);
|
||||
var notificator = notificators.@get(i);
|
||||
notificator.close ();
|
||||
notificators.remove_at (i);
|
||||
hashtags.remove (entity);
|
||||
info ("Removed #%s", entity);
|
||||
}
|
||||
else {
|
||||
users.remove (entity);
|
||||
info ("Removed @%s", entity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -5,15 +5,15 @@ public class Tootle.Widgets.Account : Widgets.Status {
|
|||
public Account (API.Account account) {
|
||||
var status = new API.Status (-1);
|
||||
status.account = account;
|
||||
status.url = account.url;
|
||||
status.content = "<a href=\"%s\">@%s</a>".printf (account.url, account.acct);
|
||||
status.created_at = account.created_at;
|
||||
//status.url = account.url;
|
||||
//status.content = "<a href=\"%s\">@%s</a>".printf (account.url, account.acct);
|
||||
//status.created_at = account.created_at;
|
||||
|
||||
base (status);
|
||||
|
||||
counters.visible = false;
|
||||
title_acct.visible = false;
|
||||
content_label.margin_bottom = 12;
|
||||
//counters.visible = false;
|
||||
//title_acct.visible = false;
|
||||
//content_label.margin_bottom = 12;
|
||||
}
|
||||
|
||||
protected override bool on_clicked (EventButton ev) {
|
||||
|
|
|
@ -1,168 +1,149 @@
|
|||
using Gtk;
|
||||
|
||||
public class Tootle.Widgets.AccountsButton : MenuButton {
|
||||
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/accounts_button.ui")]
|
||||
public class Tootle.Widgets.AccountsButton : Gtk.MenuButton, IAccountListener {
|
||||
|
||||
const int AVATAR_SIZE = 24;
|
||||
Granite.Widgets.Avatar avatar;
|
||||
Grid grid;
|
||||
Popover menu;
|
||||
ListBox list;
|
||||
ModelButton item_settings;
|
||||
ModelButton item_refresh;
|
||||
ModelButton item_search;
|
||||
ModelButton item_favs;
|
||||
ModelButton item_direct;
|
||||
ModelButton item_watchlist;
|
||||
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/accounts_button_item.ui")]
|
||||
private class Item : Grid {
|
||||
[GtkChild]
|
||||
private Widgets.Avatar avatar;
|
||||
[GtkChild]
|
||||
private Label name;
|
||||
[GtkChild]
|
||||
private Label handle;
|
||||
[GtkChild]
|
||||
private Button profile;
|
||||
[GtkChild]
|
||||
private Button remove;
|
||||
|
||||
private class AccountItemView : ListBoxRow {
|
||||
public Item (InstanceAccount acc, AccountsButton _self) {
|
||||
avatar.url = acc.avatar;
|
||||
name.label = acc.display_name;
|
||||
handle.label = acc.handle;
|
||||
|
||||
private Grid grid;
|
||||
public Label display_name;
|
||||
public Label instance;
|
||||
public Button button;
|
||||
public int id = -1;
|
||||
profile.clicked.connect (() => {
|
||||
Views.Profile.open_from_id (acc.id);
|
||||
_self.active = false;
|
||||
});
|
||||
|
||||
remove.clicked.connect (() => {
|
||||
_self.active = false;
|
||||
accounts.remove (acc);
|
||||
});
|
||||
}
|
||||
|
||||
public Item.add_new () {
|
||||
name.label = _("New Account");
|
||||
handle.label = _("Click to add");
|
||||
profile.destroy ();
|
||||
remove.destroy ();
|
||||
}
|
||||
}
|
||||
|
||||
private bool invalidated = true;
|
||||
|
||||
[GtkChild]
|
||||
private Widgets.Avatar avatar;
|
||||
[GtkChild]
|
||||
private Spinner spinner;
|
||||
|
||||
[GtkChild]
|
||||
private ListBox account_list;
|
||||
|
||||
[GtkChild]
|
||||
private ModelButton item_accounts;
|
||||
[GtkChild]
|
||||
private ModelButton item_prefs;
|
||||
[GtkChild]
|
||||
private ModelButton item_refresh;
|
||||
[GtkChild]
|
||||
private ModelButton item_search;
|
||||
[GtkChild]
|
||||
private ModelButton item_favs;
|
||||
[GtkChild]
|
||||
private ModelButton item_direct;
|
||||
[GtkChild]
|
||||
private ModelButton item_watchlist;
|
||||
|
||||
construct {
|
||||
can_default = false;
|
||||
connect_account ();
|
||||
|
||||
grid = new Grid ();
|
||||
grid.margin = 6;
|
||||
grid.margin_start = 14;
|
||||
|
||||
display_name = new Label ("");
|
||||
display_name.hexpand = true;
|
||||
display_name.halign = Align.START;
|
||||
display_name.use_markup = true;
|
||||
instance = new Label ("");
|
||||
instance.halign = Align.START;
|
||||
button = new Button.from_icon_name ("window-close-symbolic", IconSize.SMALL_TOOLBAR);
|
||||
button.receives_default = false;
|
||||
button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||
|
||||
grid.attach (display_name, 1, 0, 1, 1);
|
||||
grid.attach (instance, 1, 1, 1, 1);
|
||||
grid.attach (button, 2, 0, 2, 2);
|
||||
add (grid);
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public AccountItemView (){
|
||||
button.clicked.connect (() => accounts.remove (id));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
construct{
|
||||
avatar = new Granite.Widgets.Avatar.with_default_icon (AVATAR_SIZE);
|
||||
list = new ListBox ();
|
||||
|
||||
var item_separator = new Separator (Orientation.HORIZONTAL);
|
||||
item_separator.hexpand = true;
|
||||
|
||||
item_refresh = new ModelButton ();
|
||||
item_refresh.text = _("Refresh");
|
||||
item_refresh.clicked.connect (() => app.refresh ());
|
||||
Desktop.set_hotkey_tooltip (item_refresh, null, app.ACCEL_REFRESH);
|
||||
|
||||
item_favs = new ModelButton ();
|
||||
item_favs.text = _("Favorites");
|
||||
item_favs.clicked.connect (() => window.open_view (new Views.Favorites ()));
|
||||
|
||||
item_direct = new ModelButton ();
|
||||
item_direct.text = _("Direct Messages");
|
||||
item_direct.clicked.connect (() => window.open_view (new Views.Direct ()));
|
||||
|
||||
item_search = new ModelButton ();
|
||||
item_search.text = _("Search");
|
||||
item_search.clicked.connect (() => window.open_view (new Views.Search ()));
|
||||
//item_watchlist.clicked.connect (() => Dialogs.WatchlistEditor.open ());
|
||||
item_prefs.clicked.connect (() => Dialogs.Preferences.open ());
|
||||
|
||||
item_watchlist = new ModelButton ();
|
||||
item_watchlist.text = _("Watchlist");
|
||||
item_watchlist.clicked.connect (() => Dialogs.WatchlistEditor.open ());
|
||||
// network.started.connect (() => spinner.show ());
|
||||
// network.finished.connect (() => spinner.hide ());
|
||||
|
||||
item_settings = new ModelButton ();
|
||||
item_settings.text = _("Settings");
|
||||
item_settings.clicked.connect (() => Dialogs.Preferences.open ());
|
||||
on_account_changed (null);
|
||||
|
||||
grid = new Grid ();
|
||||
grid.orientation = Orientation.VERTICAL;
|
||||
grid.width_request = 200;
|
||||
grid.attach (list, 0, 1, 1, 1);
|
||||
grid.attach (item_separator, 0, 3, 1, 1);
|
||||
grid.attach (item_favs, 0, 4, 1, 1);
|
||||
grid.attach (item_direct, 0, 5, 1, 1);
|
||||
grid.attach (new Separator (Orientation.HORIZONTAL), 0, 6, 1, 1);
|
||||
grid.attach (item_refresh, 0, 7, 1, 1);
|
||||
grid.attach (item_search, 0, 8, 1, 1);
|
||||
grid.attach (item_watchlist, 0, 9, 1, 1);
|
||||
grid.attach (item_settings, 0, 10, 1, 1);
|
||||
grid.show_all ();
|
||||
notify["active"].connect (() => {
|
||||
if (active && invalidated)
|
||||
rebuild ();
|
||||
});
|
||||
|
||||
menu = new Popover (null);
|
||||
menu.add (grid);
|
||||
account_list.row_activated.connect (on_selection_changed) ;
|
||||
}
|
||||
|
||||
get_style_context ().add_class ("button_avatar");
|
||||
popover = menu;
|
||||
add (avatar);
|
||||
show_all ();
|
||||
|
||||
accounts.updated.connect (accounts_updated);
|
||||
accounts.switched.connect (account_switched);
|
||||
list.row_activated.connect (row => {
|
||||
var widget = row as AccountItemView;
|
||||
if (widget.id == -1) {
|
||||
Dialogs.NewAccount.open ();
|
||||
protected void on_selection_changed (ListBoxRow r) {
|
||||
var i = r.get_index ();
|
||||
if (i >= accounts.saved.size) {
|
||||
active = false;
|
||||
window.open_view (new Views.NewAccount (true));
|
||||
return;
|
||||
}
|
||||
if (widget.id == settings.current_account)
|
||||
Views.Profile.open_from_id (accounts.current.id);
|
||||
else
|
||||
accounts.switch_account (widget.id);
|
||||
|
||||
menu.popdown ();
|
||||
var account = accounts.saved.@get (i);
|
||||
if (accounts.active == account)
|
||||
return;
|
||||
|
||||
accounts.switch_account (i);
|
||||
}
|
||||
|
||||
public virtual void on_accounts_changed (Gee.ArrayList<InstanceAccount> accounts) {
|
||||
invalidated = true;
|
||||
if (active)
|
||||
rebuild ();
|
||||
}
|
||||
|
||||
public virtual void on_account_changed (InstanceAccount? account) {
|
||||
if (account == null) {
|
||||
avatar.url = null;
|
||||
item_accounts.text = "<b>" + _("No active account") + "</b>";
|
||||
}
|
||||
else {
|
||||
avatar.url = account.avatar;
|
||||
item_accounts.text = @"<b>$(account.display_name)</b>\n$(account.handle) ";
|
||||
}
|
||||
item_accounts.use_markup = true;
|
||||
}
|
||||
|
||||
private void rebuild () {
|
||||
account_list.@foreach (w => account_list.remove (w));
|
||||
accounts.saved.@foreach (acc => {
|
||||
var item = new Item (acc, this);
|
||||
var row = new ListBoxRow ();
|
||||
row.add (item);
|
||||
row.show ();
|
||||
|
||||
account_list.insert (row, -1);
|
||||
if (accounts.active == acc)
|
||||
row.activate ();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
var new_row = new ListBoxRow ();
|
||||
new_row.add (new Item.add_new ());
|
||||
new_row.selectable = false;
|
||||
new_row.show ();
|
||||
account_list.insert (new_row, -1);
|
||||
|
||||
private void accounts_updated (GenericArray<InstanceAccount> accounts) {
|
||||
list.forall (widget => widget.destroy ());
|
||||
int i = -1;
|
||||
accounts.foreach (account => {
|
||||
i++;
|
||||
var widget = new AccountItemView ();
|
||||
widget.id = i;
|
||||
widget.display_name.label = "<b>@"+account.username+"</b>";
|
||||
widget.instance.label = account.get_pretty_instance ();
|
||||
list.add (widget);
|
||||
});
|
||||
|
||||
var add_account = new AccountItemView ();
|
||||
add_account.display_name.label = _("<b>New Account</b>");
|
||||
add_account.instance.label = _("Click to add");
|
||||
add_account.button.hide ();
|
||||
list.add (add_account);
|
||||
update_selection ();
|
||||
}
|
||||
|
||||
private void account_switched (API.Account? account) {
|
||||
if (account == null)
|
||||
avatar.show_default (AVATAR_SIZE);
|
||||
else
|
||||
network.load_avatar (account.avatar, avatar, get_avatar_size ());
|
||||
}
|
||||
|
||||
private void update_selection () {
|
||||
var id = settings.current_account;
|
||||
var row = list.get_row_at_index (id);
|
||||
if (row != null)
|
||||
list.select_row (row);
|
||||
}
|
||||
|
||||
public int get_avatar_size () {
|
||||
return AVATAR_SIZE * get_style_context ().get_scale ();
|
||||
}
|
||||
|
||||
public AccountsButton () {
|
||||
account_switched (accounts.current);
|
||||
invalidated = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
using Gtk;
|
||||
using GLib;
|
||||
using Gee;
|
||||
|
||||
public class Tootle.Widgets.Attachment.Box : FlowBox {
|
||||
|
||||
public bool editing { get; construct set; }
|
||||
|
||||
construct {
|
||||
hexpand = true;
|
||||
can_focus = false;
|
||||
selection_mode = SelectionMode.NONE;
|
||||
}
|
||||
|
||||
public Box (bool editing = false) {
|
||||
Object (editing: editing);
|
||||
}
|
||||
|
||||
public void select () {
|
||||
var filter = new Gtk.FileFilter ();
|
||||
filter.add_mime_type ("image/jpeg");
|
||||
filter.add_mime_type ("image/png");
|
||||
filter.add_mime_type ("image/gif");
|
||||
filter.add_mime_type ("video/webm");
|
||||
filter.add_mime_type ("video/mp4");
|
||||
|
||||
var chooser = new Gtk.FileChooserDialog (
|
||||
_("Select media files to add"),
|
||||
null,
|
||||
Gtk.FileChooserAction.OPEN,
|
||||
_("_Cancel"),
|
||||
Gtk.ResponseType.CANCEL,
|
||||
_("_Open"),
|
||||
Gtk.ResponseType.ACCEPT);
|
||||
|
||||
chooser.select_multiple = true;
|
||||
chooser.set_filter (filter);
|
||||
|
||||
if (chooser.run () == ResponseType.ACCEPT) {
|
||||
show ();
|
||||
foreach (unowned string uri in chooser.get_uris ()) {
|
||||
//var widget = new ImageAttachment.upload (uri);
|
||||
//append_widget (widget);
|
||||
}
|
||||
}
|
||||
chooser.close ();
|
||||
}
|
||||
|
||||
public bool populate (ArrayList<API.Attachment>? list) {
|
||||
if (list == null)
|
||||
return false;
|
||||
|
||||
var max = 6;
|
||||
if (list.size % 2 == 0)
|
||||
max = 2;
|
||||
|
||||
//max_children_per_line = (int)Math.fmin (list.size, 5);
|
||||
max_children_per_line = max;
|
||||
list.@foreach (obj => pack (obj));
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool pack (API.Attachment obj) {
|
||||
var w = new Widgets.Attachment.Item (obj);
|
||||
insert (w, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|