Files
buzz/mobile/ios/Runner/NativeTabler.swift
klopez4212 5c2ea2e507 feat(mobile): add full-screen profile setup with inline avatar editing (#8628)
Replace the iOS profile setup sheet with a full-screen name → avatar
flow. Require a display name, enable avatar Continue after Done, and let
Skip save the name without an avatar. After a failed save, Not now
continues without saving the draft.

Embed photo, emoji, and animated avatar editing with native Back/Done,
matching 220-point previews, a rounded-notch pencil edit badge, an entry
haptic, equal emoji-preview spacing, and 200 ms transitions that move
the avatar into its editor position without resizing. Apply appearance
changes live across setup and editing while preserving the draft. Keep
the camera exit rail stationary, use a solid shutter, and present photo
positioning full-screen with Back/Done. Preserve the main Flutter view
during presentation and hide underlying controls from VoiceOver while
editing.

Validation: Flutter analysis, 3,796 Flutter tests, and 106 native tests
passed. Built, installed, and launched the isolated iPhone preview;
inspected the native emoji/badge rendering. The local preview entry
point is excluded. Draft pending real login/join → profile-save
verification and owner verification of the updated device flow.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
2026-10-27 22:47:35 +00:00

94 lines
3.8 KiB
Swift

import UIKit
import CoreText
import Flutter
/// Renders the same bundled Tabler font used by Flutter into template images.
/// Legacy symbol keys are kept at the platform-channel boundary only.
enum NativeTabler {
private static let fontName: String? = {
let key = FlutterDartProject.lookupKey(forAsset: "assets/fonts/TablerIcons.ttf")
let appBundle = Bundle(url: Bundle.main.bundleURL.appendingPathComponent("Frameworks/App.framework"))
guard let url = appBundle?.url(forResource: key, withExtension: nil) ?? Bundle.main.url(forResource: key, withExtension: nil),
let provider = CGDataProvider(url: url as CFURL),
let font = CGFont(provider) else { return nil }
CTFontManagerRegisterGraphicsFont(font, nil)
return font.postScriptName as String?
}()
static func image(_ symbol: String, size: CGFloat = 20) -> UIImage? {
guard let code = glyphs[symbol], let scalar = UnicodeScalar(code),
let name = fontName, let font = UIFont(name: name, size: size) else {
assertionFailure("Missing Tabler icon: \(symbol)")
return nil
}
let text = String(scalar) as NSString
let attributes: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: UIColor.black]
let measured = text.size(withAttributes: attributes)
return UIGraphicsImageRenderer(size: CGSize(width: size, height: size)).image { _ in
text.draw(at: CGPoint(x: (size - measured.width) / 2, y: (size - measured.height) / 2), withAttributes: attributes)
}.withRenderingMode(.alwaysTemplate)
}
private static let glyphs: [String: UInt32] = [
"arrow.down": 0xea16, // arrow-down
"xmark": 0xeb55, // x
"xmark.circle.fill": 0xea6a, // circle-x
"chevron.backward": 0xea60, // chevron-left
"chevron.left": 0xea60, // chevron-left
"chevron.right": 0xea61, // chevron-right
"camera": 0xea54, // camera
"photo.on.rectangle": 0xfd4a, // images — matches BuzzIcons.images
"photo.on.rectangle.angled": 0xfd4a, // images — matches BuzzIcons.images
"paintpalette": 0xeb01, // palette
"drop": 0xea97, // droplet
"drop.fill": 0xea97, // droplet
"face.smiling": 0xeaf7, // mood-smile
"person": 0xeb4d, // user
"person.crop.circle": 0xeb4d, // user
"person.2": 0xebf2, // users
"rectangle.stack": 0xeb0a, // photo
"crop": 0xea85, // crop
"arrow.triangle.2.circlepath.camera": 0xee22, // camera-rotate
"circle": 0xea6b, // circle
"circle.fill": 0xea6b, // circle
"sun.max": 0xeb30, // sun
"moon": 0xeaf8, // moon
"ellipsis": 0xea95, // dots
"arrowshape.turn.up.left": 0xeb77, // arrow-back-up
"circle.lefthalf.filled": 0xf4a3, // sun-moon
"magnifyingglass": 0xeb1c, // search
"tray": 0xeac4, // inbox
"sparkles": 0xf6d7, // sparkles
"clock": 0xea70, // clock
"leaf": 0xed4f, // leaf
"fork.knife": 0xeeff, // tools-kitchen-2
"figure.run": 0xec82, // run
"airplane": 0xeb6f, // plane
"lightbulb": 0xea51, // bulb
"heart": 0xeabe, // heart
"flag": 0xeaa6, // flag
"video": 0xed22, // video
"mic": 0xeaf0, // microphone
"doc": 0xeaa4, // file
"plus": 0xeb0b, // plus
"building.2.crop.circle": 0xebf6, // building-community
"folder.badge.plus": 0xeaab, // folder-plus
"headphones": 0xeabd, // headphones
"line.3.horizontal.decrease": 0xeaa5, // filter
"square.and.pencil": 0xea98, // edit
"pencil": 0xea98, // edit
"doc.on.doc": 0xea7a, // copy
"trash": 0xeb41, // trash
"pin": 0xec9c, // pin
"pin.slash": 0xed5f, // pinned-off
"square.and.arrow.up": 0xeb21, // share
"link": 0xeade, // link
"checkmark": 0xea5e, // check
"checkmark.circle": 0xea67, // circle-check
"bell.slash": 0xece9, // bell-off
"bubble.left": 0xeaef, // message
"bubble.left.and.bubble.right": 0xeb6c, // messages
"arrow.uturn.backward": 0xeb77, // arrow-back-up
]
}