Added ability to set profile photo from camera.

This commit is contained in:
23rd 2022-03-14 00:52:33 +03:00
parent 3b9ac19482
commit 3cb595c3c9
8 changed files with 297 additions and 189 deletions

View file

@ -1308,6 +1308,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_migrate_learn_more" = "Learn more »";
"lng_profile_migrate_button" = "Upgrade to supergroup";
"lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
"lng_profile_camera_title" = "Capture yourself";
"lng_channel_not_accessible" = "Sorry, this channel is not accessible.";
"lng_group_not_accessible" = "Sorry, this group is not accessible.";
@ -1459,6 +1460,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_attach_failed" = "Failed";
"lng_attach_file" = "File";
"lng_attach_photo" = "Photo";
"lng_attach_camera" = "Camera";
"lng_media_open_with" = "Open With";
"lng_media_download" = "Download";

View file

@ -67,7 +67,8 @@ void OpenWithPreparedFile(
void PrepareProfilePhoto(
not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback) {
Fn<void(QImage &&image)> &&doneCallback,
QImage &&image) {
const auto resizeToMinSize = [=](
QImage &&image,
Qt::AspectRatioMode mode) {
@ -82,17 +83,6 @@ void PrepareProfilePhoto(
return std::move(image);
};
const auto callback = [=, done = std::move(doneCallback)](
const FileDialog::OpenResult &result) {
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
return;
}
auto image = Images::Read({
.path = result.paths.isEmpty() ? QString() : result.paths.front(),
.content = result.remoteContent,
.forceOpaque = true,
}).image;
if (image.isNull()
|| (image.width() > (10 * image.height()))
|| (image.height() > (10 * image.width()))) {
@ -104,7 +94,7 @@ void PrepareProfilePhoto(
Qt::KeepAspectRatioByExpanding);
const auto fileImage = std::make_shared<Image>(std::move(image));
auto applyModifications = [=, done = std::move(done)](
auto applyModifications = [=, done = std::move(doneCallback)](
const PhotoModifications &mods) {
done(resizeToMinSize(
ImageModified(fileImage->original(), mods),
@ -132,6 +122,28 @@ void PrepareProfilePhoto(
.cropType = EditorData::CropType::Ellipse,
.keepAspectRatio = true, }),
Ui::LayerOption::KeepOther);
}
void PrepareProfilePhotoFromFile(
not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback) {
const auto callback = [=, done = std::move(doneCallback)](
const FileDialog::OpenResult &result) mutable {
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
return;
}
auto image = Images::Read({
.path = result.paths.isEmpty() ? QString() : result.paths.front(),
.content = result.remoteContent,
.forceOpaque = true,
}).image;
PrepareProfilePhoto(
parent,
controller,
std::move(done),
std::move(image));
};
FileDialog::GetOpenPath(
parent.get(),

View file

@ -32,6 +32,12 @@ void OpenWithPreparedFile(
Fn<void()> &&doneCallback);
void PrepareProfilePhoto(
not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback,
QImage &&image);
void PrepareProfilePhotoFromFile(
not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback);

View file

@ -56,49 +56,45 @@ Calls::Calls(
Calls::~Calls() = default;
void Calls::sectionSaveChanges(FnMut<void()> done) {
if (_micTester) {
_micTester.reset();
}
done();
}
Webrtc::VideoTrack *Calls::AddCameraSubsection(
std::shared_ptr<Ui::Show> show,
not_null<Ui::VerticalLayout*> content,
bool saveToSettings) {
auto &lifetime = content->lifetime();
void Calls::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto &settings = Core::App().settings();
const auto cameras = GetVideoInputList();
if (!cameras.empty()) {
const auto hasCall = (Core::App().calls().currentCall() != nullptr);
auto capturerOwner = content->lifetime().make_state<
const auto cameraNameStream = lifetime.make_state<
rpl::event_stream<QString>
>();
auto capturerOwner = lifetime.make_state<
std::shared_ptr<tgcalls::VideoCaptureInterface>
>();
const auto track = content->lifetime().make_state<VideoTrack>(
const auto track = lifetime.make_state<VideoTrack>(
(hasCall
? VideoState::Inactive
: VideoState::Active));
const auto currentCameraName = [&] {
const auto cameras = GetVideoInputList();
const auto i = ranges::find(
cameras,
settings.callVideoInputDeviceId(),
Core::App().settings().callVideoInputDeviceId(),
&VideoInput::id);
return (i != end(cameras))
? i->name
: tr::lng_settings_call_device_default(tr::now);
}();
AddSkip(content);
AddSubsectionTitle(content, tr::lng_settings_call_camera());
AddButtonWithLabel(
content,
tr::lng_settings_call_input_device(),
rpl::single(
currentCameraName
) | rpl::then(
_cameraNameStream.events()
cameraNameStream->events()
),
st::settingsButtonNoIcon
)->addClickHandler([=] {
@ -115,13 +111,15 @@ void Calls::setupContent() {
const auto currentOption = (i != end(devices))
? int(i - begin(devices) + 1)
: 0;
const auto save = crl::guard(this, [=](int option) {
_cameraNameStream.fire_copy(options[option]);
const auto save = crl::guard(content, [=](int option) {
cameraNameStream->fire_copy(options[option]);
const auto deviceId = option
? devices[option - 1].id
: "default";
if (saveToSettings) {
Core::App().settings().setCallVideoInputDeviceId(deviceId);
Core::App().saveSettingsDelayed();
}
if (const auto call = Core::App().calls().currentCall()) {
call->setCurrentCameraDevice(deviceId);
}
@ -131,7 +129,7 @@ void Calls::setupContent() {
false);
}
});
_controller->show(Box([=](not_null<Ui::GenericBox*> box) {
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
SingleChoiceBox(box, {
.title = tr::lng_settings_call_camera(),
.options = options,
@ -141,7 +139,7 @@ void Calls::setupContent() {
}));
});
const auto bubbleWrap = content->add(object_ptr<Ui::RpWidget>(content));
const auto bubble = content->lifetime().make_state<::Calls::VideoBubble>(
const auto bubble = lifetime.make_state<::Calls::VideoBubble>(
bubbleWrap,
track);
const auto padding = st::settingsButtonNoIcon.padding.left();
@ -202,8 +200,28 @@ void Calls::setupContent() {
} else {
crl::on_main(content, checkCapturer);
}
}, content->lifetime());
}, lifetime);
return track;
}
void Calls::sectionSaveChanges(FnMut<void()> done) {
if (_micTester) {
_micTester.reset();
}
done();
}
void Calls::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
if (!GetVideoInputList().empty()) {
AddSkip(content);
AddSubsectionTitle(content, tr::lng_settings_call_camera());
AddCameraSubsection(
std::make_shared<Window::Show>(_controller),
content,
true);
AddSkip(content);
AddDivider(content);
}

View file

@ -23,10 +23,12 @@ class Call;
namespace Ui {
class LevelMeter;
class GenericBox;
class Show;
} // namespace Ui
namespace Webrtc {
class AudioInputTester;
class VideoTrack;
} // namespace Webrtc
namespace Settings {
@ -38,6 +40,11 @@ public:
void sectionSaveChanges(FnMut<void()> done) override;
static Webrtc::VideoTrack *AddCameraSubsection(
std::shared_ptr<Ui::Show> show,
not_null<Ui::VerticalLayout*> content,
bool saveToSettings);
private:
void setupContent();
void requestPermissionAndStartTestingMicrophone();

View file

@ -66,7 +66,7 @@ void SetupPhoto(
auto callback = [=](QImage &&image) {
self->session().api().peerPhoto().upload(self, std::move(image));
};
Editor::PrepareProfilePhoto(
Editor::PrepareProfilePhotoFromFile(
upload,
&controller->window(),
std::move(callback));

View file

@ -7,8 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "ui/special_buttons.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "base/call_delayed.h"
#include "dialogs/ui/dialogs_layout.h"
#include "ui/effects/ripple_animation.h"
#include "ui/effects/radial_animation.h"
@ -32,19 +31,66 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_document.h"
#include "settings/settings_calls.h" // Calls::AddCameraSubsection.
#include "calls/calls_instance.h"
#include "webrtc/webrtc_media_devices.h" // Webrtc::GetVideoInputList.
#include "webrtc/webrtc_video_track.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "mainwidget.h"
#include "facades.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
namespace Ui {
namespace {
constexpr auto kAnimationDuration = crl::time(120);
bool IsCameraAvailable() {
return (Core::App().calls().currentCall() == nullptr)
&& !Webrtc::GetVideoInputList().empty();
}
void CameraBox(
not_null<Ui::GenericBox*> box,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback) {
using namespace Webrtc;
const auto track = Settings::Calls::AddCameraSubsection(
std::make_shared<Ui::BoxShow>(box),
box->verticalLayout(),
false);
if (!track) {
box->closeBox();
return;
}
track->stateValue(
) | rpl::start_with_next([=](const VideoState &state) {
if (state == VideoState::Inactive) {
box->closeBox();
}
}, box->lifetime());
auto done = [=, done = std::move(doneCallback)](QImage &&image) {
box->closeBox();
done(std::move(image));
};
box->setTitle(tr::lng_profile_camera_title());
box->addButton(tr::lng_continue(), [=, done = std::move(done)]() mutable {
Editor::PrepareProfilePhoto(
box,
controller,
std::move(done),
track->frame(FrameRequest()).mirrored(true, false));
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
QString CropTitle(not_null<PeerData*> peer) {
if (peer->isChat() || peer->isMegagroup()) {
return tr::lng_create_group_crop(tr::now);
@ -198,10 +244,7 @@ void UserpicButton::prepare() {
void UserpicButton::setClickHandlerByRole() {
switch (_role) {
case Role::ChangePhoto:
addClickHandler(App::LambdaDelayed(
_st.changeButton.ripple.hideDuration,
this,
[=] { changePhotoLocally(); }));
addClickHandler([=] { changePhotoLocally(); });
break;
case Role::OpenPhoto:
@ -230,10 +273,26 @@ void UserpicButton::changePhotoLocally(bool requestToUpload) {
_uploadPhotoRequests.fire({});
}
};
Editor::PrepareProfilePhoto(
const auto chooseFile = [=] {
base::call_delayed(
_st.changeButton.ripple.hideDuration,
crl::guard(this, [=] {
Editor::PrepareProfilePhotoFromFile(
this,
_window,
std::move(callback));
callback);
}));
};
if (!IsCameraAvailable()) {
chooseFile();
} else {
_menu = base::make_unique_q<Ui::PopupMenu>(this);
_menu->addAction(tr::lng_attach_file(tr::now), chooseFile);
_menu->addAction(tr::lng_attach_camera(tr::now), [=] {
_window->show(Box(CameraBox, _window, callback));
});
_menu->popup(QCursor::pos());
}
}
void UserpicButton::openPeerPhoto() {

View file

@ -36,6 +36,8 @@ struct Information;
namespace Ui {
class PopupMenu;
class HistoryDownButton : public RippleButton {
public:
HistoryDownButton(QWidget *parent, const style::TwoIconButton &st);
@ -158,6 +160,8 @@ private:
std::unique_ptr<Media::Streaming::Instance> _streamed;
PhotoData *_streamedPhoto = nullptr;
base::unique_qptr<Ui::PopupMenu> _menu;
bool _showSavedMessagesOnSelf = false;
bool _canOpenPhoto = false;
bool _cursorInChangeOverlay = false;