From 8c55364afad0ba598f53b4fd592d0b86db82addf Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 17 Jul 2024 10:37:14 +0200 Subject: [PATCH] Allow editing business location in Settings. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/calls/calls_call.cpp | 2 - .../data/business/data_business_info.cpp | 36 +++ .../data/business/data_business_info.h | 1 + Telegram/SourceFiles/data/data_location.cpp | 5 + Telegram/SourceFiles/data/data_location.h | 7 +- .../inline_bots/bot_attach_web_view.cpp | 1 + .../settings/business/settings_chat_intro.cpp | 1 - .../settings/business/settings_location.cpp | 208 ++++++++++++++++-- .../ui/controls/location_picker.cpp | 33 ++- .../SourceFiles/ui/controls/location_picker.h | 3 + 11 files changed, 266 insertions(+), 33 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fc725ff1d..c692195bc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2387,6 +2387,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_location_title" = "Location"; "lng_location_about" = "Display the location of your business on your account."; "lng_location_address" = "Enter Address"; +"lng_location_set_map" = "Set Location on Map"; "lng_location_fallback" = "You can set your location on the map from your mobile device."; "lng_hours_title" = "Business Hours"; @@ -3195,6 +3196,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_maps_point" = "Location"; "lng_maps_point_send" = "Send This Location"; +"lng_maps_point_set" = "Set This Location"; "lng_maps_or_choose" = "Or choose a venue"; "lng_maps_places_in_area" = "Places in this area"; "lng_maps_no_places" = "No places found"; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 9f1272859..0ca37c629 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -39,7 +39,6 @@ namespace tgcalls { class InstanceImpl; class InstanceV2Impl; class InstanceV2ReferenceImpl; -class InstanceV2_4_0_0Impl; class InstanceImplLegacy; void SetLegacyGlobalServerConfig(const std::string &serverConfig); } // namespace tgcalls @@ -56,7 +55,6 @@ const auto kDefaultVersion = "2.4.4"_q; const auto Register = tgcalls::Register(); const auto RegisterV2 = tgcalls::Register(); const auto RegV2Ref = tgcalls::Register(); -const auto RegisterV240 = tgcalls::Register(); const auto RegisterLegacy = tgcalls::Register(); [[nodiscard]] base::flat_set CollectEndpointIds( diff --git a/Telegram/SourceFiles/data/business/data_business_info.cpp b/Telegram/SourceFiles/data/business/data_business_info.cpp index e158c7a3a..d9bb6ec7a 100644 --- a/Telegram/SourceFiles/data/business/data_business_info.cpp +++ b/Telegram/SourceFiles/data/business/data_business_info.cpp @@ -130,6 +130,42 @@ void BusinessInfo::saveChatIntro(ChatIntro data, Fn fail) { session->user()->setBusinessDetails(std::move(details)); } +void BusinessInfo::saveLocation( + BusinessLocation data, + Fn fail) { + const auto session = &_owner->session(); + auto details = session->user()->businessDetails(); + const auto &was = details.location; + if (was == data) { + return; + } else { + const auto session = &_owner->session(); + using Flag = MTPaccount_UpdateBusinessLocation::Flag; + session->api().request(MTPaccount_UpdateBusinessLocation( + MTP_flags((data.point ? Flag::f_geo_point : Flag()) + | (data.address.isEmpty() ? Flag() : Flag::f_address)), + (data.point + ? MTP_inputGeoPoint( + MTP_flags(0), + MTP_double(data.point->lat()), + MTP_double(data.point->lon()), + MTPint()) // accuracy_radius + : MTP_inputGeoPointEmpty()), + MTP_string(data.address) + )).fail([=](const MTP::Error &error) { + auto details = session->user()->businessDetails(); + details.location = was; + session->user()->setBusinessDetails(std::move(details)); + if (fail) { + fail(error.type()); + } + }).send(); + } + + details.location = std::move(data); + session->user()->setBusinessDetails(std::move(details)); +} + void BusinessInfo::applyAwaySettings(AwaySettings data) { if (_awaySettings == data) { return; diff --git a/Telegram/SourceFiles/data/business/data_business_info.h b/Telegram/SourceFiles/data/business/data_business_info.h index 7b99e4c8f..01993f223 100644 --- a/Telegram/SourceFiles/data/business/data_business_info.h +++ b/Telegram/SourceFiles/data/business/data_business_info.h @@ -22,6 +22,7 @@ public: void saveWorkingHours(WorkingHours data, Fn fail); void saveChatIntro(ChatIntro data, Fn fail); + void saveLocation(BusinessLocation data, Fn fail); void saveAwaySettings(AwaySettings data, Fn fail); void applyAwaySettings(AwaySettings data); diff --git a/Telegram/SourceFiles/data/data_location.cpp b/Telegram/SourceFiles/data/data_location.cpp index 80727b3ba..457bb6411 100644 --- a/Telegram/SourceFiles/data/data_location.cpp +++ b/Telegram/SourceFiles/data/data_location.cpp @@ -26,6 +26,11 @@ LocationPoint::LocationPoint(const MTPDgeoPoint &point) , _access(point.vaccess_hash().v) { } +LocationPoint::LocationPoint(float64 lat, float64 lon, IgnoreAccessHash) +: _lat(lat) +, _lon(lon) { +} + QString LocationPoint::latAsString() const { return AsString(_lat); } diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h index 5157f79a1..114d21c08 100644 --- a/Telegram/SourceFiles/data/data_location.h +++ b/Telegram/SourceFiles/data/data_location.h @@ -16,6 +16,11 @@ public: LocationPoint() = default; explicit LocationPoint(const MTPDgeoPoint &point); + enum IgnoreAccessHash { + NoAccessHash, + }; + LocationPoint(float64 lat, float64 lon, IgnoreAccessHash); + [[nodiscard]] QString latAsString() const; [[nodiscard]] QString lonAsString() const; [[nodiscard]] MTPGeoPoint toMTP() const; @@ -55,7 +60,7 @@ struct InputVenue { QString venueType; [[nodiscard]] bool justLocation() const { - return id.isEmpty() && title.isEmpty() && address.isEmpty(); + return id.isEmpty(); } friend inline bool operator==( diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 4dbd94e1d..c42ecb3b9 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1822,6 +1822,7 @@ void ChooseAndSendLocation( Ui::LocationPicker::Show({ .parent = controller->widget(), .config = config, + .chooseLabel = tr::lng_maps_point_send(), .recipient = action.history->peer, .session = &controller->session(), .callback = crl::guard(controller, callback), diff --git a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp index e9bc2f73e..688a6940d 100644 --- a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp +++ b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp @@ -632,7 +632,6 @@ void ChatIntro::setupContent( } void ChatIntro::save() { - const auto show = controller()->uiShow(); const auto fail = [=](QString error) { }; controller()->session().data().businessInfo().saveChatIntro( diff --git a/Telegram/SourceFiles/settings/business/settings_location.cpp b/Telegram/SourceFiles/settings/business/settings_location.cpp index 4a2f14e73..e5bb0f8e3 100644 --- a/Telegram/SourceFiles/settings/business/settings_location.cpp +++ b/Telegram/SourceFiles/settings/business/settings_location.cpp @@ -8,16 +8,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/business/settings_location.h" #include "core/application.h" +#include "core/shortcuts.h" +#include "data/business/data_business_info.h" +#include "data/data_file_origin.h" #include "data/data_session.h" +#include "data/data_user.h" #include "lang/lang_keys.h" +#include "main/main_app_config.h" #include "main/main_session.h" +#include "mainwidget.h" +#include "mainwindow.h" #include "settings/business/settings_recipients_helper.h" +#include "settings/settings_common.h" +#include "storage/storage_account.h" +#include "ui/controls/location_picker.h" #include "ui/text/text_utilities.h" #include "ui/widgets/fields/input_field.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/vertical_list.h" #include "window/window_session_controller.h" +#include "styles/style_chat.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" #include "styles/style_settings.h" namespace Settings { @@ -40,16 +54,37 @@ private: void setupContent(not_null controller); void save(); - [[nodiscard]] bool mapSupported() const; + void setupPicker(not_null content); + void setupUnsupported(not_null content); + [[nodiscard]] bool mapSupported() const; + void chooseOnMap(); + + const Ui::LocationPickerConfig _config; + rpl::variable _data; + rpl::variable _map = nullptr; + std::shared_ptr _view; Ui::RoundRect _bottomSkipRounding; }; +[[nodiscard]] Ui::LocationPickerConfig ResolveBusinessMapsConfig( + not_null session) { + const auto &appConfig = session->appConfig(); + auto map = appConfig.get>( + u"tdesktop_config_map"_q, + base::flat_map()); + return { + .mapsToken = map[u"bmaps"_q], + .geoToken = map[u"bgeo"_q], + }; +} + Location::Location( QWidget *parent, not_null controller) : BusinessSection(parent, controller) +, _config(ResolveBusinessMapsConfig(&controller->session())) , _bottomSkipRounding(st::boxRadius, st::boxDividerBg) { setupContent(controller); } @@ -65,12 +100,23 @@ rpl::producer Location::title() { } void Location::setupContent( - not_null controller) { + not_null controller) { using namespace rpl::mappers; const auto content = Ui::CreateChild(this); -#if 0 // #TODO location choosing + if (mapSupported()) { + setupPicker(content); + } else { + setupUnsupported(content); + } + + Ui::ResizeFitChild(this, content); +} + +void Location::setupPicker(not_null content) { + _data = controller()->session().user()->businessDetails().location; + AddDividerTextWithLottie(content, { .lottie = u"location"_q, .lottieSize = st::settingsCloudPasswordIconSize, @@ -86,36 +132,158 @@ void Location::setupContent( st::settingsLocationAddress, Ui::InputField::Mode::MultiLine, tr::lng_location_address(), - QString()), + _data.current().address), st::settingsChatbotsUsernameMargins); + _data.value( + ) | rpl::start_with_next([=](const Data::BusinessLocation &location) { + address->setText(location.address); + }, address->lifetime()); + + address->changes() | rpl::start_with_next([=] { + auto copy = _data.current(); + copy.address = address->getLastText(); + _data = std::move(copy); + }, address->lifetime()); + + AddDivider(content); + AddSkip(content); + + const auto maptoggle = AddButtonWithIcon( + content, + tr::lng_location_set_map(), + st::settingsButton, + { &st::menuIconAddress } + )->toggleOn(_data.value( + ) | rpl::map([](const Data::BusinessLocation &location) { + return location.point.has_value(); + })); + + maptoggle->toggledValue() | rpl::start_with_next([=](bool toggled) { + if (!toggled) { + auto copy = _data.current(); + if (copy.point.has_value()) { + copy.point = std::nullopt; + _data = std::move(copy); + } + } else if (!_data.current().point.has_value()) { + _data.force_assign(_data.current()); + chooseOnMap(); + } + }, maptoggle->lifetime()); + + const auto mapSkip = st::defaultVerticalListSkip; + const auto mapWrap = content->add( + object_ptr>( + content, + object_ptr(content), + st::boxRowPadding + QMargins(0, mapSkip, 0, mapSkip))); + mapWrap->toggle(_data.current().point.has_value(), anim::type::instant); + + const auto map = mapWrap->entity(); + map->resize(map->width(), st::locationSize.height()); + + _data.value( + ) | rpl::start_with_next([=](const Data::BusinessLocation &location) { + const auto image = location.point.has_value() + ? controller()->session().data().location(*location.point).get() + : nullptr; + if (image) { + image->load(&controller()->session(), {}); + _view = image->createView(); + } + mapWrap->toggle(image != nullptr, anim::type::normal); + _map = image; + }, mapWrap->lifetime()); + + map->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(map); + + const auto left = (map->width() - st::locationSize.width()) / 2; + const auto rect = QRect(QPoint(left, 0), st::locationSize); + const auto &image = _view ? *_view : QImage(); + if (!image.isNull()) { + p.drawImage(rect, image); + } + + const auto paintMarker = [&](const style::icon &icon) { + icon.paint( + p, + rect.x() + ((rect.width() - icon.width()) / 2), + rect.y() + (rect.height() / 2) - icon.height(), + width()); + }; + paintMarker(st::historyMapPoint); + paintMarker(st::historyMapPointInner); + }, map->lifetime()); + + controller()->session().downloaderTaskFinished( + ) | rpl::start_with_next([=] { + map->update(); + }, map->lifetime()); + + map->setClickedCallback([=] { + chooseOnMap(); + }); + showFinishes() | rpl::start_with_next([=] { address->setFocus(); }, address->lifetime()); -#endif +} - if (!mapSupported()) { - AddDividerTextWithLottie(content, { - .lottie = u"phone"_q, - .lottieSize = st::settingsCloudPasswordIconSize, - .lottieMargins = st::peerAppearanceIconPadding, - .showFinished = showFinishes(), - .about = tr::lng_location_fallback(Ui::Text::WithEntities), - .aboutMargins = st::peerAppearanceCoverLabelMargin, - .parts = RectPart::Top, - }); - } else { +void Location::chooseOnMap() { + const auto callback = [=](Data::InputVenue venue) { + auto copy = _data.current(); + copy.point = Data::LocationPoint( + venue.lat, + venue.lon, + Data::LocationPoint::NoAccessHash); + copy.address = venue.address; + _data = std::move(copy); + }; + const auto session = &controller()->session(); + const auto current = _data.current().point; + const auto initial = current + ? Core::GeoLocation{ + .point = { current->lat(), current->lon() }, + .accuracy = Core::GeoLocationAccuracy::Exact, + } + : Core::GeoLocation(); + Ui::LocationPicker::Show({ + .parent = controller()->widget(), + .config = _config, + .chooseLabel = tr::lng_maps_point_set(), + .session = session, + .initial = initial, + .callback = crl::guard(this, callback), + .quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); }, + .storageId = session->local().resolveStorageIdBots(), + .closeRequests = controller()->content()->death(), + }); +} - } - - Ui::ResizeFitChild(this, content); +void Location::setupUnsupported(not_null content) { + AddDividerTextWithLottie(content, { + .lottie = u"phone"_q, + .lottieSize = st::settingsCloudPasswordIconSize, + .lottieMargins = st::peerAppearanceIconPadding, + .showFinished = showFinishes(), + .about = tr::lng_location_fallback(Ui::Text::WithEntities), + .aboutMargins = st::peerAppearanceCoverLabelMargin, + .parts = RectPart::Top, + }); } void Location::save() { + const auto fail = [=](QString error) { + }; + auto value = _data.current(); + value.address = value.address.trimmed(); + controller()->session().data().businessInfo().saveLocation(value, fail); } bool Location::mapSupported() const { - return false; + return Ui::LocationPicker::Available(_config); } } // namespace diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index b461a902b..3f1c74fe0 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -427,8 +427,9 @@ void VenuesController::rowPaintIcon( )"_q; } -[[nodiscard]] object_ptr MakeSendLocationButton( +[[nodiscard]] object_ptr MakeChooseLocationButton( QWidget *parent, + rpl::producer label, rpl::producer address) { auto result = object_ptr( parent, @@ -465,7 +466,7 @@ void VenuesController::rowPaintIcon( }); const auto name = CreateChild( raw, - tr::lng_maps_point_send(tr::now), + std::move(label), st::pickLocationButtonText); name->show(); const auto status = CreateChild( @@ -679,10 +680,15 @@ bool LocationPicker::Available(const LocationPickerConfig &config) { void LocationPicker::setup(const Descriptor &descriptor) { setupWindow(descriptor); setupWebview(descriptor); - if (LastExactLocation) { - venuesRequest(LastExactLocation); - resolveAddress(LastExactLocation); - venuesSearchEnableAt(LastExactLocation); + + _initialProvided = descriptor.initial.exact(); + const auto initial = _initialProvided + ? descriptor.initial + : LastExactLocation; + if (initial) { + venuesRequest(initial); + resolveAddress(initial); + venuesSearchEnableAt(initial); } } @@ -717,7 +723,10 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) { const auto toppad = mapControls->add(object_ptr(controls)); const auto button = mapControls->add( - MakeSendLocationButton(mapControls, _geocoderAddress.value()), + MakeChooseLocationButton( + mapControls, + std::move(descriptor.chooseLabel), + _geocoderAddress.value()), { 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip }); button->setClickedCallback([=] { _webview->eval("LocationPicker.send();"); @@ -809,7 +818,9 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { const auto event = object.value("event").toString(); if (event == u"ready"_q) { mapReady(); - resolveCurrentLocation(); + if (!_initialProvided) { + resolveCurrentLocation(); + } if (_webview) { _webview->focus(); } @@ -820,7 +831,11 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) { } else if (event == u"send"_q) { const auto lat = object.value("latitude").toDouble(); const auto lon = object.value("longitude").toDouble(); - _callback({ lat, lon }); + _callback({ + .lat = lat, + .lon = lon, + .address = _geocoderAddress.current(), + }); close(); } else if (event == u"move_start"_q) { if (const auto now = _geocoderAddress.current() diff --git a/Telegram/SourceFiles/ui/controls/location_picker.h b/Telegram/SourceFiles/ui/controls/location_picker.h index 756e6b162..0a1485157 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.h +++ b/Telegram/SourceFiles/ui/controls/location_picker.h @@ -80,8 +80,10 @@ public: struct Descriptor { RpWidget *parent = nullptr; LocationPickerConfig config; + rpl::producer chooseLabel; PeerData *recipient = nullptr; not_null session; + Core::GeoLocation initial; Fn callback; Fn quit; Webview::StorageId storageId; @@ -132,6 +134,7 @@ private: std::unique_ptr _webview; SingleQueuedInvokation _updateStyles; bool _subscribedToColors = false; + bool _initialProvided = false; base::Timer _geocoderResolveTimer; Core::GeoLocation _geocoderResolvePostponed;