chore: bump version to 1.2
This commit is contained in:
parent
f6bb33d193
commit
83f209e44a
101 changed files with 2419 additions and 608 deletions
|
|
@ -160,7 +160,7 @@ genrule(
|
|||
],
|
||||
)
|
||||
|
||||
minimum_os_version = "13.0"
|
||||
minimum_os_version = "26.5"
|
||||
|
||||
notificationServiceExtensionVersion = "v1"
|
||||
|
||||
|
|
@ -1850,7 +1850,7 @@ ios_application(
|
|||
xcodeproj(
|
||||
name = "Telegram_xcodeproj",
|
||||
bazel_path = telegram_bazel_path,
|
||||
project_name = "Telegram",
|
||||
project_name = "WinterGram",
|
||||
tags = ["manual"],
|
||||
top_level_targets = [
|
||||
top_level_target(":Telegram", target_environments = ["device", "simulator"]),
|
||||
|
|
|
|||
|
|
@ -2612,60 +2612,36 @@ typealias CMJImage = UIImage
|
|||
|
||||
// MARK: - Public Namespace
|
||||
|
||||
/// A lightweight utility for turning ordinary bitmap images into ``NSAdaptiveImageGlyph`` (Apple “Genmoji”) objects.
|
||||
///
|
||||
/// ## Overview
|
||||
/// The public API is minimal — a single call to generate a glyph and helper utilities for
|
||||
/// de/serialising attributed strings.
|
||||
///
|
||||
/// ```swift
|
||||
/// // Synchronous usage
|
||||
/// let glyph = try Customoji.makeGlyph(
|
||||
/// from: image,
|
||||
/// description: "alt text",
|
||||
/// cropToSquare: true
|
||||
/// )
|
||||
/// let attr = NSAttributedString(adaptiveImageGlyph: glyph, attributes: [:])
|
||||
/// ```
|
||||
///
|
||||
/// ## Requirements
|
||||
/// * iOS 18 / macOS 15
|
||||
/// * Swift 5.10+
|
||||
///
|
||||
/// ## Topics
|
||||
/// - Generating glyphs: ``makeGlyph(from:description:identifier:tileSizes:cropToSquare:heicQuality:)``
|
||||
/// - Generating glyphs asynchronously: ``makeGlyphAsync(from:description:identifier:tileSizes:cropToSquare:heicQuality:)``
|
||||
/// - Serialising: ``decompose(_:)`` / ``recompose(plain:ranges:blobs:)``
|
||||
///
|
||||
/// Utility for converting bitmaps into ``NSAdaptiveImageGlyph`` (Genmoji).
|
||||
public struct Customoji {
|
||||
|
||||
// MARK: Library-scoped Errors
|
||||
/// Errors that can be thrown by ``Customoji``.
|
||||
/// Errors thrown by ``Customoji``.
|
||||
public enum Error: Swift.Error, LocalizedError {
|
||||
/// The input image is not square and ``makeGlyph(from:description:identifier:tileSizes:cropToSquare:heicQuality:)`` was called with `cropToSquare == false`.
|
||||
/// Input image is not square and cropping is disabled.
|
||||
case nonSquare
|
||||
/// The longest side of the input image exceeds ``maxSide`` pixels (defaults to 4096).
|
||||
/// Input image exceeds ``maxSide`` pixels.
|
||||
case imageTooLarge
|
||||
/// Failed to create an in-memory HEIC container via *Image I/O*.
|
||||
/// Failed to create the in-memory HEIC container.
|
||||
case heicDestinationCreationFailed
|
||||
/// Failed to finalise the HEIC container after writing all tiles.
|
||||
/// Failed to finalize the HEIC container.
|
||||
case heicFinalizeFailed
|
||||
/// Internal resize operation failed when generating a tile.
|
||||
/// Resizing a tile failed.
|
||||
case imageScaleFailed
|
||||
/// A `CGImage` representation could not be extracted from the source image.
|
||||
/// Could not extract a `CGImage` from the source image.
|
||||
case cgImageUnavailable
|
||||
/// The `tileSizes` parameter was empty, not strictly ascending, contained duplicates, or exceeded the source size.
|
||||
/// `tileSizes` was empty, not strictly ascending, contained duplicates, or exceeded the source size.
|
||||
case invalidTileSizes
|
||||
/// No tiles were generated — normally unreachable unless all requested sizes were invalid.
|
||||
/// No tiles were generated; normally unreachable unless all requested sizes were invalid.
|
||||
case noTilesGenerated
|
||||
/// The current platform is neither UIKit- nor AppKit-based (unlikely in practice).
|
||||
/// Unsupported platform.
|
||||
case unsupportedPlatform
|
||||
/// The `heicQuality` parameter was outside the 0.0 … 1.0 range.
|
||||
/// `heicQuality` was outside the 0.0...1.0 range.
|
||||
case invalidHeicQuality
|
||||
/// Cropping or scaling to a square failed.
|
||||
case squareCropFailed
|
||||
|
||||
/// Textual description suitable for presenting to users.
|
||||
/// Localized error description.
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .nonSquare: return "Input image must be square."
|
||||
|
|
@ -2694,26 +2670,16 @@ public struct Customoji {
|
|||
}
|
||||
}
|
||||
|
||||
/// Default tile sizes (in pixels) officially used by Apple for Genmoji.
|
||||
/// Default Genmoji tile sizes.
|
||||
public static let defaultTileSizes = [40, 64, 96, 160, 320]
|
||||
|
||||
/// Maximum allowed side length (in pixels) to guard against excessive memory consumption.
|
||||
/// Maximum allowed side length, in pixels.
|
||||
private static let maxSide = 4_096
|
||||
|
||||
// MARK: - Synchronous Builder
|
||||
#if swift(>=5.10)
|
||||
@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, *)
|
||||
/// Generates an ``NSAdaptiveImageGlyph`` synchronously on the **current** thread.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - image: Source image. Can be rectangular; see `cropToSquare`.
|
||||
/// - description: A human-readable accessibility description (VoiceOver/Narrator).
|
||||
/// - identifier: A globally unique content identifier. Defaults to a fresh UUID.
|
||||
/// - tileSizes: Desired tile sizes, in pixels. Must be strictly ascending. Defaults to ``defaultTileSizes``.
|
||||
/// - cropToSquare: When `true`, the central square area is cropped if the input image is not square.
|
||||
/// - heicQuality: Lossy compression quality between `0.0` and `1.0`. Defaults to `0.8`.
|
||||
/// - Throws: ``Customoji/Error`` if validation or encoding fails.
|
||||
/// - Returns: A fully-formed glyph ready for attribution.
|
||||
/// Generate an ``NSAdaptiveImageGlyph`` from a bitmap.
|
||||
static func makeGlyph(
|
||||
from image: CMJImage,
|
||||
description: String,
|
||||
|
|
@ -2739,19 +2705,7 @@ public struct Customoji {
|
|||
// MARK: - AttributedString Utilities
|
||||
#if swift(>=5.10)
|
||||
@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, *)
|
||||
/// Breaks an ``NSAttributedString`` that may contain adaptive-image glyphs into three parts.
|
||||
///
|
||||
/// This is handy when you need to **serialise** rich text for network transport or persistent
|
||||
/// storage, because `NSAdaptiveImageGlyph` itself is not `Codable`.
|
||||
///
|
||||
/// ```swift
|
||||
/// let (plain, ranges, blobs) = Customoji.decompose(attrString)
|
||||
/// ```
|
||||
///
|
||||
/// - Returns: A tuple where:
|
||||
/// - plain: UTF-16 plain text.
|
||||
/// - ranges: An array mapping text ranges to glyph identifiers.
|
||||
/// - blobs: A dictionary mapping identifier → raw HEIC data.
|
||||
/// Split an attributed string into plain text, glyph ranges, and HEIC data.
|
||||
@inlinable
|
||||
public static func decompose(_ attr: NSAttributedString) -> (
|
||||
plain: String, ranges: [(NSRange, String)], blobs: [String: Data]
|
||||
|
|
@ -2773,13 +2727,7 @@ public struct Customoji {
|
|||
}
|
||||
|
||||
@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, *)
|
||||
/// Reassembles an attributed string previously produced by ``decompose(_:)``.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - plain: The plain UTF-16 text.
|
||||
/// - ranges: An array of `(NSRange, identifier)` pairs.
|
||||
/// - blobs: A dictionary of identifier → HEIC data. *Must* contain every identifier referenced by `ranges`.
|
||||
/// - Returns: A fully restored `NSAttributedString` with adaptive-image glyphs reinstated.
|
||||
/// Reassemble an attributed string from ``decompose(_:)`` output.
|
||||
public static func recompose(
|
||||
plain: String,
|
||||
ranges: [(NSRange, String)],
|
||||
|
|
@ -2805,8 +2753,7 @@ public struct Customoji {
|
|||
#endif
|
||||
|
||||
// MARK: - Helper: Pixel-Side Validation
|
||||
/// Ensures that the image’s **longer edge** does not exceed ``maxSide`` pixels, guarding against
|
||||
/// excessive memory usage during HEIC encoding.
|
||||
/// Reject images whose longer edge exceeds ``maxSide``.
|
||||
private static func validatePixelSide(of image: CMJImage) throws {
|
||||
#if canImport(UIKit)
|
||||
let scale = image.scale == 0 ? 1 : image.scale
|
||||
|
|
@ -2821,11 +2768,6 @@ public struct Customoji {
|
|||
}
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// Below this line lies the internal implementation.
|
||||
// Public-facing symbols have already been documented above.
|
||||
// =============================================================
|
||||
|
||||
// MARK: - Core Builder
|
||||
@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, *)
|
||||
extension Customoji {
|
||||
|
|
|
|||
|
|
@ -353,6 +353,8 @@
|
|||
<string>remote-notification</string>
|
||||
<string>voip</string>
|
||||
</array>
|
||||
<key>UIDesignRequiresCompatibility</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
|
|
|
|||
|
|
@ -402,22 +402,22 @@
|
|||
|
||||
// Tour
|
||||
"Tour.Title1" = "WinterGram";
|
||||
"Tour.Text1" = "The world's **fastest** messaging app.\nIt is **free** and **secure**.";
|
||||
"Tour.Text1" = "A native **AyuGram** port for iPhone.\nAn open-source **Telegram** fork.";
|
||||
|
||||
"Tour.Title2" = "Fast";
|
||||
"Tour.Text2" = "**WinterGram** delivers messages\nfaster than any other application.";
|
||||
"Tour.Title2" = "Telegram Fork";
|
||||
"Tour.Text2" = "Fully compatible with **Telegram**.\nYour chats, channels and accounts.";
|
||||
|
||||
"Tour.Title3" = "Powerful";
|
||||
"Tour.Text3" = "**WinterGram** has no limits on\nthe size of your media and chats.";
|
||||
"Tour.Title3" = "Power Tools";
|
||||
"Tour.Text3" = "Saved **deleted messages**, full **edit\nhistory** and a **hidden archive**.";
|
||||
|
||||
"Tour.Title4" = "Secure";
|
||||
"Tour.Text4" = "**WinterGram** keeps your messages\nsafe from hacker attacks.";
|
||||
"Tour.Title4" = "Make It Yours";
|
||||
"Tour.Text4" = "Deep **customization**, **Liquid Glass**,\nlocal **Premium** and a device **spoofer**.";
|
||||
|
||||
"Tour.Title5" = "Cloud-Based";
|
||||
"Tour.Text5" = "**WinterGram** lets you access your\nmessages from multiple devices.";
|
||||
"Tour.Title5" = "Open Source";
|
||||
"Tour.Text5" = "Free and fully **open-source**.\nInspect and build it yourself.";
|
||||
|
||||
"Tour.Title6" = "Free";
|
||||
"Tour.Text6" = "**WinterGram** provides free unlimited\ncloud storage for chats and media.";
|
||||
"Tour.Title6" = "Ghost Mode";
|
||||
"Tour.Text6" = "Read messages without sending\nread receipts, online or typing status.";
|
||||
|
||||
"Tour.StartButton" = "Start Messaging";
|
||||
|
||||
|
|
@ -16509,6 +16509,7 @@ Error: %8$@";
|
|||
"WinterGram.ShowMessageSeconds" = "Show Message Seconds";
|
||||
"WinterGram.ShowPeerID" = "Show Peer ID";
|
||||
"WinterGram.ShowRegistrationDate" = "Show Registration Date";
|
||||
"WinterGram.RegistrationDate" = "Registration date";
|
||||
"WinterGram.SingleCornerRadius" = "Single Corner Radius";
|
||||
"WinterGram.SomeSettingsWillTakeEffectAfterRestart" = "Some settings will take effect after restart.";
|
||||
"WinterGram.SpoofAppVersion" = "App Version";
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@ class UITests: XCTestCase {
|
|||
deleteTestAccount(phone: "9996625296")
|
||||
app.launch()
|
||||
|
||||
// Welcome screen — tap Start Messaging
|
||||
// Start messaging.
|
||||
let startButton = app.buttons["Auth.Welcome.StartButton"]
|
||||
XCTAssert(startButton.waitForExistence(timeout: 5.0))
|
||||
startButton.tap()
|
||||
|
||||
// Phone entry screen — enter test phone number
|
||||
// Enter the test phone number.
|
||||
let countryCodeField = app.textFields["Auth.PhoneEntry.CountryCodeField"]
|
||||
XCTAssert(countryCodeField.waitForExistence(timeout: 10.0))
|
||||
countryCodeField.tap()
|
||||
|
|
@ -57,12 +57,12 @@ class UITests: XCTestCase {
|
|||
XCTAssert(continueButton.isEnabled)
|
||||
continueButton.tap()
|
||||
|
||||
// Confirmation dialog — tap Continue
|
||||
// Confirm.
|
||||
let confirmButton = app.buttons["Auth.PhoneConfirm.ContinueButton"]
|
||||
XCTAssert(confirmButton.waitForExistence(timeout: 5.0))
|
||||
confirmButton.tap()
|
||||
|
||||
// Code entry screen — enter verification code
|
||||
// Enter the verification code.
|
||||
let codeEntryTitle = app.staticTexts["Auth.CodeEntry.Title"]
|
||||
XCTAssert(codeEntryTitle.waitForExistence(timeout: 15.0))
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ class UITests: XCTestCase {
|
|||
XCTAssert(codeField.waitForExistence(timeout: 3.0))
|
||||
codeField.typeText("22222")
|
||||
|
||||
// Set name screen — enter name and submit
|
||||
// Set the name.
|
||||
let firstNameField = app.textFields["Auth.SetName.FirstNameField"]
|
||||
XCTAssert(firstNameField.waitForExistence(timeout: 15.0))
|
||||
firstNameField.tap()
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
against an exported tgwatch source tree, optionally codesigns the result, and exposes
|
||||
it through the providers that `ios_application(watch_application = ...)` consumes:
|
||||
|
||||
* AppleBundleInfo — bundle metadata (the host reads only `.product_type`).
|
||||
* AppleEmbeddableInfo — `watch_bundles` (the zipped .app placed under Watch/).
|
||||
* AppleBundleInfo: bundle metadata (the host reads only `.product_type`).
|
||||
* AppleEmbeddableInfo: `watch_bundles` (the zipped .app placed under Watch/).
|
||||
|
||||
The watch source tree is the committed in-repo snapshot at `Telegram/WatchApp/` (tracked
|
||||
inputs). To update it, re-sync from the standalone tgwatch repo via
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# Worker for the apple_prebuilt_watchos_application Bazel rule.
|
||||
#
|
||||
# Builds the tgwatch watch app via xcodebuild (device, Release, UNSIGNED), then
|
||||
# — if a provisioning profile is supplied — codesigns the app and its nested
|
||||
# If a provisioning profile is supplied, codesigns the app and its nested
|
||||
# frameworks with the watchkitapp provisioning profile and a matching identity,
|
||||
# and finally zips the .app into the rule's output archive.
|
||||
#
|
||||
|
|
@ -45,7 +45,7 @@ DD="$(mktemp -d)"
|
|||
trap 'rm -rf "$DD"' EXIT
|
||||
|
||||
# Build from a writable copy so xcodebuild/SwiftPM never write into the (possibly
|
||||
# in-repo, read-only) source tree — e.g. SwiftPM's Package.resolved or the workspace.
|
||||
# in-repo, read-only) source tree, such as SwiftPM's Package.resolved or the workspace.
|
||||
# The tree is small (~12M); a plain cp on each (uncached) build is acceptable.
|
||||
WORKSRC="$DD/src"
|
||||
mkdir -p "$WORKSRC"
|
||||
|
|
@ -84,7 +84,7 @@ fi
|
|||
# Sign the watch app whenever a provisioning profile is available. When no explicit
|
||||
# identity is supplied, derive it from the certificate embedded in that profile, so
|
||||
# the watch app is signed with the same distribution/development identity as the host
|
||||
# app (resolved from the shared codesigning material) — required for App Store, where
|
||||
# app (resolved from the shared codesigning material), as required for App Store, where
|
||||
# every nested bundle must carry the Apple submission certificate. Without a profile
|
||||
# the app is left unsigned (the host does not re-sign it).
|
||||
if [ -n "$PROFILE" ]; then
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue