mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-11-03 22:27:13 +01:00
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
496 lines
32 KiB
Swift
496 lines
32 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import TelegramCore
|
|
import AccountContext
|
|
import PromptUI
|
|
import PresentationDataUtils
|
|
import GGCore
|
|
import GGSettingsUI
|
|
import GGCameraPipeline
|
|
import GGFeaturesShared
|
|
|
|
/// Menu rows of the zone: «Медиа и звонки › Кружки и камера» (masks, round video settings, gallery,
|
|
/// photo watermark), the «Маски и камера» section of «Звонки», «Приватность › Анонимный режим».
|
|
/// Every row is a binding to a registry setting: writing it rebuilds the screen, nothing else.
|
|
enum GGCameraMasksMenu {
|
|
static let tab = GGMenuTabID.mediaCameraVideoMessages
|
|
|
|
static let sectionQuality = GGMenuSectionID("media.cameraVideoMessages.quality")
|
|
static let sectionCamera = GGMenuSectionID("media.cameraVideoMessages.camera")
|
|
static let sectionRecording = GGMenuSectionID("media.cameraVideoMessages.recording")
|
|
static let sectionSound = GGMenuSectionID("media.cameraVideoMessages.sound")
|
|
static let sectionWatermark = GGMenuSectionID("media.cameraVideoMessages.watermark")
|
|
static let sectionGallery = GGMenuSectionID("media.cameraVideoMessages.gallery")
|
|
static let sectionPhotoWatermark = GGMenuSectionID("media.cameraVideoMessages.photoWatermark")
|
|
static let sectionReset = GGMenuSectionID("media.cameraVideoMessages.reset")
|
|
static let sectionCalls = GGMenuSectionID("media.calls.cameraMasks")
|
|
|
|
static let masksTab = GGMenuTabID("cameraMasks.masks")
|
|
static let studioTab = GGMenuTabID("cameraMasks.studio")
|
|
static let virtualCameraTab = GGMenuTabID("cameraMasks.virtualCamera")
|
|
|
|
static func register() {
|
|
GGCameraMasksMenu.registerSections()
|
|
GGCameraMasksMenu.registerPreview()
|
|
GGCameraMasksMenu.registerMasks()
|
|
GGCameraMasksMenu.registerQuality()
|
|
GGCameraMasksMenu.registerCamera()
|
|
GGCameraMasksMenu.registerRecording()
|
|
GGCameraMasksMenu.registerSound()
|
|
GGCameraMasksMenu.registerWatermark()
|
|
GGCameraMasksMenu.registerGallery()
|
|
GGCameraMasksMenu.registerPhotoWatermark()
|
|
GGCameraMasksMenu.registerReset()
|
|
GGCameraMasksMenu.registerCalls()
|
|
GGCameraMasksMenu.registerAnonymousMode()
|
|
}
|
|
|
|
private static func registerSections() {
|
|
let screen = GGMenuScreenID.tab(GGCameraMasksMenu.tab)
|
|
GGMenuRegistry.register(section: GGMenuSectionDescriptor(id: GGCameraMasksMenu.sectionQuality, screen: screen, order: 300, header: { GGStrings.CameraMasks.Round.Section.quality }, footer: { _ in GGStrings.CameraMasks.Round.qualityFooter }))
|
|
GGMenuRegistry.register(section: GGMenuSectionDescriptor(id: GGCameraMasksMenu.sectionCamera, screen: screen, order: 400, header: { GGStrings.CameraMasks.Round.Section.camera }, footer: { _ in GGStrings.CameraMasks.Round.cameraFooter }))
|
|
GGMenuRegistry.register(section: GGMenuSectionDescriptor(id: GGCameraMasksMenu.sectionRecording, screen: screen, order: 500, header: { GGStrings.CameraMasks.Round.Section.recording }))
|
|
GGMenuRegistry.register(section: GGMenuSectionDescriptor(id: GGCameraMasksMenu.sectionSound, screen: screen, order: 600, header: { GGStrings.CameraMasks.Round.Section.sound }, footer: { _ in GGStrings.CameraMasks.Round.soundFooter }))
|
|
GGMenuRegistry.register(section: GGMenuSectionDescriptor(id: GGCameraMasksMenu.sectionWatermark, screen: screen, order: 700, header: { GGStrings.CameraMasks.Round.Section.watermark }, footer: { _ in GGStrings.CameraMasks.Round.watermarkFooter }))
|
|
GGMenuRegistry.register(section: GGMenuSectionDescriptor(id: GGCameraMasksMenu.sectionGallery, screen: screen, order: 800, header: { GGStrings.CameraMasks.Round.Section.gallery }, footer: { _ in GGStrings.CameraMasks.Round.galleryFooter }))
|
|
GGMenuRegistry.register(section: GGMenuSectionDescriptor(id: GGCameraMasksMenu.sectionPhotoWatermark, screen: screen, order: 900, header: { GGStrings.CameraMasks.Photo.section }, footer: { _ in GGStrings.CameraMasks.Photo.footer }))
|
|
GGMenuRegistry.register(section: GGMenuSectionDescriptor(id: GGCameraMasksMenu.sectionReset, screen: screen, order: 1000))
|
|
GGMenuRegistry.register(section: GGMenuSectionDescriptor(id: GGCameraMasksMenu.sectionCalls, screen: .tab(.mediaCalls), order: 800, header: { GGStrings.CameraMasks.Calls.section }, footer: { _ in GGStrings.CameraMasks.Calls.footer }))
|
|
}
|
|
|
|
private static func registerPreview() {
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.preview",
|
|
section: .preview(of: GGCameraMasksMenu.tab),
|
|
order: 100,
|
|
title: { "" },
|
|
kind: .preview(.custom(height: 236.0, make: { _ in
|
|
return GGRoundVideoPreviewView(frame: .zero)
|
|
}))
|
|
))
|
|
}
|
|
|
|
// MARK: Masks
|
|
|
|
private static func registerMasks() {
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.masks.enabled",
|
|
section: .main(of: GGCameraMasksMenu.tab),
|
|
order: 100,
|
|
title: { GGStrings.CameraMasks.Masks.enabled },
|
|
subtitle: { GGStrings.CameraMasks.Masks.enabledInfo },
|
|
kind: .toggle(.global(GGMaskSettings.enabled)),
|
|
searchKeywords: { [GGStrings.CameraMasks.Masks.keywords] }
|
|
))
|
|
GGMenuRegistry.register(
|
|
tab: GGMenuTabDescriptor(
|
|
id: GGCameraMasksMenu.masksTab,
|
|
parent: .tab(GGCameraMasksMenu.tab),
|
|
title: { GGStrings.CameraMasks.Masks.title },
|
|
makeScreen: { context in
|
|
return ggMakeMaskLibraryScreen(context: context.context, target: .current)
|
|
},
|
|
searchKeywords: { [GGStrings.CameraMasks.Masks.keywords] }
|
|
),
|
|
linkIn: .main(of: GGCameraMasksMenu.tab),
|
|
order: 200,
|
|
subtitle: { GGStrings.CameraMasks.Masks.chooseInfo },
|
|
icon: .system("theatermasks"),
|
|
label: { context in
|
|
return GGMaskEngine.title(GGMaskSelection(storage: GGMaskSettings.selection.value))
|
|
}
|
|
)
|
|
GGMenuRegistry.register(
|
|
tab: GGMenuTabDescriptor(
|
|
id: GGCameraMasksMenu.studioTab,
|
|
parent: .tab(GGCameraMasksMenu.tab),
|
|
title: { GGStrings.CameraMasks.Studio.title },
|
|
makeScreen: { context in
|
|
return ggMakeMaskStudioHome(context: context.context)
|
|
},
|
|
searchKeywords: { [GGStrings.CameraMasks.Studio.keywords] }
|
|
),
|
|
linkIn: .main(of: GGCameraMasksMenu.tab),
|
|
order: 300,
|
|
subtitle: { GGStrings.CameraMasks.Studio.info },
|
|
icon: .system("paintbrush"),
|
|
label: { _ in
|
|
let count = GGMaskStore.shared.currentEntries.count
|
|
return count > 0 ? "\(count)" : nil
|
|
}
|
|
)
|
|
}
|
|
|
|
// MARK: Quality
|
|
|
|
private static func registerQuality() {
|
|
let section = GGCameraMasksMenu.sectionQuality
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.resolution",
|
|
section: section,
|
|
order: 100,
|
|
title: { GGStrings.CameraMasks.Round.resolution },
|
|
kind: .choice(.global(GGRoundVideoSettings.resolution), options: [
|
|
GGMenuChoiceOption(value: "standard", title: { GGStrings.CameraMasks.Round.asTelegram }),
|
|
GGMenuChoiceOption(value: "400", title: { GGStrings.CameraMasks.Round.side(side: "400") }),
|
|
GGMenuChoiceOption(value: "512", title: { GGStrings.CameraMasks.Round.side(side: "512") }),
|
|
GGMenuChoiceOption(value: "640", title: { GGStrings.CameraMasks.Round.side(side: "640") })
|
|
]),
|
|
searchKeywords: { [GGStrings.CameraMasks.Round.qualityKeywords] }
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.bitrate",
|
|
section: section,
|
|
order: 200,
|
|
title: { GGStrings.CameraMasks.Round.bitrate },
|
|
kind: .slider(.global(GGRoundVideoSettings.bitrate), range: 0.0 ... 4000.0, step: 500.0, format: { value in
|
|
if value < 500.0 {
|
|
return GGStrings.CameraMasks.Round.asTelegram
|
|
}
|
|
// What is really recorded: above Telegram's size limit the bitrate is lowered
|
|
// (GGRoundVideoConfig.recording), so the slider says so instead of the chosen value.
|
|
if let maximum = GGRoundVideoServerLimits.maximumVideoKbps(audioKbps: GGRoundVideoServerLimits.audioKbps), Double(maximum) < min(4000.0, value) {
|
|
return GGStrings.CameraMasks.Round.bitrateLimited(value: String(format: "%.1f", Double(maximum) / 1000.0))
|
|
}
|
|
return GGStrings.CameraMasks.Round.bitrateValue(value: String(format: "%.1f", value / 1000.0))
|
|
})
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.codec",
|
|
section: section,
|
|
order: 300,
|
|
title: { GGStrings.CameraMasks.Round.codec },
|
|
kind: .choice(.global(GGRoundVideoSettings.codec), options: [
|
|
GGMenuChoiceOption(value: GGRoundVideoCodec.h264.rawValue, title: { GGStrings.CameraMasks.Round.codecH264 }),
|
|
GGMenuChoiceOption(value: GGRoundVideoCodec.hevc.rawValue, title: { GGStrings.CameraMasks.Round.codecHevc })
|
|
])
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.frameRate",
|
|
section: section,
|
|
order: 400,
|
|
title: { GGStrings.CameraMasks.Round.frameRate },
|
|
subtitle: { GGStrings.CameraMasks.Round.frameRateInfo },
|
|
kind: .choice(.global(GGRoundVideoSettings.frameRate), options: [
|
|
GGMenuChoiceOption(value: "30", title: { GGStrings.CameraMasks.Round.fps(fps: "30") }),
|
|
GGMenuChoiceOption(value: "60", title: { GGStrings.CameraMasks.Round.fps(fps: "60") })
|
|
])
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.stabilization",
|
|
section: section,
|
|
order: 500,
|
|
title: { GGStrings.CameraMasks.Round.stabilization },
|
|
kind: .choice(.global(GGRoundVideoSettings.stabilization), options: [
|
|
GGMenuChoiceOption(value: GGRoundVideoStabilization.standard.rawValue, title: { GGStrings.CameraMasks.Round.stabilizationStandard }),
|
|
GGMenuChoiceOption(value: GGRoundVideoStabilization.off.rawValue, title: { GGStrings.CameraMasks.Round.stabilizationOff }),
|
|
GGMenuChoiceOption(value: GGRoundVideoStabilization.cinematic.rawValue, title: { GGStrings.CameraMasks.Round.stabilizationCinematic })
|
|
])
|
|
))
|
|
}
|
|
|
|
// MARK: Camera
|
|
|
|
private static func registerCamera() {
|
|
let section = GGCameraMasksMenu.sectionCamera
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.rearCamera",
|
|
section: section,
|
|
order: 100,
|
|
title: { GGStrings.CameraMasks.Round.rearCamera },
|
|
subtitle: { GGStrings.CameraMasks.Round.rearCameraInfo },
|
|
kind: .toggle(.global(GGRoundVideoSettings.startWithRearCamera))
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.startZoom",
|
|
section: section,
|
|
order: 200,
|
|
title: { GGStrings.CameraMasks.Round.startZoom },
|
|
kind: .choice(.global(GGRoundVideoSettings.startZoom), options: [
|
|
GGMenuChoiceOption(value: GGRoundVideoStartZoom.one.rawValue, title: { GGStrings.CameraMasks.Round.zoomOne }),
|
|
GGMenuChoiceOption(value: GGRoundVideoStartZoom.two.rawValue, title: { GGStrings.CameraMasks.Round.zoomTwo }),
|
|
GGMenuChoiceOption(value: GGRoundVideoStartZoom.last.rawValue, title: { GGStrings.CameraMasks.Round.zoomLast })
|
|
])
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.rememberZoom",
|
|
section: section,
|
|
order: 300,
|
|
title: { GGStrings.CameraMasks.Round.rememberZoom },
|
|
subtitle: { GGStrings.CameraMasks.Round.rememberZoomInfo },
|
|
kind: .toggle(.global(GGRoundVideoSettings.rememberZoom))
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.zoomSlider",
|
|
section: section,
|
|
order: 400,
|
|
title: { GGStrings.CameraMasks.Round.zoomSlider },
|
|
subtitle: { GGStrings.CameraMasks.Round.zoomSliderInfo },
|
|
kind: .toggle(.global(GGRoundVideoSettings.zoomSlider))
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.rearHalfZoom",
|
|
section: section,
|
|
order: 450,
|
|
title: { GGStrings.CameraMasks.Round.rearHalfZoom },
|
|
subtitle: { GGStrings.CameraMasks.Round.rearHalfZoomInfo },
|
|
kind: .toggle(.global(GGRoundVideoSettings.rearHalfZoom)),
|
|
isVisible: { _ in GGRoundVideoConfig.hasUltraWideCamera }
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.flipAnimation",
|
|
section: section,
|
|
order: 500,
|
|
title: { GGStrings.CameraMasks.Round.flipAnimation },
|
|
subtitle: { GGStrings.CameraMasks.Round.flipAnimationInfo },
|
|
kind: .toggle(.global(GGRoundVideoSettings.flipAnimation))
|
|
))
|
|
}
|
|
|
|
// MARK: Recording
|
|
|
|
private static func registerRecording() {
|
|
let section = GGCameraMasksMenu.sectionRecording
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.countdown", section: section, order: 100, title: { GGStrings.CameraMasks.Round.countdown }, subtitle: { GGStrings.CameraMasks.Round.countdownInfo }, kind: .toggle(.global(GGRoundVideoSettings.countdown))))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.viewOnce", section: section, order: 200, title: { GGStrings.CameraMasks.Round.viewOnce }, subtitle: { GGStrings.CameraMasks.Round.viewOnceInfo }, kind: .toggle(.global(GGRoundVideoSettings.viewOnceByDefault))))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.sendAsVideo", section: section, order: 300, title: { GGStrings.CameraMasks.Round.sendAsVideo }, subtitle: { GGStrings.CameraMasks.Round.sendAsVideoInfo }, kind: .toggle(.global(GGRoundVideoSettings.sendAsRegularVideo))))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.saveToGallery", section: section, order: 400, title: { GGStrings.CameraMasks.Round.saveToGallery }, subtitle: { GGStrings.CameraMasks.Round.saveToGalleryInfo }, kind: .toggle(.global(GGRoundVideoSettings.saveCopyToGallery))))
|
|
}
|
|
|
|
// MARK: Sound
|
|
|
|
private static func registerSound() {
|
|
let section = GGCameraMasksMenu.sectionSound
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.microphone",
|
|
section: section,
|
|
order: 100,
|
|
title: { GGStrings.CameraMasks.Round.microphone },
|
|
kind: .choice(.global(GGRoundVideoSettings.microphoneMode), options: [
|
|
GGMenuChoiceOption(value: GGRoundVideoMicrophoneMode.standard.rawValue, title: { GGStrings.CameraMasks.Round.asTelegram }),
|
|
GGMenuChoiceOption(value: GGRoundVideoMicrophoneMode.videoRecording.rawValue, title: { GGStrings.CameraMasks.Round.microphoneVideo }),
|
|
GGMenuChoiceOption(value: GGRoundVideoMicrophoneMode.voiceChat.rawValue, title: { GGStrings.CameraMasks.Round.microphoneVoice })
|
|
])
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.normalize", section: section, order: 200, title: { GGStrings.CameraMasks.Round.normalize }, subtitle: { GGStrings.CameraMasks.Round.normalizeInfo }, kind: .toggle(.global(GGRoundVideoSettings.normalizeAudio))))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.mute", section: section, order: 300, title: { GGStrings.CameraMasks.Round.mute }, subtitle: { GGStrings.CameraMasks.Round.muteInfo }, kind: .toggle(.global(GGRoundVideoSettings.muteAudio))))
|
|
}
|
|
|
|
// MARK: Watermark (round)
|
|
|
|
static func fontOptions() -> [GGMenuChoiceOption] {
|
|
return [
|
|
GGMenuChoiceOption(value: GGWatermarkFont.regular.rawValue, title: { GGStrings.CameraMasks.Watermark.fontRegular }),
|
|
GGMenuChoiceOption(value: GGWatermarkFont.bold.rawValue, title: { GGStrings.CameraMasks.Watermark.fontBold }),
|
|
GGMenuChoiceOption(value: GGWatermarkFont.mono.rawValue, title: { GGStrings.CameraMasks.Watermark.fontMono }),
|
|
GGMenuChoiceOption(value: GGWatermarkFont.italic.rawValue, title: { GGStrings.CameraMasks.Watermark.fontItalic })
|
|
]
|
|
}
|
|
|
|
static func colorOptions() -> [GGMenuChoiceOption] {
|
|
return [
|
|
GGMenuChoiceOption(value: GGWatermarkColor.white.rawValue, title: { GGStrings.CameraMasks.Watermark.colorWhite }),
|
|
GGMenuChoiceOption(value: GGWatermarkColor.black.rawValue, title: { GGStrings.CameraMasks.Watermark.colorBlack }),
|
|
GGMenuChoiceOption(value: GGWatermarkColor.red.rawValue, title: { GGStrings.CameraMasks.Watermark.colorRed }),
|
|
GGMenuChoiceOption(value: GGWatermarkColor.yellow.rawValue, title: { GGStrings.CameraMasks.Watermark.colorYellow })
|
|
]
|
|
}
|
|
|
|
/// A row that edits a text setting in a prompt.
|
|
static func textRow(id: String, section: GGMenuSectionID, order: Int, title: @escaping () -> String, prompt: @escaping () -> String, setting: GGSetting<String>, limit: Int, isVisible: ((GGMenuContext) -> Bool)?) -> GGMenuRow {
|
|
return GGMenuRow(
|
|
id: id,
|
|
section: section,
|
|
order: order,
|
|
title: title,
|
|
kind: .action({ context in
|
|
context.present(promptController(context: context.context, text: prompt(), value: setting.value, placeholder: GGStrings.CameraMasks.Watermark.placeholder, characterLimit: limit, apply: { value in
|
|
if let value {
|
|
setting.set(value.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
}
|
|
}))
|
|
}),
|
|
isVisible: isVisible,
|
|
label: { _ in
|
|
let value = setting.value
|
|
return value.isEmpty ? GGStrings.CameraMasks.Watermark.noText : value
|
|
}
|
|
)
|
|
}
|
|
|
|
private static func registerWatermark() {
|
|
let section = GGCameraMasksMenu.sectionWatermark
|
|
let isOn: (GGMenuContext) -> Bool = { _ in
|
|
return GGRoundVideoSettings.watermark.value
|
|
}
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.watermark", section: section, order: 100, title: { GGStrings.CameraMasks.Round.watermark }, kind: .toggle(.global(GGRoundVideoSettings.watermark)), searchKeywords: { [GGStrings.CameraMasks.Watermark.keywords] }))
|
|
GGMenuRegistry.register(GGCameraMasksMenu.textRow(id: "cameraMasks.roundVideo.watermarkText", section: section, order: 200, title: { GGStrings.CameraMasks.Watermark.text }, prompt: { GGStrings.CameraMasks.Watermark.textPrompt }, setting: GGRoundVideoSettings.watermarkText, limit: 48, isVisible: isOn))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.watermarkPosition",
|
|
section: section,
|
|
order: 300,
|
|
title: { GGStrings.CameraMasks.Watermark.position },
|
|
kind: .choice(.global(GGRoundVideoSettings.watermarkPosition), options: [
|
|
GGMenuChoiceOption(value: GGRoundWatermarkPosition.bottom.rawValue, title: { GGStrings.CameraMasks.Watermark.positionBottom }),
|
|
GGMenuChoiceOption(value: GGRoundWatermarkPosition.top.rawValue, title: { GGStrings.CameraMasks.Watermark.positionTop }),
|
|
GGMenuChoiceOption(value: GGRoundWatermarkPosition.center.rawValue, title: { GGStrings.CameraMasks.Watermark.positionCenter })
|
|
]),
|
|
isVisible: isOn
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.watermarkFont", section: section, order: 400, title: { GGStrings.CameraMasks.Watermark.font }, kind: .choice(.global(GGRoundVideoSettings.watermarkFont), options: GGCameraMasksMenu.fontOptions()), isVisible: isOn))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.watermarkSize", section: section, order: 500, title: { GGStrings.CameraMasks.Watermark.size }, kind: .slider(.global(GGRoundVideoSettings.watermarkSize), range: 2.0 ... 12.0, step: 1.0, format: { value in GGStrings.CameraMasks.Watermark.percent(value: "\(Int(value))") }), isVisible: isOn))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.watermarkOpacity", section: section, order: 600, title: { GGStrings.CameraMasks.Watermark.opacity }, kind: .slider(.global(GGRoundVideoSettings.watermarkOpacity), range: 10.0 ... 100.0, step: 5.0, format: { value in GGStrings.CameraMasks.Watermark.percent(value: "\(Int(value))") }), isVisible: isOn))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.watermarkColor", section: section, order: 700, title: { GGStrings.CameraMasks.Watermark.color }, kind: .choice(.global(GGRoundVideoSettings.watermarkColor), options: GGCameraMasksMenu.colorOptions()), isVisible: isOn))
|
|
}
|
|
|
|
// MARK: Gallery (video → round, SG-F5)
|
|
|
|
private static func registerGallery() {
|
|
let section = GGCameraMasksMenu.sectionGallery
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.galleryAsRound", section: section, order: 100, title: { GGStrings.CameraMasks.Round.galleryAsRound }, subtitle: { GGStrings.CameraMasks.Round.galleryAsRoundInfo }, kind: .toggle(.global(GGRoundVideoSettings.galleryVideosAsRound))))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.roundVideo.galleryMenuItem", section: section, order: 150, title: { GGStrings.CameraMasks.Round.galleryMenuItem }, subtitle: { GGStrings.CameraMasks.Round.galleryMenuItemInfo }, kind: .toggle(.global(GGRoundVideoSettings.galleryMenuItem)), isVisible: { _ in !GGRoundVideoSettings.galleryVideosAsRound.value }))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.gallery.hideCamera", section: section, order: 200, title: { GGStrings.CameraMasks.Gallery.hideCamera }, subtitle: { GGStrings.CameraMasks.Gallery.hideCameraInfo }, kind: .toggle(.global(GGGalleryCameraSettings.hideCamera))))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.gallery.noLivePreview", section: section, order: 300, title: { GGStrings.CameraMasks.Gallery.noLivePreview }, subtitle: { GGStrings.CameraMasks.Gallery.noLivePreviewInfo }, kind: .toggle(.global(GGGalleryCameraSettings.noLivePreview)), isVisible: { _ in !GGGalleryCameraSettings.hideCamera.value }))
|
|
}
|
|
|
|
// MARK: Photo watermark
|
|
|
|
private static func registerPhotoWatermark() {
|
|
let section = GGCameraMasksMenu.sectionPhotoWatermark
|
|
let isOn: (GGMenuContext) -> Bool = { _ in
|
|
return GGPhotoWatermarkSettings.enabled.value
|
|
}
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.photoWatermark.preview", section: section, order: 50, title: { "" }, kind: .preview(.custom(height: 190.0, make: { _ in GGPhotoWatermarkPreviewView(frame: .zero) }))))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.photoWatermark.enabled", section: section, order: 100, title: { GGStrings.CameraMasks.Photo.enabled }, subtitle: { GGStrings.CameraMasks.Photo.enabledInfo }, kind: .toggle(.global(GGPhotoWatermarkSettings.enabled)), searchKeywords: { [GGStrings.CameraMasks.Watermark.keywords] }))
|
|
GGMenuRegistry.register(GGCameraMasksMenu.textRow(id: "cameraMasks.photoWatermark.text", section: section, order: 200, title: { GGStrings.CameraMasks.Watermark.text }, prompt: { GGStrings.CameraMasks.Watermark.textPrompt }, setting: GGPhotoWatermarkSettings.text, limit: 40, isVisible: isOn))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.photoWatermark.tiled", section: section, order: 300, title: { GGStrings.CameraMasks.Photo.tiled }, kind: .toggle(.global(GGPhotoWatermarkSettings.tiled)), isVisible: isOn))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.photoWatermark.position",
|
|
section: section,
|
|
order: 400,
|
|
title: { GGStrings.CameraMasks.Watermark.position },
|
|
kind: .choice(.global(GGPhotoWatermarkSettings.position), options: [
|
|
GGMenuChoiceOption(value: GGPhotoWatermarkPosition.bottomRight.rawValue, title: { GGStrings.CameraMasks.Photo.bottomRight }),
|
|
GGMenuChoiceOption(value: GGPhotoWatermarkPosition.bottomLeft.rawValue, title: { GGStrings.CameraMasks.Photo.bottomLeft }),
|
|
GGMenuChoiceOption(value: GGPhotoWatermarkPosition.topRight.rawValue, title: { GGStrings.CameraMasks.Photo.topRight }),
|
|
GGMenuChoiceOption(value: GGPhotoWatermarkPosition.topLeft.rawValue, title: { GGStrings.CameraMasks.Photo.topLeft }),
|
|
GGMenuChoiceOption(value: GGPhotoWatermarkPosition.center.rawValue, title: { GGStrings.CameraMasks.Watermark.positionCenter })
|
|
]),
|
|
isVisible: { context in isOn(context) && !GGPhotoWatermarkSettings.tiled.value }
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.photoWatermark.font", section: section, order: 500, title: { GGStrings.CameraMasks.Watermark.font }, kind: .choice(.global(GGPhotoWatermarkSettings.font), options: GGCameraMasksMenu.fontOptions()), isVisible: isOn))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.photoWatermark.size", section: section, order: 600, title: { GGStrings.CameraMasks.Watermark.size }, kind: .slider(.global(GGPhotoWatermarkSettings.size), range: 2.0 ... 12.0, step: 1.0, format: { value in GGStrings.CameraMasks.Watermark.percent(value: "\(Int(value))") }), isVisible: isOn))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.photoWatermark.opacity", section: section, order: 700, title: { GGStrings.CameraMasks.Watermark.opacity }, kind: .slider(.global(GGPhotoWatermarkSettings.opacity), range: 10.0 ... 100.0, step: 5.0, format: { value in GGStrings.CameraMasks.Watermark.percent(value: "\(Int(value))") }), isVisible: isOn))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.photoWatermark.color", section: section, order: 800, title: { GGStrings.CameraMasks.Watermark.color }, kind: .choice(.global(GGPhotoWatermarkSettings.color), options: GGCameraMasksMenu.colorOptions()), isVisible: isOn))
|
|
}
|
|
|
|
// MARK: Reset
|
|
|
|
private static func registerReset() {
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.roundVideo.reset",
|
|
section: GGCameraMasksMenu.sectionReset,
|
|
order: 100,
|
|
title: { GGStrings.CameraMasks.Round.reset },
|
|
kind: .action({ context in
|
|
let presentationData = context.presentationData
|
|
context.present(textAlertController(context: context.context, title: GGStrings.CameraMasks.Round.resetTitle, text: GGStrings.CameraMasks.Round.resetText, actions: [
|
|
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
|
|
TextAlertAction(type: .destructiveAction, title: GGStrings.CameraMasks.Round.resetConfirm, action: {
|
|
for setting in GGRoundVideoSettings.resettableKeys {
|
|
setting.reset()
|
|
}
|
|
})
|
|
]))
|
|
}),
|
|
isEnabled: { _ in
|
|
return GGRoundVideoSettings.resettableKeys.contains(where: { !$0.isDefault() })
|
|
},
|
|
isDestructive: true
|
|
))
|
|
}
|
|
|
|
// MARK: Calls
|
|
|
|
private static func registerCalls() {
|
|
let section = GGCameraMasksMenu.sectionCalls
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.calls.masks", section: section, order: 100, title: { GGStrings.CameraMasks.Calls.masks }, subtitle: { GGStrings.CameraMasks.Calls.masksInfo }, kind: .toggle(.global(GGMaskSettings.inCalls)), searchKeywords: { [GGStrings.CameraMasks.Masks.keywords] }))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.virtualCamera.enabled", section: section, order: 200, title: { GGStrings.CameraMasks.VirtualCamera.enabled }, subtitle: { GGStrings.CameraMasks.VirtualCamera.enabledInfo }, kind: .toggle(.global(GGVirtualCameraSettings.enabled)), searchKeywords: { [GGStrings.CameraMasks.VirtualCamera.keywords] }))
|
|
GGMenuRegistry.register(GGMenuRow(id: "cameraMasks.virtualCamera.loop", section: section, order: 300, title: { GGStrings.CameraMasks.VirtualCamera.loop }, kind: .toggle(.global(GGVirtualCameraSettings.loop)), isVisible: { _ in GGVirtualCameraSettings.enabled.value }))
|
|
GGMenuRegistry.register(
|
|
tab: GGMenuTabDescriptor(
|
|
id: GGCameraMasksMenu.virtualCameraTab,
|
|
parent: .tab(.mediaCalls),
|
|
title: { GGStrings.CameraMasks.VirtualCamera.library },
|
|
makeScreen: { context in
|
|
return ggMakeVirtualCameraScreen(context: context.context)
|
|
},
|
|
searchKeywords: { [GGStrings.CameraMasks.VirtualCamera.keywords] }
|
|
),
|
|
linkIn: GGCameraMasksMenu.sectionCalls,
|
|
order: 400,
|
|
subtitle: { GGStrings.CameraMasks.VirtualCamera.libraryInfo },
|
|
icon: .system("photo.on.rectangle"),
|
|
label: { _ in
|
|
if let item = GGVirtualCameraSettings.selectedItem {
|
|
return item.title
|
|
}
|
|
return nil
|
|
}
|
|
)
|
|
}
|
|
|
|
// MARK: Anonymous mode
|
|
|
|
private static func registerAnonymousMode() {
|
|
let tab = GGMenuTabID.privacyAnonymousMode
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.anonymous.active",
|
|
section: .main(of: tab),
|
|
order: 100,
|
|
title: { GGStrings.CameraMasks.Anonymous.toggle },
|
|
subtitle: { GGStrings.CameraMasks.Anonymous.toggleInfo },
|
|
kind: .toggle(.account(GGAnonymousMode.isActive)),
|
|
searchKeywords: { [GGStrings.CameraMasks.Anonymous.keywords] }
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.anonymous.mask",
|
|
section: .main(of: tab),
|
|
order: 200,
|
|
title: { GGStrings.CameraMasks.Anonymous.mask },
|
|
kind: .action({ context in
|
|
context.push(ggMakeMaskLibraryScreen(context: context.context, target: .anonymous))
|
|
}),
|
|
label: { _ in
|
|
return GGMaskEngine.title(GGMaskSelection(storage: GGAnonymousMode.mask.value))
|
|
}
|
|
))
|
|
GGMenuRegistry.register(GGMenuRow(
|
|
id: "cameraMasks.anonymous.voice",
|
|
section: .main(of: tab),
|
|
order: 300,
|
|
title: { GGStrings.CameraMasks.Anonymous.voice },
|
|
kind: .action({ context in
|
|
let presets = GGAnonymousMode.allVoicePresets()
|
|
let sheet = UIAlertController(title: GGStrings.CameraMasks.Anonymous.voice, message: nil, preferredStyle: .actionSheet)
|
|
for preset in presets {
|
|
let title = (preset.id == GGAnonymousMode.voice.value ? "✓ " : "") + preset.title()
|
|
sheet.addAction(UIAlertAction(title: title, style: .default, handler: { _ in
|
|
GGAnonymousMode.voice.set(preset.id)
|
|
}))
|
|
}
|
|
sheet.addAction(UIAlertAction(title: context.presentationData.strings.Common_Cancel, style: .cancel))
|
|
context.presentNative(sheet)
|
|
}),
|
|
isVisible: { _ in
|
|
return !GGAnonymousMode.voicePresets.isEmpty
|
|
},
|
|
label: { _ in
|
|
let value = GGAnonymousMode.voice.value
|
|
return GGAnonymousMode.allVoicePresets().first(where: { $0.id == value })?.title()
|
|
}
|
|
))
|
|
}
|
|
}
|