diff --git a/data/gresource.xml b/data/gresource.xml index be8c1ded9..8e04581a5 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -56,6 +56,9 @@ icons/scalable/actions/tuba-markdown-symbolic.svg icons/scalable/actions/tuba-rich-text-symbolic.svg icons/scalable/actions/tuba-earth-symbolic.svg + icons/scalable/actions/tuba-bear-symbolic.svg + icons/scalable/actions/tuba-image-round-symbolic.svg + icons/scalable/actions/tuba-sentiment-satisfied-symbolic.svg icons/scalable/actions/tuba-verified-checkmark-symbolic.svg icons/scalable/actions/tuba-funnel-symbolic.svg icons/scalable/actions/tuba-error-symbolic.svg @@ -88,6 +91,7 @@ ui/dialogs/list_edit.ui ui/dialogs/new_account.ui ui/dialogs/compose.ui + ui/dialogs/new_composer.ui ui/dialogs/main.ui ui/dialogs/preferences.ui ui/dialogs/profile_edit.ui diff --git a/data/gtk/dropdown/icon.ui b/data/gtk/dropdown/icon.ui index a7f616c57..41a6ece58 100644 --- a/data/gtk/dropdown/icon.ui +++ b/data/gtk/dropdown/icon.ui @@ -1,13 +1,29 @@ - diff --git a/data/icons/scalable/actions/tuba-bear-symbolic.svg b/data/icons/scalable/actions/tuba-bear-symbolic.svg new file mode 100644 index 000000000..253250365 --- /dev/null +++ b/data/icons/scalable/actions/tuba-bear-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/scalable/actions/tuba-image-round-symbolic.svg b/data/icons/scalable/actions/tuba-image-round-symbolic.svg new file mode 100644 index 000000000..bc45f2ef6 --- /dev/null +++ b/data/icons/scalable/actions/tuba-image-round-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/scalable/actions/tuba-sentiment-satisfied-symbolic.svg b/data/icons/scalable/actions/tuba-sentiment-satisfied-symbolic.svg new file mode 100644 index 000000000..6bf2ccf52 --- /dev/null +++ b/data/icons/scalable/actions/tuba-sentiment-satisfied-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/style.css b/data/style.css index d4761799d..4e71ef8d4 100644 --- a/data/style.css +++ b/data/style.css @@ -658,6 +658,18 @@ GtkSourceAssistant row:last-child { font-size: small; } +.accented-color { + color: @accent_color; +} + +.font-large { + font-size: large; +} + +.dropdown-border-radius > button { + border-radius: 9999px; +} + .filter { margin: -2px; border-radius: 0; diff --git a/data/ui/dialogs/new_composer.ui b/data/ui/dialogs/new_composer.ui new file mode 100644 index 000000000..c7e7cb602 --- /dev/null +++ b/data/ui/dialogs/new_composer.ui @@ -0,0 +1,163 @@ + + + + + + \ No newline at end of file diff --git a/src/Application.vala b/src/Application.vala index 32288c8e2..14da46f1c 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -356,7 +356,7 @@ namespace Tuba { void compose_activated () { if (accounts.active.instance_info == null) return; - new Dialogs.Compose (); + new Dialogs.NewCompose (); } void back_activated () { diff --git a/src/Dialogs/Composer/NewDialog.vala b/src/Dialogs/Composer/NewDialog.vala new file mode 100644 index 000000000..b55dca85b --- /dev/null +++ b/src/Dialogs/Composer/NewDialog.vala @@ -0,0 +1,218 @@ +[GtkTemplate (ui = "/dev/geopjr/Tuba/ui/dialogs/new_composer.ui")] +public class Tuba.Dialogs.NewCompose : Adw.Dialog { + [GtkChild] private unowned Gtk.Label counter_label; + [GtkChild] private unowned Gtk.Button post_btn; + [GtkChild] private unowned Gtk.Box btns_box; + [GtkChild] private unowned Gtk.Box dropdowns_box; + [GtkChild] private unowned Gtk.Grid grid; + + [GtkChild] private unowned Gtk.Box status_box; + [GtkChild] private unowned Gtk.Box main_box; + [GtkChild] private unowned Gtk.Label status_title; + + [GtkChild] private unowned Gtk.MenuButton native_emojis_button; + [GtkChild] private unowned Gtk.MenuButton custom_emojis_button; + + private bool _is_narrow = false; + public bool is_narrow { + get { + return _is_narrow; + } + set { + Gtk.GridLayout layout_manager = (Gtk.GridLayout) grid.get_layout_manager (); + Gtk.GridLayoutChild counter_layout_child = (Gtk.GridLayoutChild) layout_manager.get_layout_child (counter_label); + Gtk.GridLayoutChild post_layout_child = (Gtk.GridLayoutChild) layout_manager.get_layout_child (post_btn); + Gtk.GridLayoutChild btns_layout_child = (Gtk.GridLayoutChild) layout_manager.get_layout_child (btns_box); + + if (value) { + post_layout_child.column = 1; + post_layout_child.row = 1; + post_layout_child.row_span = 1; + + counter_layout_child.row = 0; + counter_layout_child.column = 1; + + btns_layout_child.column_span = 1; + + counter_label.margin_end = 10; + counter_label.margin_start = 0; + grid.row_spacing = 12; + grid.margin_start = 16; + grid.margin_end = 16; + } else { + post_layout_child.column = 2; + post_layout_child.row = 0; + post_layout_child.row_span = 2; + + counter_layout_child.row = 1; + counter_layout_child.column = 1; + + btns_layout_child.column_span = 2; + + counter_label.margin_end = 0; + counter_label.margin_start = 12; + grid.row_spacing = 16; + grid.margin_start = 32; + grid.margin_end = 32; + } + + _is_narrow = value; + } + } + protected int64 char_limit { get; set; default = 500; } + + private int64 _remaining_chars = 500; + protected int64 remaining_chars { + get { + return _remaining_chars; + } + set { + _remaining_chars = value; + counter_label.label = counter_label.tooltip_text = @"$value / $char_limit"; + + if (value < 0) { + counter_label.add_css_class ("error"); + counter_label.remove_css_class ("accented-color"); + } else { + counter_label.remove_css_class ("error"); + counter_label.add_css_class ("accented-color"); + } + } + } + + private void install_emoji_pickers () { + var emoji_picker = new Gtk.EmojiChooser (); + native_emojis_button.popover = emoji_picker; + emoji_picker.emoji_picked.connect (editor.insert_string_at_cursor); + + if (accounts.active.instance_emojis?.size > 0) { + var custom_emoji_picker = new Widgets.CustomEmojiChooser (); + custom_emojis_button.popover = custom_emoji_picker; + custom_emoji_picker.emoji_picked.connect (editor.insert_string_at_cursor); + } + } + + private Componenets.Editor editor; + private void install_editor () { + editor = new Dialogs.Componenets.Editor (); + main_box.append (editor); + + editor.notify["char-count"].connect (update_remaining_chars); + this.focus_widget = editor; + } + + private void update_remaining_chars () { + int64 res = char_limit; + res -= editor.char_count; + remaining_chars = res; + } + + protected Gtk.DropDown visibility_button; + protected Gtk.DropDown language_button; + + private void append_dropdown (Gtk.DropDown dropdown) { + var togglebtn = dropdown.get_first_child (); + if (togglebtn != null) { + togglebtn.add_css_class ("flat"); + } + + dropdowns_box.append (dropdown); + } + + protected void install_visibility (string default_visibility = settings.default_post_visibility) { + visibility_button = new Gtk.DropDown (accounts.active.visibility_list, null) { + expression = new Gtk.PropertyExpression (typeof (InstanceAccount.Visibility), null, "name"), + factory = new Gtk.BuilderListItemFactory.from_resource (null, @"$(Build.RESOURCES)gtk/dropdown/icon.ui"), + list_factory = new Gtk.BuilderListItemFactory.from_resource (null, @"$(Build.RESOURCES)gtk/dropdown/full.ui"), + tooltip_text = _("Post Privacy"), + valign = Gtk.Align.CENTER + }; + visibility_button.add_css_class ("dropdown-border-radius"); + + var safe_visibility = accounts.active.visibility.has_key (default_visibility) ? default_visibility : "public"; + uint default_visibility_index; + if ( + accounts.active.visibility_list.find ( + accounts.active.visibility[safe_visibility], + out default_visibility_index + ) + ) { + visibility_button.selected = default_visibility_index; + } + + append_dropdown (visibility_button); + } + + private void install_languages (string? locale_iso = null) { + language_button = new Gtk.DropDown (app.app_locales.list_store, null) { + expression = new Gtk.PropertyExpression (typeof (Tuba.Locales.Locale), null, "name"), + factory = new Gtk.BuilderListItemFactory.from_resource (null, @"$(Build.RESOURCES)gtk/dropdown/language_title.ui"), + list_factory = new Gtk.BuilderListItemFactory.from_resource (null, @"$(Build.RESOURCES)gtk/dropdown/language.ui"), + tooltip_text = _("Post Language"), + enable_search = true, + valign = Gtk.Align.CENTER + }; + language_button.add_css_class ("dropdown-border-radius"); + + if (locale_iso != null) { + uint default_lang_index; + if ( + app.app_locales.list_store.find_with_equal_func ( + new Tuba.Locales.Locale (locale_iso, null, null), + Tuba.Locales.Locale.compare, + out default_lang_index + ) + ) { + language_button.selected = default_lang_index; + } + } + + append_dropdown (language_button); + } + + construct { + var condition = new Adw.BreakpointCondition.length ( + Adw.BreakpointConditionLengthType.MAX_WIDTH, + 400, Adw.LengthUnit.SP + ); + var breakpoint = new Adw.Breakpoint (condition); + breakpoint.add_setter (this, "is-narrow", true); + add_breakpoint (breakpoint); + + var char_limit_api = accounts.active.instance_info.compat_status_max_characters; + if (char_limit_api > 0) + char_limit = char_limit_api; + + install_editor (); + install_emoji_pickers (); + install_visibility (); + install_languages (); + + update_remaining_chars (); + present (app.main_window); + } + + public NewCompose (API.Status template = new API.Status.empty ()) { + Object (); + } + + public NewCompose.reply (API.Status to) { + Object (); + + try { + Widgets.Status widget_status = (Widgets.Status?) to.to_widget (); + widget_status.add_css_class ("card"); + widget_status.actions.visible = false; + widget_status.menu_button.visible = false; + widget_status.activatable = false; + widget_status.can_target = false; + widget_status.can_focus = false; + + status_box.insert_child_after (widget_status, status_title); + } catch (Error e) { + warning (@"Couldn't create status widget: $(e.message)"); + } + + status_title.label = _("Reply to %s").printf (to.account.handle); + } +} diff --git a/src/Dialogs/Composer/NewDialogEditor.vala b/src/Dialogs/Composer/NewDialogEditor.vala new file mode 100644 index 000000000..1e6d9d603 --- /dev/null +++ b/src/Dialogs/Composer/NewDialogEditor.vala @@ -0,0 +1,103 @@ +public class Tuba.Dialogs.Componenets.Editor : Adw.Bin { + public int64 char_count { get; private set; default = 0; } + public string content { + owned get { + return editor.buffer.text; + } + set { + editor.buffer.text = value; + } + } + + public void insert_string_at_cursor (string text) { + editor.buffer.insert_at_cursor (text, text.data.length); + } + + protected Gtk.Overlay overlay; + protected Gtk.Label placeholder; + protected GtkSource.View editor; + + private void count_chars () { + int64 res = 0; + + // if (cw_button.active) + // res += (int64) cw_entry.buffer.length; + res += editor.buffer.get_char_count (); + + char_count = res; + } + + private void on_content_changed () { + editor.show_completion (); + count_chars (); + placeholder.visible = char_count == 0; + } + + construct { + install_editor (); + install_overlay (); + + child = overlay; + } + + private void install_editor () { + editor = new GtkSource.View () { + vexpand = true, + hexpand = true, + top_margin = 6, + right_margin = 6, + bottom_margin = 6, + left_margin = 6, + pixels_below_lines = 6, + accepts_tab = false, + wrap_mode = Gtk.WrapMode.WORD_CHAR, + tab_width = 1, + // TODO: remove when other componenets are enabled + height_request = 100 + }; + editor.remove_css_class ("view"); + editor.add_css_class ("font-large"); + + #if LIBSPELLING + var adapter = new Spelling.TextBufferAdapter ((GtkSource.Buffer) editor.buffer, Spelling.Checker.get_default ()); + + editor.extra_menu = adapter.get_menu_model (); + editor.insert_action_group ("spelling", adapter); + adapter.enabled = true; + #endif + + editor.completion.add_provider (new Tuba.HandleProvider ()); + editor.completion.add_provider (new Tuba.HashtagProvider ()); + editor.completion.add_provider (new Tuba.EmojiProvider ()); + editor.completion.select_on_show = true; + editor.completion.show_icons = true; + editor.completion.page_size = 3; + update_editor_style_scheme (); + + editor.buffer.changed.connect (on_content_changed); + } + + protected void update_editor_style_scheme () { + var manager = GtkSource.StyleSchemeManager.get_default (); + var scheme = manager.get_scheme ("adwaita"); + var buffer = editor.buffer as GtkSource.Buffer; + buffer.style_scheme = scheme; + } + + private void install_overlay () { + overlay = new Gtk.Overlay (); + placeholder = new Gtk.Label (_("What's on your mind?")) { + valign = Gtk.Align.START, + halign = Gtk.Align.START, + justify = Gtk.Justification.FILL, + margin_top = 6, + margin_start = 6, + wrap = true, + sensitive = false, + css_classes = {"font-large"} + }; + + overlay.add_overlay (placeholder); + overlay.child = editor; + } +} diff --git a/src/Dialogs/Composer/meson.build b/src/Dialogs/Composer/meson.build index bab710a6f..dc8e9b65d 100644 --- a/src/Dialogs/Composer/meson.build +++ b/src/Dialogs/Composer/meson.build @@ -3,6 +3,8 @@ sources += files( 'AttachmentsPageAttachment.vala', 'Dialog.vala', 'EditorPage.vala', + 'NewDialog.vala', + 'NewDialogEditor.vala', 'Page.vala', 'PollPage.vala', ) diff --git a/src/Widgets/Status.vala b/src/Widgets/Status.vala index f09f3f441..e1ea5fd5d 100644 --- a/src/Widgets/Status.vala +++ b/src/Widgets/Status.vala @@ -651,7 +651,8 @@ } private void on_reply_button_clicked () { - new Dialogs.Compose.reply (status.formal, on_reply); + // new Dialogs.Compose.reply (status.formal, on_reply); + new Dialogs.NewCompose.reply (status.formal); } [GtkCallback] public void toggle_spoiler () {