Location picker fixes

This commit is contained in:
Ilya Laktyushin 2019-11-28 10:31:14 +04:00
parent dcd23387b3
commit bc4dc9807b
9 changed files with 347 additions and 58 deletions

View File

@ -499,4 +499,41 @@ extension DeviceContactAddressData {
}
return dictionary
}
public var string: String {
var array: [String] = []
if !self.street1.isEmpty {
array.append(self.street1)
}
if !self.city.isEmpty {
array.append(self.city)
}
if !self.state.isEmpty {
array.append(self.state)
}
if !self.country.isEmpty {
array.append(self.country)
}
if !self.postcode.isEmpty {
array.append(self.postcode)
}
return array.joined(separator: " ")
}
public var displayString: String {
var array: [String] = []
if !self.street1.isEmpty {
array.append(self.street1)
}
if !self.city.isEmpty {
array.append(self.city)
}
if !self.state.isEmpty {
array.append(self.state)
}
if !self.country.isEmpty {
array.append(self.country)
}
return array.joined(separator: ", ")
}
}

View File

@ -174,8 +174,8 @@ public func venueIcon(postbox: Postbox, type: String, background: Bool) -> Signa
c.setFillColor(backgroundColor.cgColor)
c.fillEllipse(in: CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
}
let boundsSize = CGSize(width: arguments.drawingRect.size.width - 4.0 * 2.0, height: arguments.drawingRect.size.height - 4.0 * 2.0)
if let image = iconImage, let cgImage = generateTintedImage(image: image, color: foregroundColor)?.cgImage {
let boundsSize = CGSize(width: arguments.drawingRect.size.width - 4.0 * 2.0, height: arguments.drawingRect.size.height - 4.0 * 2.0)
let fittedSize = image.size.aspectFitted(boundsSize)
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - fittedSize.width) / 2.0), y: floor((arguments.drawingRect.height - fittedSize.height) / 2.0)), size: fittedSize))
} else if isBuiltinIcon {
@ -191,7 +191,8 @@ public func venueIcon(postbox: Postbox, type: String, background: Bool) -> Signa
image = nil
}
if let image = image, let pinImage = generateTintedImage(image: image, color: foregroundColor), let cgImage = pinImage.cgImage {
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - pinImage.size.width) / 2.0), y: floor((arguments.drawingRect.height - pinImage.size.height) / 2.0)), size: pinImage.size))
let fittedSize = image.size.aspectFitted(boundsSize)
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - fittedSize.width) / 2.0), y: floor((arguments.drawingRect.height - fittedSize.height) / 2.0)), size: fittedSize))
}
}
}

View File

@ -32,6 +32,8 @@ static_library(
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/DeviceAccess:DeviceAccess",
"//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/PersistentStringHash:PersistentStringHash",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -0,0 +1,73 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import TelegramUIPreferences
import PersistentStringHash
import AccountContext
import Geocoding
public final class CachedGeocode: PostboxCoding {
public let latitude: Double
public let longitude: Double
public init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
public init(decoder: PostboxDecoder) {
self.latitude = decoder.decodeDoubleForKey("lat", orElse: 0.0)
self.longitude = decoder.decodeDoubleForKey("lon", orElse: 0.0)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeDouble(self.latitude, forKey: "lat")
encoder.encodeDouble(self.longitude, forKey: "lon")
}
}
private func cachedGeocode(postbox: Postbox, address: DeviceContactAddressData) -> Signal<CachedGeocode?, NoError> {
return postbox.transaction { transaction -> CachedGeocode? in
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: Int64(bitPattern: address.string.persistentHashValue))
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.cachedGeocodes, key: key)) as? CachedGeocode {
return entry
} else {
return nil
}
}
}
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
private func updateCachedGeocode(postbox: Postbox, address: DeviceContactAddressData, latitude: Double, longitude: Double) -> Signal<(Double, Double), NoError> {
return postbox.transaction { transaction -> (Double, Double) in
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: Int64(bitPattern: address.string.persistentHashValue))
let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.cachedGeocodes, key: key)
transaction.putItemCacheEntry(id: id, entry: CachedGeocode(latitude: latitude, longitude: longitude), collectionSpec: collectionSpec)
return (latitude, longitude)
}
}
public func geocodeAddress(postbox: Postbox, address: DeviceContactAddressData) -> Signal<(Double, Double)?, NoError> {
return cachedGeocode(postbox: postbox, address: address)
|> mapToSignal { cached -> Signal<(Double, Double)?, NoError> in
if let cached = cached {
return .single((cached.latitude, cached.longitude))
} else {
return geocodeLocation(dictionary: address.dictionary)
|> mapToSignal { coordinate in
if let (latitude, longitude) = coordinate {
return updateCachedGeocode(postbox: postbox, address: address, latitude: latitude, longitude: longitude)
|> map(Optional.init)
} else {
return .single(nil)
}
}
}
}
}

View File

@ -115,7 +115,7 @@ class LocationPinAnnotationView: MKAnnotationView {
}
self.iconNode = TransformImageNode()
self.iconNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 64.0, height: 64.0))
self.iconNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0))
self.smallIconNode = TransformImageNode()
self.smallIconNode.frame = CGRect(origin: CGPoint(x: 15.0, y: 15.0), size: CGSize(width: 26.0, height: 26.0))
@ -140,6 +140,14 @@ class LocationPinAnnotationView: MKAnnotationView {
self.annotation = annotation
}
var defaultZPosition: CGFloat {
if let annotation = self.annotation as? LocationPinAnnotation, let venueType = annotation.location?.venue?.type, ["home", "work"].contains(venueType) {
return -0.5
} else {
return -1.0
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@ -386,46 +394,70 @@ class LocationPinAnnotationView: MKAnnotationView {
}
func setCustom(_ custom: Bool, animated: Bool) {
if let annotation = self.annotation as? LocationPinAnnotation {
self.iconNode.setSignal(venueIcon(postbox: annotation.account.postbox, type: "", background: false))
}
if let avatarNode = self.avatarNode {
self.backgroundNode.addSubnode(avatarNode)
avatarNode.position = CGPoint(x: self.backgroundNode.frame.width / 2.0, y: self.backgroundNode.frame.height / 2.0 - 5.0)
}
self.shadowNode.position = CGPoint(x: UIScreenPixel, y: -36.0)
self.backgroundNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0)
self.iconNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0 - 5.0)
let transition = {
let color: UIColor
if custom, let annotation = self.annotation as? LocationPinAnnotation {
color = annotation.theme.list.itemAccentColor
} else {
color = .white
}
self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Location/PinBackground"), color: color)
self.avatarNode?.isHidden = custom
self.iconNode.isHidden = !custom
}
let completion = {
if !custom, let avatarNode = self.avatarNode {
self.addSubnode(avatarNode)
}
}
if animated {
self.animating = true
if let annotation = self.annotation as? LocationPinAnnotation {
self.iconNode.setSignal(venueIcon(postbox: annotation.account.postbox, type: "", background: false))
}
if let avatarNode = self.avatarNode {
self.backgroundNode.addSubnode(avatarNode)
avatarNode.position = CGPoint(x: self.backgroundNode.frame.width / 2.0, y: self.backgroundNode.frame.height / 2.0 - 5.0)
}
self.shadowNode.position = CGPoint(x: UIScreenPixel, y: -36.0)
self.backgroundNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0)
self.iconNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0 - 5.0)
Queue.mainQueue().after(0.01) {
UIView.transition(with: self.backgroundNode.view, duration: 0.2, options: [.transitionCrossDissolve, .allowAnimatedContent], animations: {
let color: UIColor
if custom, let annotation = self.annotation as? LocationPinAnnotation {
color = annotation.theme.list.itemAccentColor
} else {
color = .white
}
self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Location/PinBackground"), color: color)
self.avatarNode?.isHidden = custom
self.iconNode.isHidden = !custom
transition()
}) { finished in
if !custom, let avatarNode = self.avatarNode {
self.addSubnode(avatarNode)
}
completion()
self.animating = false
}
}
self.setNeedsLayout()
} else {
transition()
completion()
}
self.setNeedsLayout()
self.dotNode.isHidden = !custom
}
func animateAppearance() {
self.smallNode.transform = CATransform3DMakeScale(0.1, 0.1, 1.0)
let avatarNodeTransform = self.avatarNode?.transform
self.avatarNode?.transform = CATransform3DMakeScale(0.1, 0.1, 1.0)
UIView.animate(withDuration: 0.55, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], animations: {
self.smallNode.transform = CATransform3DIdentity
if let avatarNodeTransform = avatarNodeTransform {
self.avatarNode?.transform = avatarNodeTransform
}
}) { _ in
}
}
override func layoutSubviews() {
super.layoutSubviews()
@ -456,10 +488,8 @@ class LocationPinAnnotationView: MKAnnotationView {
if !self.appeared {
self.appeared = true
self.smallNode.transform = CATransform3DMakeScale(0.1, 0.1, 1.0)
UIView.animate(withDuration: 0.55, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], animations: {
self.smallNode.transform = CATransform3DIdentity
}) { _ in
if let annotation = annotation as? LocationPinAnnotation, annotation.location != nil {
self.animateAppearance()
}
}
}

View File

@ -33,6 +33,17 @@ private class PickerAnnotationContainerView: UIView {
}
}
private class LocationMapView: MKMapView, UIGestureRecognizerDelegate {
var customHitTest: ((CGPoint) -> Bool)?
@objc override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let customHitTest = self.customHitTest, customHitTest(gestureRecognizer.location(in: self)) {
return false
}
return true
}
}
final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
private let locationPromise = Promise<CLLocation?>(nil)
@ -41,8 +52,8 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
private let pinDisposable = MetaDisposable()
private var mapView: MKMapView? {
return self.view as? MKMapView
private var mapView: LocationMapView? {
return self.view as? LocationMapView
}
var returnedToUserLocation = true
@ -50,7 +61,9 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
var isDragging = false
var beganInteractiveDragging: (() -> Void)?
var endedInteractiveDragging: ((CLLocationCoordinate2D) -> Void)?
var annotationSelected: ((LocationPinAnnotation?) -> Void)?
var userLocationAnnotationSelected: (() -> Void)?
override init() {
self.pickerAnnotationContainerView = PickerAnnotationContainerView()
@ -59,7 +72,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
super.init()
self.setViewBlock({
return MKMapView()
return LocationMapView()
})
}
@ -78,6 +91,18 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
self.mapView?.isRotateEnabled = self.isRotateEnabled
self.mapView?.showsUserLocation = true
self.mapView?.showsPointsOfInterest = false
self.mapView?.customHitTest = { [weak self] point in
guard let strongSelf = self, let annotationView = strongSelf.customUserLocationAnnotationView else {
return false
}
if let annotationRect = annotationView.superview?.convert(annotationView.frame.insetBy(dx: -16.0, dy: -16.0), to: strongSelf.mapView), annotationRect.contains(point) {
strongSelf.userLocationAnnotationSelected?()
return true
}
return false
}
self.view.addSubview(self.pickerAnnotationContainerView)
}
@ -105,9 +130,16 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
self.ignoreRegionChanges = false
if isUserLocation && !self.returnedToUserLocation {
self.returnedToUserLocation = true
self.pickerAnnotationView?.setRaised(true, animated: true)
if isUserLocation {
if !self.returnedToUserLocation {
self.returnedToUserLocation = true
self.pickerAnnotationView?.setRaised(true, animated: true)
}
} else if self.hasPickerAnnotation, let customUserLocationAnnotationView = self.customUserLocationAnnotationView, customUserLocationAnnotationView.isHidden {
self.pickerAnnotationContainerView.isHidden = true
customUserLocationAnnotationView.setSelected(false, animated: false)
customUserLocationAnnotationView.isHidden = false
customUserLocationAnnotationView.animateAppearance()
}
}
@ -122,16 +154,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
self.returnedToUserLocation = false
self.beganInteractiveDragging?()
if self.hasPickerAnnotation {
self.customUserLocationAnnotationView?.isHidden = true
self.pickerAnnotationContainerView.isHidden = false
if let pickerAnnotationView = self.pickerAnnotationView, !pickerAnnotationView.isRaised {
pickerAnnotationView.setCustom(true, animated: true)
pickerAnnotationView.setRaised(true, animated: true)
}
self.resetAnnotationSelection()
self.resetScheduledPin()
}
self.switchToPicking(raise: true, animated: true)
break
}
}
@ -189,7 +212,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
view.addSubview(annotationView)
}
} else if let view = view as? LocationPinAnnotationView {
view.setZPosition(-1.0)
view.setZPosition(view.defaultZPosition)
}
}
}
@ -213,7 +236,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
if let view = view as? LocationPinAnnotationView {
Queue.mainQueue().after(0.2) {
view.setZPosition(-1.0)
view.setZPosition(view.defaultZPosition)
}
}
@ -264,6 +287,23 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
func switchToPicking(raise: Bool = false, animated: Bool) {
guard self.hasPickerAnnotation else {
return
}
self.customUserLocationAnnotationView?.isHidden = true
self.pickerAnnotationContainerView.isHidden = false
if let pickerAnnotationView = self.pickerAnnotationView, !pickerAnnotationView.isRaised {
pickerAnnotationView.setCustom(true, animated: animated)
if raise {
pickerAnnotationView.setRaised(true, animated: animated)
}
}
self.resetAnnotationSelection()
self.resetScheduledPin()
}
var customUserLocationAnnotationView: LocationPinAnnotationView? = nil
var userLocationAnnotation: LocationPinAnnotation? = nil {
didSet {

View File

@ -15,6 +15,7 @@ import AccountContext
import AppBundle
import CoreLocation
import Geocoding
import PhoneNumberFormat
private struct LocationPickerTransaction {
let deletions: [ListViewDeleteItem]
@ -323,13 +324,106 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
}
}
}
let personalAddresses = self.context.account.postbox.peerView(id: self.context.account.peerId)
|> mapToSignal { view -> Signal<(DeviceContactAddressData?, DeviceContactAddressData?)?, NoError> in
if let user = peerViewMainPeer(view) as? TelegramUser, let phoneNumber = user.phone {
return ((context.sharedContext.contactDataManager?.basicData() ?? .single([:])) |> take(1))
|> mapToSignal { basicData -> Signal<DeviceContactExtendedData?, NoError> in
var stableId: String?
let queryPhoneNumber = formatPhoneNumber(phoneNumber)
outer: for (id, data) in basicData {
for phoneNumber in data.phoneNumbers {
if formatPhoneNumber(phoneNumber.value) == queryPhoneNumber {
stableId = id
break outer
}
}
}
if let stableId = stableId {
return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil))
|> take(1)
|> map { extendedData -> DeviceContactExtendedData? in
return extendedData
}
} else {
return .single(nil)
}
}
|> map { extendedData -> (DeviceContactAddressData?, DeviceContactAddressData?)? in
if let extendedData = extendedData {
var homeAddress: DeviceContactAddressData?
var workAddress: DeviceContactAddressData?
for address in extendedData.addresses {
if address.label == "_$!<Home>!$_" {
homeAddress = address
} else if address.label == "_$!<Work>!$_" {
workAddress = address
}
}
return (homeAddress, workAddress)
} else {
return nil
}
}
} else {
return .single(nil)
}
}
let personalVenues: Signal<[TelegramMediaMap]?, NoError> = .single(nil)
|> then(
personalAddresses
|> mapToSignal { homeAndWorkAddresses -> Signal<[TelegramMediaMap]?, NoError> in
if let (homeAddress, workAddress) = homeAndWorkAddresses {
let home: Signal<(Double, Double)?, NoError>
let work: Signal<(Double, Double)?, NoError>
if let address = homeAddress {
home = geocodeAddress(postbox: context.account.postbox, address: address)
} else {
home = .single(nil)
}
if let address = workAddress {
work = geocodeAddress(postbox: context.account.postbox, address: address)
} else {
work = .single(nil)
}
return combineLatest(home, work)
|> map { homeCoordinate, workCoordinate -> [TelegramMediaMap]? in
var venues: [TelegramMediaMap] = []
if let (latitude, longitude) = homeCoordinate, let address = homeAddress {
venues.append(TelegramMediaMap(latitude: latitude, longitude: longitude, geoPlace: nil, venue: MapVenue(title: presentationData.strings.Map_Home, address: address.displayString, provider: nil, id: "home", type: "home"), liveBroadcastingTimeout: nil))
}
if let (latitude, longitude) = workCoordinate, let address = workAddress {
venues.append(TelegramMediaMap(latitude: latitude, longitude: longitude, geoPlace: nil, venue: MapVenue(title: presentationData.strings.Map_Work, address: address.displayString, provider: nil, id: "work", type: "work"), liveBroadcastingTimeout: nil))
}
return venues
}
} else {
return .single(nil)
}
}
)
let venues: Signal<[TelegramMediaMap]?, NoError> = .single(nil)
|> then(
filteredUserLocation
|> mapToSignal { location -> Signal<[TelegramMediaMap]?, NoError> in
if let location = location, location.horizontalAccuracy > 0 {
return nearbyVenues(account: context.account, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
|> map(Optional.init)
return combineLatest(nearbyVenues(account: context.account, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), personalVenues)
|> map { nearbyVenues, personalVenues -> [TelegramMediaMap]? in
var resultVenues: [TelegramMediaMap] = []
if let personalVenues = personalVenues {
for venue in personalVenues {
let venueLocation = CLLocation(latitude: venue.latitude, longitude: venue.longitude)
if venueLocation.distance(from: location) < 500 {
resultVenues.append(venue)
}
}
}
resultVenues.append(contentsOf: nearbyVenues)
return resultVenues
}
} else {
return .single(nil)
}
@ -436,6 +530,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
}
if updateMap {
strongSelf.headerNode.mapNode.setMapCenter(coordinate: coordinate, isUserLocation: false, animated: true)
strongSelf.headerNode.mapNode.switchToPicking(animated: false)
}
case let .venue(venue):
strongSelf.headerNode.mapNode.setMapCenter(coordinate: venue.coordinate, animated: true)
@ -497,7 +592,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout, strongSelf.listNode.scrollEnabled else {
return
}
let overlap: CGFloat = 6.0
strongSelf.listOffset = max(0.0, offset)
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap)))
@ -510,7 +604,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
guard let strongSelf = self else {
return
}
strongSelf.updateState { state in
var state = state
state.displayingMapModeOptions = false
@ -522,7 +615,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
guard let strongSelf = self else {
return
}
strongSelf.updateState { state in
var state = state
state.displayingMapModeOptions = false
@ -535,7 +627,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
guard let strongSelf = self else {
return
}
strongSelf.updateState { state in
var state = state
if case .selecting = state.selectedLocation {
@ -549,7 +640,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
guard let strongSelf = self else {
return
}
strongSelf.updateState { state in
var state = state
state.displayingMapModeOptions = false
@ -557,6 +647,18 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
return state
}
}
self.headerNode.mapNode.userLocationAnnotationSelected = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.updateState { state in
var state = state
state.displayingMapModeOptions = false
state.selectedLocation = .none
return state
}
}
}
deinit {

View File

@ -9,6 +9,7 @@ import WebSearchUI
import InstantPageCache
import SettingsUI
import WallpaperResources
import LocationUI
private var telegramUIDeclaredEncodables: Void = {
declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) })
@ -52,6 +53,7 @@ private var telegramUIDeclaredEncodables: Void = {
declareEncodable(MediaPlaybackStoredState.self, f: { MediaPlaybackStoredState(decoder: $0) })
declareEncodable(WebBrowserSettings.self, f: { WebBrowserSettings(decoder: $0) })
declareEncodable(IntentsSettings.self, f: { IntentsSettings(decoder: $0) })
declareEncodable(CachedGeocode.self, f: { CachedGeocode(decoder: $0) })
return
}()

View File

@ -60,6 +60,7 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
case cachedInstantPages = 1
case cachedWallpapers = 2
case mediaPlaybackStoredState = 3
case cachedGeocodes = 4
}
public struct ApplicationSpecificItemCacheCollectionId {
@ -67,6 +68,7 @@ public struct ApplicationSpecificItemCacheCollectionId {
public static let cachedInstantPages = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedInstantPages.rawValue)
public static let cachedWallpapers = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedWallpapers.rawValue)
public static let mediaPlaybackStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaPlaybackStoredState.rawValue)
public static let cachedGeocodes = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedGeocodes.rawValue)
}
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {