mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Added ability to set profile photo from camera.
This commit is contained in:
parent
3b9ac19482
commit
3cb595c3c9
8 changed files with 297 additions and 189 deletions
|
@ -1308,6 +1308,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_profile_migrate_learn_more" = "Learn more »";
|
"lng_profile_migrate_learn_more" = "Learn more »";
|
||||||
"lng_profile_migrate_button" = "Upgrade to supergroup";
|
"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_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_channel_not_accessible" = "Sorry, this channel is not accessible.";
|
||||||
"lng_group_not_accessible" = "Sorry, this group 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_failed" = "Failed";
|
||||||
"lng_attach_file" = "File";
|
"lng_attach_file" = "File";
|
||||||
"lng_attach_photo" = "Photo";
|
"lng_attach_photo" = "Photo";
|
||||||
|
"lng_attach_camera" = "Camera";
|
||||||
|
|
||||||
"lng_media_open_with" = "Open With";
|
"lng_media_open_with" = "Open With";
|
||||||
"lng_media_download" = "Download";
|
"lng_media_download" = "Download";
|
||||||
|
|
|
@ -67,7 +67,8 @@ void OpenWithPreparedFile(
|
||||||
void PrepareProfilePhoto(
|
void PrepareProfilePhoto(
|
||||||
not_null<Ui::RpWidget*> parent,
|
not_null<Ui::RpWidget*> parent,
|
||||||
not_null<Window::Controller*> controller,
|
not_null<Window::Controller*> controller,
|
||||||
Fn<void(QImage &&image)> &&doneCallback) {
|
Fn<void(QImage &&image)> &&doneCallback,
|
||||||
|
QImage &&image) {
|
||||||
const auto resizeToMinSize = [=](
|
const auto resizeToMinSize = [=](
|
||||||
QImage &&image,
|
QImage &&image,
|
||||||
Qt::AspectRatioMode mode) {
|
Qt::AspectRatioMode mode) {
|
||||||
|
@ -82,8 +83,53 @@ void PrepareProfilePhoto(
|
||||||
return std::move(image);
|
return std::move(image);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (image.isNull()
|
||||||
|
|| (image.width() > (10 * image.height()))
|
||||||
|
|| (image.height() > (10 * image.width()))) {
|
||||||
|
controller->show(Ui::MakeInformBox(tr::lng_bad_photo()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
image = resizeToMinSize(
|
||||||
|
std::move(image),
|
||||||
|
Qt::KeepAspectRatioByExpanding);
|
||||||
|
const auto fileImage = std::make_shared<Image>(std::move(image));
|
||||||
|
|
||||||
|
auto applyModifications = [=, done = std::move(doneCallback)](
|
||||||
|
const PhotoModifications &mods) {
|
||||||
|
done(resizeToMinSize(
|
||||||
|
ImageModified(fileImage->original(), mods),
|
||||||
|
Qt::KeepAspectRatio));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto crop = [&] {
|
||||||
|
const auto &i = fileImage;
|
||||||
|
const auto minSide = std::min(i->width(), i->height());
|
||||||
|
return QRect(
|
||||||
|
(i->width() - minSide) / 2,
|
||||||
|
(i->height() - minSide) / 2,
|
||||||
|
minSide,
|
||||||
|
minSide);
|
||||||
|
}();
|
||||||
|
|
||||||
|
controller->showLayer(
|
||||||
|
std::make_unique<LayerWidget>(
|
||||||
|
parent,
|
||||||
|
controller,
|
||||||
|
fileImage,
|
||||||
|
PhotoModifications{ .crop = std::move(crop) },
|
||||||
|
std::move(applyModifications),
|
||||||
|
EditorData{
|
||||||
|
.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 auto callback = [=, done = std::move(doneCallback)](
|
||||||
const FileDialog::OpenResult &result) {
|
const FileDialog::OpenResult &result) mutable {
|
||||||
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -93,45 +139,11 @@ void PrepareProfilePhoto(
|
||||||
.content = result.remoteContent,
|
.content = result.remoteContent,
|
||||||
.forceOpaque = true,
|
.forceOpaque = true,
|
||||||
}).image;
|
}).image;
|
||||||
if (image.isNull()
|
PrepareProfilePhoto(
|
||||||
|| (image.width() > (10 * image.height()))
|
parent,
|
||||||
|| (image.height() > (10 * image.width()))) {
|
controller,
|
||||||
controller->show(Ui::MakeInformBox(tr::lng_bad_photo()));
|
std::move(done),
|
||||||
return;
|
std::move(image));
|
||||||
}
|
|
||||||
image = resizeToMinSize(
|
|
||||||
std::move(image),
|
|
||||||
Qt::KeepAspectRatioByExpanding);
|
|
||||||
const auto fileImage = std::make_shared<Image>(std::move(image));
|
|
||||||
|
|
||||||
auto applyModifications = [=, done = std::move(done)](
|
|
||||||
const PhotoModifications &mods) {
|
|
||||||
done(resizeToMinSize(
|
|
||||||
ImageModified(fileImage->original(), mods),
|
|
||||||
Qt::KeepAspectRatio));
|
|
||||||
};
|
|
||||||
|
|
||||||
auto crop = [&] {
|
|
||||||
const auto &i = fileImage;
|
|
||||||
const auto minSide = std::min(i->width(), i->height());
|
|
||||||
return QRect(
|
|
||||||
(i->width() - minSide) / 2,
|
|
||||||
(i->height() - minSide) / 2,
|
|
||||||
minSide,
|
|
||||||
minSide);
|
|
||||||
}();
|
|
||||||
|
|
||||||
controller->showLayer(
|
|
||||||
std::make_unique<LayerWidget>(
|
|
||||||
parent,
|
|
||||||
controller,
|
|
||||||
fileImage,
|
|
||||||
PhotoModifications{ .crop = std::move(crop) },
|
|
||||||
std::move(applyModifications),
|
|
||||||
EditorData{
|
|
||||||
.cropType = EditorData::CropType::Ellipse,
|
|
||||||
.keepAspectRatio = true, }),
|
|
||||||
Ui::LayerOption::KeepOther);
|
|
||||||
};
|
};
|
||||||
FileDialog::GetOpenPath(
|
FileDialog::GetOpenPath(
|
||||||
parent.get(),
|
parent.get(),
|
||||||
|
|
|
@ -32,6 +32,12 @@ void OpenWithPreparedFile(
|
||||||
Fn<void()> &&doneCallback);
|
Fn<void()> &&doneCallback);
|
||||||
|
|
||||||
void PrepareProfilePhoto(
|
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<Ui::RpWidget*> parent,
|
||||||
not_null<Window::Controller*> controller,
|
not_null<Window::Controller*> controller,
|
||||||
Fn<void(QImage &&image)> &&doneCallback);
|
Fn<void(QImage &&image)> &&doneCallback);
|
||||||
|
|
|
@ -56,6 +56,155 @@ Calls::Calls(
|
||||||
|
|
||||||
Calls::~Calls() = default;
|
Calls::~Calls() = default;
|
||||||
|
|
||||||
|
Webrtc::VideoTrack *Calls::AddCameraSubsection(
|
||||||
|
std::shared_ptr<Ui::Show> show,
|
||||||
|
not_null<Ui::VerticalLayout*> content,
|
||||||
|
bool saveToSettings) {
|
||||||
|
auto &lifetime = content->lifetime();
|
||||||
|
|
||||||
|
const auto hasCall = (Core::App().calls().currentCall() != nullptr);
|
||||||
|
|
||||||
|
const auto cameraNameStream = lifetime.make_state<
|
||||||
|
rpl::event_stream<QString>
|
||||||
|
>();
|
||||||
|
|
||||||
|
auto capturerOwner = lifetime.make_state<
|
||||||
|
std::shared_ptr<tgcalls::VideoCaptureInterface>
|
||||||
|
>();
|
||||||
|
|
||||||
|
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,
|
||||||
|
Core::App().settings().callVideoInputDeviceId(),
|
||||||
|
&VideoInput::id);
|
||||||
|
return (i != end(cameras))
|
||||||
|
? i->name
|
||||||
|
: tr::lng_settings_call_device_default(tr::now);
|
||||||
|
}();
|
||||||
|
|
||||||
|
AddButtonWithLabel(
|
||||||
|
content,
|
||||||
|
tr::lng_settings_call_input_device(),
|
||||||
|
rpl::single(
|
||||||
|
currentCameraName
|
||||||
|
) | rpl::then(
|
||||||
|
cameraNameStream->events()
|
||||||
|
),
|
||||||
|
st::settingsButtonNoIcon
|
||||||
|
)->addClickHandler([=] {
|
||||||
|
const auto &devices = GetVideoInputList();
|
||||||
|
const auto options = ranges::views::concat(
|
||||||
|
ranges::views::single(
|
||||||
|
tr::lng_settings_call_device_default(tr::now)),
|
||||||
|
devices | ranges::views::transform(&VideoInput::name)
|
||||||
|
) | ranges::to_vector;
|
||||||
|
const auto i = ranges::find(
|
||||||
|
devices,
|
||||||
|
Core::App().settings().callVideoInputDeviceId(),
|
||||||
|
&VideoInput::id);
|
||||||
|
const auto currentOption = (i != end(devices))
|
||||||
|
? int(i - begin(devices) + 1)
|
||||||
|
: 0;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (*capturerOwner) {
|
||||||
|
(*capturerOwner)->switchToDevice(
|
||||||
|
deviceId.toStdString(),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
|
||||||
|
SingleChoiceBox(box, {
|
||||||
|
.title = tr::lng_settings_call_camera(),
|
||||||
|
.options = options,
|
||||||
|
.initialSelection = currentOption,
|
||||||
|
.callback = save,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
const auto bubbleWrap = content->add(object_ptr<Ui::RpWidget>(content));
|
||||||
|
const auto bubble = lifetime.make_state<::Calls::VideoBubble>(
|
||||||
|
bubbleWrap,
|
||||||
|
track);
|
||||||
|
const auto padding = st::settingsButtonNoIcon.padding.left();
|
||||||
|
const auto top = st::boxRoundShadow.extend.top();
|
||||||
|
const auto bottom = st::boxRoundShadow.extend.bottom();
|
||||||
|
|
||||||
|
auto frameSize = track->renderNextFrame(
|
||||||
|
) | rpl::map([=] {
|
||||||
|
return track->frameSize();
|
||||||
|
}) | rpl::filter([=](QSize size) {
|
||||||
|
return !size.isEmpty()
|
||||||
|
&& !Core::App().calls().currentCall()
|
||||||
|
&& !Core::App().calls().currentGroupCall();
|
||||||
|
});
|
||||||
|
auto bubbleWidth = bubbleWrap->widthValue(
|
||||||
|
) | rpl::filter([=](int width) {
|
||||||
|
return width > 2 * padding + 1;
|
||||||
|
});
|
||||||
|
rpl::combine(
|
||||||
|
std::move(bubbleWidth),
|
||||||
|
std::move(frameSize)
|
||||||
|
) | rpl::start_with_next([=](int width, QSize frame) {
|
||||||
|
const auto useWidth = (width - 2 * padding);
|
||||||
|
const auto useHeight = std::min(
|
||||||
|
((useWidth * frame.height()) / frame.width()),
|
||||||
|
(useWidth * 480) / 640);
|
||||||
|
bubbleWrap->resize(width, top + useHeight + bottom);
|
||||||
|
bubble->updateGeometry(
|
||||||
|
::Calls::VideoBubble::DragMode::None,
|
||||||
|
QRect(padding, top, useWidth, useHeight));
|
||||||
|
bubbleWrap->update();
|
||||||
|
}, bubbleWrap->lifetime());
|
||||||
|
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
const auto checkCapturer = [=] {
|
||||||
|
if (*capturerOwner
|
||||||
|
|| Core::App().calls().currentCall()
|
||||||
|
|| Core::App().calls().currentGroupCall()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*capturerOwner = Core::App().calls().getVideoCapture(
|
||||||
|
Core::App().settings().callVideoInputDeviceId(),
|
||||||
|
false);
|
||||||
|
(*capturerOwner)->setPreferredAspectRatio(0.);
|
||||||
|
track->setState(VideoState::Active);
|
||||||
|
(*capturerOwner)->setState(tgcalls::VideoState::Active);
|
||||||
|
(*capturerOwner)->setOutput(track->sink());
|
||||||
|
};
|
||||||
|
rpl::combine(
|
||||||
|
Core::App().calls().currentCallValue(),
|
||||||
|
Core::App().calls().currentGroupCallValue(),
|
||||||
|
_1 || _2
|
||||||
|
) | rpl::start_with_next([=](bool has) {
|
||||||
|
if (has) {
|
||||||
|
track->setState(VideoState::Inactive);
|
||||||
|
bubbleWrap->resize(bubbleWrap->width(), 0);
|
||||||
|
*capturerOwner = nullptr;
|
||||||
|
} else {
|
||||||
|
crl::on_main(content, checkCapturer);
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
void Calls::sectionSaveChanges(FnMut<void()> done) {
|
void Calls::sectionSaveChanges(FnMut<void()> done) {
|
||||||
if (_micTester) {
|
if (_micTester) {
|
||||||
_micTester.reset();
|
_micTester.reset();
|
||||||
|
@ -66,144 +215,13 @@ void Calls::sectionSaveChanges(FnMut<void()> done) {
|
||||||
void Calls::setupContent() {
|
void Calls::setupContent() {
|
||||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||||
|
|
||||||
const auto &settings = Core::App().settings();
|
if (!GetVideoInputList().empty()) {
|
||||||
const auto cameras = GetVideoInputList();
|
|
||||||
if (!cameras.empty()) {
|
|
||||||
const auto hasCall = (Core::App().calls().currentCall() != nullptr);
|
|
||||||
|
|
||||||
auto capturerOwner = content->lifetime().make_state<
|
|
||||||
std::shared_ptr<tgcalls::VideoCaptureInterface>
|
|
||||||
>();
|
|
||||||
|
|
||||||
const auto track = content->lifetime().make_state<VideoTrack>(
|
|
||||||
(hasCall
|
|
||||||
? VideoState::Inactive
|
|
||||||
: VideoState::Active));
|
|
||||||
|
|
||||||
const auto currentCameraName = [&] {
|
|
||||||
const auto i = ranges::find(
|
|
||||||
cameras,
|
|
||||||
settings.callVideoInputDeviceId(),
|
|
||||||
&VideoInput::id);
|
|
||||||
return (i != end(cameras))
|
|
||||||
? i->name
|
|
||||||
: tr::lng_settings_call_device_default(tr::now);
|
|
||||||
}();
|
|
||||||
|
|
||||||
AddSkip(content);
|
AddSkip(content);
|
||||||
AddSubsectionTitle(content, tr::lng_settings_call_camera());
|
AddSubsectionTitle(content, tr::lng_settings_call_camera());
|
||||||
AddButtonWithLabel(
|
AddCameraSubsection(
|
||||||
|
std::make_shared<Window::Show>(_controller),
|
||||||
content,
|
content,
|
||||||
tr::lng_settings_call_input_device(),
|
true);
|
||||||
rpl::single(
|
|
||||||
currentCameraName
|
|
||||||
) | rpl::then(
|
|
||||||
_cameraNameStream.events()
|
|
||||||
),
|
|
||||||
st::settingsButtonNoIcon
|
|
||||||
)->addClickHandler([=] {
|
|
||||||
const auto &devices = GetVideoInputList();
|
|
||||||
const auto options = ranges::views::concat(
|
|
||||||
ranges::views::single(
|
|
||||||
tr::lng_settings_call_device_default(tr::now)),
|
|
||||||
devices | ranges::views::transform(&VideoInput::name)
|
|
||||||
) | ranges::to_vector;
|
|
||||||
const auto i = ranges::find(
|
|
||||||
devices,
|
|
||||||
Core::App().settings().callVideoInputDeviceId(),
|
|
||||||
&VideoInput::id);
|
|
||||||
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 deviceId = option
|
|
||||||
? devices[option - 1].id
|
|
||||||
: "default";
|
|
||||||
Core::App().settings().setCallVideoInputDeviceId(deviceId);
|
|
||||||
Core::App().saveSettingsDelayed();
|
|
||||||
if (const auto call = Core::App().calls().currentCall()) {
|
|
||||||
call->setCurrentCameraDevice(deviceId);
|
|
||||||
}
|
|
||||||
if (*capturerOwner) {
|
|
||||||
(*capturerOwner)->switchToDevice(
|
|
||||||
deviceId.toStdString(),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_controller->show(Box([=](not_null<Ui::GenericBox*> box) {
|
|
||||||
SingleChoiceBox(box, {
|
|
||||||
.title = tr::lng_settings_call_camera(),
|
|
||||||
.options = options,
|
|
||||||
.initialSelection = currentOption,
|
|
||||||
.callback = save,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
const auto bubbleWrap = content->add(object_ptr<Ui::RpWidget>(content));
|
|
||||||
const auto bubble = content->lifetime().make_state<::Calls::VideoBubble>(
|
|
||||||
bubbleWrap,
|
|
||||||
track);
|
|
||||||
const auto padding = st::settingsButtonNoIcon.padding.left();
|
|
||||||
const auto top = st::boxRoundShadow.extend.top();
|
|
||||||
const auto bottom = st::boxRoundShadow.extend.bottom();
|
|
||||||
|
|
||||||
auto frameSize = track->renderNextFrame(
|
|
||||||
) | rpl::map([=] {
|
|
||||||
return track->frameSize();
|
|
||||||
}) | rpl::filter([=](QSize size) {
|
|
||||||
return !size.isEmpty()
|
|
||||||
&& !Core::App().calls().currentCall()
|
|
||||||
&& !Core::App().calls().currentGroupCall();
|
|
||||||
});
|
|
||||||
auto bubbleWidth = bubbleWrap->widthValue(
|
|
||||||
) | rpl::filter([=](int width) {
|
|
||||||
return width > 2 * padding + 1;
|
|
||||||
});
|
|
||||||
rpl::combine(
|
|
||||||
std::move(bubbleWidth),
|
|
||||||
std::move(frameSize)
|
|
||||||
) | rpl::start_with_next([=](int width, QSize frame) {
|
|
||||||
const auto useWidth = (width - 2 * padding);
|
|
||||||
const auto useHeight = std::min(
|
|
||||||
((useWidth * frame.height()) / frame.width()),
|
|
||||||
(useWidth * 480) / 640);
|
|
||||||
bubbleWrap->resize(width, top + useHeight + bottom);
|
|
||||||
bubble->updateGeometry(
|
|
||||||
::Calls::VideoBubble::DragMode::None,
|
|
||||||
QRect(padding, top, useWidth, useHeight));
|
|
||||||
bubbleWrap->update();
|
|
||||||
}, bubbleWrap->lifetime());
|
|
||||||
|
|
||||||
using namespace rpl::mappers;
|
|
||||||
const auto checkCapturer = [=] {
|
|
||||||
if (*capturerOwner
|
|
||||||
|| Core::App().calls().currentCall()
|
|
||||||
|| Core::App().calls().currentGroupCall()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*capturerOwner = Core::App().calls().getVideoCapture(
|
|
||||||
Core::App().settings().callVideoInputDeviceId(),
|
|
||||||
false);
|
|
||||||
(*capturerOwner)->setPreferredAspectRatio(0.);
|
|
||||||
track->setState(VideoState::Active);
|
|
||||||
(*capturerOwner)->setState(tgcalls::VideoState::Active);
|
|
||||||
(*capturerOwner)->setOutput(track->sink());
|
|
||||||
};
|
|
||||||
rpl::combine(
|
|
||||||
Core::App().calls().currentCallValue(),
|
|
||||||
Core::App().calls().currentGroupCallValue(),
|
|
||||||
_1 || _2
|
|
||||||
) | rpl::start_with_next([=](bool has) {
|
|
||||||
if (has) {
|
|
||||||
track->setState(VideoState::Inactive);
|
|
||||||
bubbleWrap->resize(bubbleWrap->width(), 0);
|
|
||||||
*capturerOwner = nullptr;
|
|
||||||
} else {
|
|
||||||
crl::on_main(content, checkCapturer);
|
|
||||||
}
|
|
||||||
}, content->lifetime());
|
|
||||||
|
|
||||||
AddSkip(content);
|
AddSkip(content);
|
||||||
AddDivider(content);
|
AddDivider(content);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,12 @@ class Call;
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class LevelMeter;
|
class LevelMeter;
|
||||||
class GenericBox;
|
class GenericBox;
|
||||||
|
class Show;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Webrtc {
|
namespace Webrtc {
|
||||||
class AudioInputTester;
|
class AudioInputTester;
|
||||||
|
class VideoTrack;
|
||||||
} // namespace Webrtc
|
} // namespace Webrtc
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
@ -38,6 +40,11 @@ public:
|
||||||
|
|
||||||
void sectionSaveChanges(FnMut<void()> done) override;
|
void sectionSaveChanges(FnMut<void()> done) override;
|
||||||
|
|
||||||
|
static Webrtc::VideoTrack *AddCameraSubsection(
|
||||||
|
std::shared_ptr<Ui::Show> show,
|
||||||
|
not_null<Ui::VerticalLayout*> content,
|
||||||
|
bool saveToSettings);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupContent();
|
void setupContent();
|
||||||
void requestPermissionAndStartTestingMicrophone();
|
void requestPermissionAndStartTestingMicrophone();
|
||||||
|
|
|
@ -66,7 +66,7 @@ void SetupPhoto(
|
||||||
auto callback = [=](QImage &&image) {
|
auto callback = [=](QImage &&image) {
|
||||||
self->session().api().peerPhoto().upload(self, std::move(image));
|
self->session().api().peerPhoto().upload(self, std::move(image));
|
||||||
};
|
};
|
||||||
Editor::PrepareProfilePhoto(
|
Editor::PrepareProfilePhotoFromFile(
|
||||||
upload,
|
upload,
|
||||||
&controller->window(),
|
&controller->window(),
|
||||||
std::move(callback));
|
std::move(callback));
|
||||||
|
|
|
@ -7,8 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "ui/special_buttons.h"
|
#include "ui/special_buttons.h"
|
||||||
|
|
||||||
#include "styles/style_boxes.h"
|
#include "base/call_delayed.h"
|
||||||
#include "styles/style_chat.h"
|
|
||||||
#include "dialogs/ui/dialogs_layout.h"
|
#include "dialogs/ui/dialogs_layout.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "ui/effects/radial_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_instance.h"
|
||||||
#include "media/streaming/media_streaming_player.h"
|
#include "media/streaming/media_streaming_player.h"
|
||||||
#include "media/streaming/media_streaming_document.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_controller.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "mainwidget.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "facades.h"
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kAnimationDuration = crl::time(120);
|
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) {
|
QString CropTitle(not_null<PeerData*> peer) {
|
||||||
if (peer->isChat() || peer->isMegagroup()) {
|
if (peer->isChat() || peer->isMegagroup()) {
|
||||||
return tr::lng_create_group_crop(tr::now);
|
return tr::lng_create_group_crop(tr::now);
|
||||||
|
@ -198,10 +244,7 @@ void UserpicButton::prepare() {
|
||||||
void UserpicButton::setClickHandlerByRole() {
|
void UserpicButton::setClickHandlerByRole() {
|
||||||
switch (_role) {
|
switch (_role) {
|
||||||
case Role::ChangePhoto:
|
case Role::ChangePhoto:
|
||||||
addClickHandler(App::LambdaDelayed(
|
addClickHandler([=] { changePhotoLocally(); });
|
||||||
_st.changeButton.ripple.hideDuration,
|
|
||||||
this,
|
|
||||||
[=] { changePhotoLocally(); }));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Role::OpenPhoto:
|
case Role::OpenPhoto:
|
||||||
|
@ -230,10 +273,26 @@ void UserpicButton::changePhotoLocally(bool requestToUpload) {
|
||||||
_uploadPhotoRequests.fire({});
|
_uploadPhotoRequests.fire({});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Editor::PrepareProfilePhoto(
|
const auto chooseFile = [=] {
|
||||||
this,
|
base::call_delayed(
|
||||||
_window,
|
_st.changeButton.ripple.hideDuration,
|
||||||
std::move(callback));
|
crl::guard(this, [=] {
|
||||||
|
Editor::PrepareProfilePhotoFromFile(
|
||||||
|
this,
|
||||||
|
_window,
|
||||||
|
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() {
|
void UserpicButton::openPeerPhoto() {
|
||||||
|
|
|
@ -36,6 +36,8 @@ struct Information;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
|
class PopupMenu;
|
||||||
|
|
||||||
class HistoryDownButton : public RippleButton {
|
class HistoryDownButton : public RippleButton {
|
||||||
public:
|
public:
|
||||||
HistoryDownButton(QWidget *parent, const style::TwoIconButton &st);
|
HistoryDownButton(QWidget *parent, const style::TwoIconButton &st);
|
||||||
|
@ -158,6 +160,8 @@ private:
|
||||||
std::unique_ptr<Media::Streaming::Instance> _streamed;
|
std::unique_ptr<Media::Streaming::Instance> _streamed;
|
||||||
PhotoData *_streamedPhoto = nullptr;
|
PhotoData *_streamedPhoto = nullptr;
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
|
||||||
bool _showSavedMessagesOnSelf = false;
|
bool _showSavedMessagesOnSelf = false;
|
||||||
bool _canOpenPhoto = false;
|
bool _canOpenPhoto = false;
|
||||||
bool _cursorInChangeOverlay = false;
|
bool _cursorInChangeOverlay = false;
|
||||||
|
|
Loading…
Add table
Reference in a new issue