Power saving UI improvements

This commit is contained in:
Ali 2023-03-01 16:13:20 +04:00
parent 7091eea37e
commit d088449371
10 changed files with 172 additions and 27 deletions

View File

@ -905,6 +905,35 @@ public extension ContainedViewLayoutTransition {
}
}
func updateTintColor(layer: CALayer, color: UIColor, completion: ((Bool) -> Void)? = nil) {
if let current = layer.layerTintColor, current == color.cgColor {
completion?(true)
return
}
switch self {
case .immediate:
layer.layerTintColor = color.cgColor
completion?(true)
case let .animated(duration, curve):
let previousColor: CGColor = layer.layerTintColor ?? UIColor.clear.cgColor
layer.layerTintColor = color.cgColor
layer.animate(
from: previousColor,
to: color.cgColor,
keyPath: "contentsMultiplyColor",
timingFunction: curve.timingFunction,
duration: duration,
delay: 0.0,
mediaTimingFunction: curve.mediaTimingFunction,
removeOnCompletion: true,
additive: false,
completion: completion
)
}
}
func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)? = nil) {
if layer.contentsRect == contentsRect {
if let completion = completion {

View File

@ -40,6 +40,7 @@
@property (nonatomic, assign) CGFloat dotSize;
@property (nonatomic, assign) bool enablePanHandling;
@property (nonatomic, assign) bool enableEdgeTap;
- (void)setValue:(CGFloat)value animated:(BOOL)animated;

View File

@ -16,6 +16,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
UIPanGestureRecognizer *_panGestureRecognizer;
UITapGestureRecognizer *_tapGestureRecognizer;
UITapGestureRecognizer *_edgeTapGestureRecognizer;
UITapGestureRecognizer *_doubleTapGestureRecognizer;
UIColor *_backColor;
@ -75,6 +76,10 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
_tapGestureRecognizer.enabled = false;
[self addGestureRecognizer:_tapGestureRecognizer];
_edgeTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleEdgeTap:)];
_edgeTapGestureRecognizer.enabled = false;
[self addGestureRecognizer:_edgeTapGestureRecognizer];
_doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
_doubleTapGestureRecognizer.numberOfTapsRequired = 2;
[self addGestureRecognizer:_doubleTapGestureRecognizer];
@ -195,7 +200,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
}
if (self.displayEdges) {
CGContextSetFillColorWithColor(context, _startColor.CGColor);
CGContextSetFillColorWithColor(context, _backColor.CGColor);
[self drawRectangle:endFrame cornerRadius:self.trackCornerRadius context:context];
}
@ -510,6 +515,46 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
}
}
- (void)setEnableEdgeTap:(bool)enableEdgeTap {
_enableEdgeTap = enableEdgeTap;
_edgeTapGestureRecognizer.enabled = enableEdgeTap;
}
- (void)handleEdgeTap:(UITapGestureRecognizer *)gestureRecognizer {
bool changed = false;
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
CGPoint touchLocation = [gestureRecognizer locationInView:self];
CGFloat edgeWidth = 16.0f;
if (touchLocation.x < edgeWidth || touchLocation.x > self.bounds.size.width - edgeWidth) {
CGRect knobRect = CGRectInset(self.knobView.frame, -8.0, -8.0);
if (!CGRectContainsPoint(knobRect, touchLocation)) {
if (touchLocation.x < edgeWidth) {
[self setValue:_minimumValue];
} else {
[self setValue:_maximumValue];
}
changed = true;
}
}
}
if (changed)
{
if (self.interactionBegan != nil)
self.interactionBegan();
[self setNeedsLayout];
[self sendActionsForControlEvents:UIControlEventValueChanged];
if (self.interactionEnded != nil)
self.interactionEnded();
[_feedbackGenerator selectionChanged];
[_feedbackGenerator prepare];
}
}
- (void)handleDoubleTap:(UITapGestureRecognizer *)__unused gestureRecognizer
{
if (self.reset != nil)

View File

@ -17,8 +17,8 @@ enum ItemType: CaseIterable {
case loopStickers
case loopEmoji
case fullTranslucency
case extendBackgroundWork
case autodownloadInBackground
case extendBackgroundWork
var settingsKeyPath: WritableKeyPath<EnergyUsageSettings, Bool> {
switch self {
@ -63,7 +63,7 @@ enum ItemType: CaseIterable {
case .loopEmoji:
return (
"Settings/Power/PowerIconEmoji",
"Emoli Animations",
"Emoji Animations",
"Loop animated emoji in messages, reactions, statuses."
)
case .fullTranslucency:
@ -242,7 +242,7 @@ private func energeSavingSettingsScreenEntries(
entries.append(.itemsHeader)
for type in ItemType.allCases {
entries.append(.item(index: entries.count, type: type, value: settings.energyUsageSettings[keyPath: type.settingsKeyPath], enabled: itemsEnabled))
entries.append(.item(index: entries.count, type: type, value: settings.energyUsageSettings[keyPath: type.settingsKeyPath] && itemsEnabled, enabled: itemsEnabled))
}
return entries

View File

@ -73,6 +73,7 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode {
private let batteryImage: UIImage?
private let batteryBackgroundNode: ASImageNode
private let batteryForegroundNode: ASImageNode
private var item: EnergyUsageBatteryLevelItem?
private var layoutParams: ListViewItemLayoutParams?
@ -96,6 +97,7 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode {
self.batteryImage = UIImage(bundleImageName: "Settings/UsageBatteryFrame")
self.batteryBackgroundNode = ASImageNode()
self.batteryForegroundNode = ASImageNode()
super.init(layerBacked: false, dynamicBounce: false)
@ -103,12 +105,14 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode {
self.addSubnode(self.rightTextNode)
self.addSubnode(self.centerTextNode)
self.addSubnode(self.batteryBackgroundNode)
self.addSubnode(self.batteryForegroundNode)
}
override func didLoad() {
super.didLoad()
let sliderView = TGPhotoEditorSliderView()
sliderView.enableEdgeTap = true
sliderView.enablePanHandling = true
sliderView.trackCornerRadius = 1.0
sliderView.lineSize = 4.0
@ -223,6 +227,7 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode {
centralMeasureText = "When Below 99%"
strongSelf.batteryBackgroundNode.isHidden = false
}
strongSelf.batteryForegroundNode.isHidden = strongSelf.batteryBackgroundNode.isHidden
strongSelf.centerTextNode.attributedText = NSAttributedString(string: centralText, font: Font.regular(16.0), textColor: item.theme.list.itemPrimaryTextColor)
strongSelf.centerMeasureTextNode.attributedText = NSAttributedString(string: centralMeasureText, font: Font.regular(16.0), textColor: item.theme.list.itemPrimaryTextColor)
@ -254,15 +259,41 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode {
let contentRect = CGRect(origin: CGPoint(x: 3.0, y: (size.height - 9.0) * 0.5), size: CGSize(width: 20.8, height: 9.0))
context.addPath(UIBezierPath(roundedRect: contentRect, cornerRadius: 2.0).cgPath)
context.clip()
context.setFillColor(UIColor(rgb: 0xFF3B30).cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: contentRect.origin, size: CGSize(width: contentRect.width * CGFloat(item.value) / 100.0, height: contentRect.height)), cornerRadius: 1.0).cgPath)
context.fillPath()
}
UIGraphicsPopContext()
})
strongSelf.batteryForegroundNode.image = generateImage(frameImage.size, rotatedContext: { size, context in
UIGraphicsPushContext(context)
context.clear(CGRect(origin: CGPoint(), size: size))
let contentRect = CGRect(origin: CGPoint(x: 3.0, y: (size.height - 9.0) * 0.5), size: CGSize(width: 20.8, height: 9.0))
context.addPath(UIBezierPath(roundedRect: contentRect, cornerRadius: 2.0).cgPath)
context.clip()
context.setFillColor(UIColor.white.cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: contentRect.origin, size: CGSize(width: contentRect.width * CGFloat(item.value) / 100.0, height: contentRect.height)), cornerRadius: 1.0).cgPath)
context.fillPath()
UIGraphicsPopContext()
})
let batteryColor: UIColor
if item.value <= 20 {
batteryColor = UIColor(rgb: 0xFF3B30)
} else {
batteryColor = item.theme.list.itemSwitchColors.positiveColor
}
if strongSelf.batteryForegroundNode.layer.layerTintColor == nil {
strongSelf.batteryForegroundNode.layer.layerTintColor = batteryColor.cgColor
} else {
ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).updateTintColor(layer: strongSelf.batteryForegroundNode.layer, color: batteryColor)
}
strongSelf.batteryBackgroundNode.frame = CGRect(origin: CGPoint(x: centerFrame.minX + centerMeasureTextSize.width + 4.0, y: floor(centerFrame.midY - frameImage.size.height * 0.5)), size: frameImage.size)
strongSelf.batteryForegroundNode.frame = strongSelf.batteryBackgroundNode.frame
}
if let sliderView = strongSelf.sliderView {

View File

@ -143,9 +143,9 @@ private final class KeepMediaDurationPickerItemNode: ListViewItemNode {
sliderView.value = CGFloat(value)
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.itemSwitchColors.frameColor
sliderView.startColor = item.theme.list.itemSwitchColors.frameColor
sliderView.trackColor = item.theme.list.itemAccentColor
sliderView.backColor = item.theme.list.itemSwitchColors.frameColor.blitOver(item.theme.list.itemBlocksBackgroundColor, alpha: 1.0)
sliderView.startColor = item.theme.list.itemSwitchColors.frameColor.blitOver(item.theme.list.itemBlocksBackgroundColor, alpha: 1.0)
sliderView.trackColor = item.theme.list.itemAccentColor.blitOver(item.theme.list.itemBlocksBackgroundColor, alpha: 1.0)
sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme)
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0))

View File

@ -57,6 +57,7 @@ private final class ThemeSettingsControllerArguments {
let openTextSize: () -> Void
let openBubbleSettings: () -> Void
let openPowerSavingSettings: () -> Void
let openStickersAndEmoji: () -> Void
let toggleLargeEmoji: (Bool) -> Void
let disableAnimations: (Bool) -> Void
let selectAppIcon: (PresentationAppIcon) -> Void
@ -64,7 +65,7 @@ private final class ThemeSettingsControllerArguments {
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context
self.selectTheme = selectTheme
self.openThemeSettings = openThemeSettings
@ -76,6 +77,7 @@ private final class ThemeSettingsControllerArguments {
self.openTextSize = openTextSize
self.openBubbleSettings = openBubbleSettings
self.openPowerSavingSettings = openPowerSavingSettings
self.openStickersAndEmoji = openStickersAndEmoji
self.toggleLargeEmoji = toggleLargeEmoji
self.disableAnimations = disableAnimations
self.selectAppIcon = selectAppIcon
@ -101,6 +103,7 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
case accentColor
case icon
case powerSaving
case stickersAndEmoji
case largeEmoji
case animations
@ -126,6 +129,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case iconHeader(PresentationTheme, String)
case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], Bool, String?)
case powerSaving
case stickersAndEmoji
case otherHeader(PresentationTheme, String)
case largeEmoji(PresentationTheme, String, Bool)
case animations(PresentationTheme, String, Bool)
@ -141,7 +145,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ThemeSettingsControllerSection.message.rawValue
case .iconHeader, .iconItem:
return ThemeSettingsControllerSection.icon.rawValue
case .powerSaving:
case .powerSaving, .stickersAndEmoji:
return ThemeSettingsControllerSection.message.rawValue
case .otherHeader, .largeEmoji, .animations, .animationsInfo:
return ThemeSettingsControllerSection.other.rawValue
@ -170,18 +174,20 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return 8
case .powerSaving:
return 9
case .iconHeader:
case .stickersAndEmoji:
return 10
case .iconItem:
case .iconHeader:
return 11
case .otherHeader:
case .iconItem:
return 12
case .largeEmoji:
case .otherHeader:
return 13
case .animations:
case .largeEmoji:
return 14
case .animationsInfo:
case .animations:
return 15
case .animationsInfo:
return 16
}
}
@ -259,6 +265,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case .stickersAndEmoji:
if case .stickersAndEmoji = rhs {
return true
} else {
return false
}
case let .otherHeader(lhsTheme, lhsText):
if case let .otherHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -338,6 +350,11 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: "Animations", label: "", labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openPowerSavingSettings()
})
case .stickersAndEmoji:
//TODO:localize
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: "Stickers and Emoji", label: "", labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openStickersAndEmoji()
})
case let .otherHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .largeEmoji(_, title, value):
@ -397,6 +414,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
entries.append(.textSize(presentationData.theme, strings.Appearance_TextSizeSetting, textSizeValue))
entries.append(.bubbleSettings(presentationData.theme, strings.Appearance_BubbleCornersSetting, ""))
entries.append(.powerSaving)
entries.append(.stickersAndEmoji)
if !availableAppIcons.isEmpty {
entries.append(.iconHeader(presentationData.theme, strings.Appearance_AppIcon.uppercased()))
@ -460,6 +478,9 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let removedThemeIndexesPromise = Promise<Set<Int64>>(Set())
let removedThemeIndexes = Atomic<Set<Int64>>(value: Set())
let archivedPacks = Promise<[ArchivedStickerPackItem]?>()
archivedPacks.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks() |> map(Optional.init)))
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|> map { animatedEmoji -> [String: [StickerPackItem]] in
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
@ -515,6 +536,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})
}, openPowerSavingSettings: {
pushControllerImpl?(energySavingSettingsScreen(context: context))
}, openStickersAndEmoji: {
let _ = (archivedPacks.get() |> take(1) |> deliverOnMainQueue).start(next: { archivedStickerPacks in
pushControllerImpl?(installedStickerPacksController(context: context, mode: .general, archivedPacks: archivedStickerPacks, updatedPacks: { _ in
}))
})
}, toggleLargeEmoji: { largeEmoji in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedLargeEmoji(largeEmoji)

View File

@ -841,7 +841,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
interaction.openPaymentMethod()
}))*/
let stickersLabel: String
/*let stickersLabel: String
if let settings = data.globalSettings {
stickersLabel = settings.unreadTrendingStickerPacks > 0 ? "\(settings.unreadTrendingStickerPacks)" : ""
} else {
@ -849,7 +849,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
}
items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 5, label: .badge(stickersLabel, presentationData.theme.list.itemAccentColor), text: presentationData.strings.ChatSettings_StickersAndReactions, icon: PresentationResourcesSettings.stickers, action: {
interaction.openSettings(.stickers)
}))
}))*/
if let settings = data.globalSettings {
if settings.hasPassport {

View File

@ -244,9 +244,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.currentInAppNotificationSettings = Atomic(value: initialPresentationDataAndSettings.inAppNotificationSettings)
if automaticEnergyUsageShouldBeOnNow(settings: self.currentAutomaticMediaDownloadSettings) {
self.energyUsageSettings = self.currentAutomaticMediaDownloadSettings.energyUsageSettings
self.energyUsageSettings = EnergyUsageSettings.powerSavingDefault
} else {
self.energyUsageSettings = EnergyUsageSettings.default
self.energyUsageSettings = self.currentAutomaticMediaDownloadSettings.energyUsageSettings
}
let presentationData: Signal<PresentationData, NoError> = .single(initialPresentationDataAndSettings.presentationData)
@ -389,17 +389,17 @@ public final class SharedAccountContextImpl: SharedAccountContext {
strongSelf.currentAutomaticMediaDownloadSettings = next
if automaticEnergyUsageShouldBeOnNow(settings: next) {
strongSelf.energyUsageSettings = next.energyUsageSettings
strongSelf.energyUsageSettings = EnergyUsageSettings.powerSavingDefault
} else {
strongSelf.energyUsageSettings = EnergyUsageSettings.default
strongSelf.energyUsageSettings = next.energyUsageSettings
}
strongSelf.energyUsageAutomaticDisposable.set((automaticEnergyUsageShouldBeOn(settings: next)
|> deliverOnMainQueue).start(next: { value in
if let strongSelf = self {
if value {
strongSelf.energyUsageSettings = next.energyUsageSettings
strongSelf.energyUsageSettings = EnergyUsageSettings.powerSavingDefault
} else {
strongSelf.energyUsageSettings = EnergyUsageSettings.default
strongSelf.energyUsageSettings = next.energyUsageSettings
}
}
}))

View File

@ -283,6 +283,19 @@ public struct EnergyUsageSettings: Codable, Equatable {
)
}
public static var powerSavingDefault: EnergyUsageSettings {
return EnergyUsageSettings(
activationThreshold: 10,
autoplayVideo: false,
autoplayGif: false,
loopStickers: false,
loopEmoji: false,
fullTranslucency: false,
extendBackgroundWork: false,
autodownloadInBackground: false
)
}
public var activationThreshold: Int32
public var autoplayVideo: Bool