Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
20.12.2024
Дев-адвент 20: анімація з Transition
Так, дійсно, анімацію принципово легше зробити через вбудовані засоби. А саме .animation
- для змін стану, та .transition
- для появи та зникнення елементів. В моєму випадку, оскільки нові шари мапи саме зʼявляються, то тут потрібний .transition
.
Порівняно з React+CSS, мені подобається, що видалений елемент автоматично зберігається аж доки не відпрацює перехід. Просто додаєш/прибираєш елемент з промальовки, та сам запускається перехід. Навіть помітив, що якщо на кінець переходу він не зникає з екрана через прозорість або позицію, то некрасиво зависає ще на пару секунд.
Для конфігурації Transition задається “активний (чомусь) стан”, а насправді стан “неіснування”. От в моєму випадку тег зʼявляється зі своєї позиції в попередньому батьку, тому код переходу виглядає так:
.transition(
.offset(pathElement.offsetOfOriginalPosition)
.combined(with: .scale(pathElement.scaleOfOriginalPosition))
.combined(with: .opacity))
З хорошого — що в основі це працює чудово, навіть без всіх особливих налаштувань. З поганого — що все ж доводиться обчислювати відносне позиціювання та розміри, бо нутрощі мапи я малюю все ще через Canvas
(а кожен рівень навігації є новим Canvas
, з власними вкладеними тегами.) Ну й ще можна полірувати параметри анімації… нескінченно.
19.12.2024
Дев-адвент 19: дерево, тепер з анімацією
Сьогодні замотався з роботою, хотілося зробити щось веселе — а що може бути веселіше анімації? Занурився в анімаційні можливості SwiftUI та… щось навіть вийшло.
Як можна побачити зверху, я придумав ось такий спосіб навігації деревом: обираєш тег, він збільшується — але зберігає пропорції. Так легше відстежити звʼязок даних, ніж коли кожний вкладений тег розтягується (як тут) на весь екран. Тепер достатньо один раз викласти дерево, а вкладені рівні малювати суто через лінійні перетворення. Далі почалася сувора 2D-графіка без нормальних векторних типів…
Для такої задачі важливо не починати з кінця. Тому спочатку я тільки замінив логіку переходу, щоб малювати обрані теги зверху попереднього рівня — ще без масштабування та точно без анімації.
Найскладнішою частиною було правильно виконати перетворення, щоб тег зʼявлявся посередині екрану. Я обчислюю та запамʼятовую для кожного рівня зсув та масштаб відносно базової викладки (яку отримав ще на самому початку.) Їх потрібно врахувати й для малювання, й під час обробки натискань.
Це все вже нормально працює, а ось анімацію я тільки почав робити. Для неї є гарний компонент TimelineView - він перемальовує власний зміст за розкладом, хоч кожного кадру. Залишалося тільки досипати до моєї векторної алгебри ще й обчислення проміжного значення між “маленьким” та збільшеним розміром тегу. А ще проміжного значення прозорості фону. А ще лінійний перехід виглядає абсолютно ненатурально, та треба робити easing…
Ото сиджу тепер та думаю, марно це все було. Треба було кожний шар робити окремим Canvas
, позиціювати та масштабувати їх засобами SwiftUI, та анімувати їх теж через SwiftUI, як я колись робив пейджинатор. Тоді брудної роботи було б значно менше.
18.12.2024
Дев-адвент 18: життєвий цикл у SwiftUI
😵💫 Розбирався з ситуацією, що після зміни налаштувань не оновлюються запити, які ті налаштування зчитують. (Не з бази зчитують, а зі змінних.) Для того довелося все ж вивчити життєвий цикл компонент у SwiftUI. Знайшов прегарну книжку Thinking in SwiftUI (це справжній “посібник, який забули покласти”!) та з її допомогою вдалося второпати.
(Примітка. Я звик до реактивного UI в моделі React, особливо — з хуками. Все ще вважаю, що цей API, разом з лінтером для Rules of Hooks, найкращий у своєму класі. Там в мене ніколи не стає питання, що саме буде виконано при рендері, коли будуть викликані хуки, та який стан є тимчасовим, а який — сталим. У SwiftUI не все так гарно.)
😶🌫️ У SwiftUI компоненти є нащадками структури View. Недосвідченому у Swift програмісту може здатись, що наявність структури робить компонент сталою сутністю, бо це “щось на кшталт класу”. Але така інтуїція неправильна, бо навпаки, структура у Swift - це “тип-значення”, а відповідно, нащадки View не здатні зберігати стан.
🏭 Натомість кожен View є “фабрикою” для компонент. Центром її є атрибут body
- він аналогічний до функціональної компоненти в React. А все, що оголошено поза body
, є допоміжною логікою. Головним чином, ця логіка встановлює привʼязку до стану. Як і в React, стан зберігається… “десь там”, а ми до нього можемо звернутися — обгорткою @State
тощо. Важливо памʼятати, що весь зміст View
- це тільки аргументи до функції рендеру body
, та вони не є сталими.
Ось такий цікавий патерн Swift - структура є фактично замиканням навколо функції та її параметрів. А тільки що відкрив вікіпедію та побачив, що таке використання буквально називається замиканням! А я-то думав, що замиканням може бути тільки функція.
💣 А тепер, підступ. Під час виконання конструктора View
ще не має доступу до реального стану! Бо це ж тільки фабрика будується, а не компонент. Тому в конструкторі можна задати тільки початковий стан @State
, або, наприклад @Query з GRDB. Це зовсім не очевидно, бо конструктор дійсно викликається на кожну перемальовку, та в ньому можна присвоїти значення для змінної @State
, але працювати воно не буде. Та ані компілятор, ані лінтер не попереджають.
🪨 В моєму випадку з запитами знайшовся режим Query(constant:in:). В ньому, як я це розумію, замість стану, який можна змінювати, запит стає “константою” - а це значить, парадоксально, що коли його параметри в конструкторі змінюються, то й результат теж — а не тільки початковий стан. Таке.
17.12.2024
Дев-адвент 17: часограми
🏡 Поки задоволений головним екраном. На ньому ще колись зʼявляться навчальні підказки. Та ще гарно було б в понеділок бачити підсумки попереднього тижня — бо тільки по закінченню тижня його результати стають повними, та поки в цей самий момент вони зникають з виду.
🌈 Поставив у стовпчик часограми для багатьох тижнів і вони почали складатися в щось цікаве! Мені такий вигляд більше подобається, ніж лінійна діаграма. Взагалі в часограмах ще виводяться емодзі. Та так, придумав для цих смужок назву “часограма”.
𝓱 Придумав, щоб показувати приблизне значення годин, але підкреслити, що воно саме приблизне, писати не “h”, а “𝒽”. Ця дрібничка важлива для усвідомлення. Десь я такий спосіб бачив в математиці або фізиці, але не можу згадати, де. До речі, цей символ з блоку математичних символів в Unicode, та там ще багато “стильних” креслень для всіх латинських літер: “𝐡 ℎ 𝒉 𝗁 𝗵 𝘩 𝙝 𝒽 𝓱 𝔥 𝖍 𝚑 𝕙”, які доступні в більшості якісних шрифтів.
16.12.2024
Дев-адвент 16: домашній екран, та "спектрограма"
Поступово заповнюю домашній екран.
❎ Блок з незаповненими пробами — це дуже корисно (порівняно з просто список всіх проб, як в мене було.) Не тільки тому, що їх гарно видно, а й тому, що я можу приховати ті проби, які вже точно не зможу заповнити, та вони більше не мулятимуть очі. Бо зі списком я без кінця переглядав останні проби, щоб не забути про щось.
📝 Також зʼявилися нові ідеї про форму проби: там гарно було б показувати попередню та наступну проби, якщо такі є та заповнені. В мене вже попередня показується — бо зручно копіювати з неї теги, якщо вони повторюються. Але коли заповнюєш пропущені, то й наступна теж важлива, а також відстань між ними.
⚖️ А з показом поточної інформації доведеться йти по тонкому краю: з одного боку, хочеться бути корисним; з іншого — вся наша інформація приблизна, та чим “поточніше” вона, тим й приблизніша. Думаю, не буде нічого поганого, якщо просто показувати, скільки цього тижня було проб обраних тегів.
🤏 Тому мені подобається оцей новий прогрес-бар, який можна бачити вище. 100% на ньому — це тиждень. Сьогодні понеділок, отже, він майже не заповнений. Всі теги за сьогодні втиснуті в вузький простір. Це наче й не наочно, але: інформація за один день все одно статистично не значуща. Не треба її детально розглядувати. Чим більше заповниться прогрес-бар, тим більше буде на що дивитися.
(PS: вже зробив можливість виключити з графіків сон та незаповнені проби. А ще прогрес-бар наповнюється тією ж логікою, що й деревна мапа — зручно!)
🌈 Чим мені ще подобається така смуга — з неї можна зробити історичний перегляд — якщо скласти вертикально смуги для кожного тижня. А ще вона нагадує спектрограму (ту, що зі світла, а не зі звуку.) Та й за сенсом така собі “спектрограма життя”, га?
15.12.2024
Дев-адвент 15: домашній екран
👋 Займаюся дизайном головного екрану. По цей день мій стохастичний таймтрекер відкривається на список останніх проб. Частіше за все я відкриваю таймтрекер, щоб заповнити пропущені проби, тому це логічно. Але не дуже зручно: зокрема, старіші пропущені просто губляться, хоча інколи є можливість їх відновити. (Часто я просто маю змогу записати пробу в моменті та тримаю в голові на майбутнє.)
⚠️ Та й взагалі, список то сумний та нецікавий головний екран. А щоб зробити краще, треба спочатку зрозуміти потреби користувача. Одну з них я вже згадав — це заповнення пропущених проб. Тільки краще виводити лише пропущені, щоб їх не шукати. Та ще й наголосити візуально.
📌 Також, нарешті, треба придумати, як витягати користь з результатів, щодня. Це складніше, ніж здається. На щастя, в мене вже є бізнес-канва з цілями та проблемами користувачів. З цього (та власного досвіду!) придумав можливість “закріпити” тег та бачити його статистику на головному екрані.
💹 Ну й нарешті, хотілося б графік якийсь, щоб було яскраво. Можливо, це буде та сама деревна мапа поточного тижня, тільки стиснута. А ще є ідея її “лінеаризувати”, тобто перетворити на такий собі прогрес-бар.
14.12.2024
(Не)Дев-Адвент 2024: "вихідний": заміна акумулятора
⚡ Заміна автомобільного акумулятора це одна з найцікавіших у своїй нудності задач. Робиться раз в 3-5-7 років. Практично не впливає на поведінку автомобіля. Втім, ігнорувати несправний акумулятор не вийде аж ніяк.
🥶 Акумулятори стихійно виходять з ладу, коли холодає. Для здоровʼя акумулятора гірше жар двигуна, ніж зимний холод. Але на холоді складніше крутити стартер двигуна, от і проявляється. (Власне, чим холодніше місцевість, тим пильніше треба обирати акумулятор.) Далі залишається прикурити від сусіда, та їхати за новим. (А допомогти сусідові — теж святе діло.)
📏 З одного боку, акумулятор — не та деталь, де має сенс обирати з альтернатив. А точніше, майже певно можна ставити те, що буде в наявності в сервісі, куди ти приїдеш. Але! З іншого боку, в автомобільних акумуляторах значно більше різновидів, ніж “пальчикові чи пальчикові тонкі”. Головне — це, звісно, розмір та місткість, які повинні збігатися з тим, що є. Але потім виявляється, що є й різні технології (SFA/EFB/AGM), і стандарти корпусів (євро/азія), і розташування полюсів. А ще окремі вимоги накладає наявність старт/стопу. Одним словом, обирати все ж доведеться, та замовити акумулятор навмання буде ризиковано.
👀 Так само ризиковано покладатися на документацію, замість того, щоб відкрити капот та подивитися, а ще й поміряти рулеткою. Для моєї CX-5 я буквально ніде в інтернеті не бачив специфікації акумулятора, яке збігається з тим, що мені поставили на заводі. Ну то й що — головне замінити сумісним по факту.
🤔 Про що це я… А, взагалі от: сьогодні я міняв акумулятор. А за день до того було інше. Я трохи зʼїхав з системи задач, а точніше, з виконання задач, тобто вони напливали в контекстах. Та вчора вирішив все ж зробити хоч щось, зарядив FVP по 43 задачах в контексті “комп” та обрав найпріоритетнішу: “підібрати новий акумулятор”. Та сьогодні, коли машина не завелася, відчував себе підготованим, а не розгубленим. Ось так FVP допомагає дійсно зробити головне, навіть без примусового призначення пріоритетів та сортування.
13.12.2024
Дев-адвент 13: воно працює!
Взагалі сьогодні встановив macOS 15.2, а там таке… Відкриваю увечері XCode, а він каже — не можу відкритися - “XCode is updating”. Перший раз таке бачу… Як виявилося, це значило, що нова версія вже завантажилася та тепер розпаковується, на що пішло, мабуть, з пів години мого золотого часу. Класика XCode.
Але. Зробив тут знакову річ, яку давно хотів. Оскільки я сон тегаю автоматично з даних Apple Health (це не обовʼязково, але дуже зручно), але проби під час сну генеруються, як завжди, випадково. А якщо проба припадає на час сну, то вона отримує тег “сон”.
Про що це я… Виходить. для сну можна перевірити точні виміри та приблизні, обчислені з випадкових проб! Та підтвердити (чи спростувати) надійність методу. Затамувавши подих, дописав код для отримання сирих даних з Apple Health та побудови графіку.
(Тут не так все просто, до речі, бо в Apple Health всі дані сидять в формі своїх “проб”. В проби є час початку та кінцю. Та головне, що проби Apple Health можуть перетинатися за часом. Наприклад, коли виміри сну приходять з двох гаджетів з різними системами, то вони утворять подвійні записи. Але коли вже це знаєш, то достатньо просто дедублювати: відсортувати все за часом початку, а потім стежити за останнім покритим часом та відтинати все, що раніше за нього.)
І що виходить: воно працює! Реальна кількість сну майже завжди в інтервалі впевненості. Та хоч шуму на “випадковому” графіку немало, загальний тренд зберігається (у випадку сну — тренду на стабільність.) Такий графік буде доступний всім користувачам для тегу “сон”, щоб власноруч переконатись в справжності методу.
Рухаємось далі.
12.12.2024
Дев-адвент 12: пріоритети на мапі
Так, нарешті додав можливість виправити пріоритети на деревній мапі. А саме, в тегу можна обрати пріоритет від -2 до 2. Впливає він дуже просто: в алгоритмі “деревізації” сортування тепер відбувається спочатку за пріоритетом, а вже потім за кількістю згадок.
Наприклад: тег “комп” містить 50 годин, а “робота+комп” 40 годин. За базовим алгоритмом “робота” завжди буде в “компі”. Ще гірше, коли є ще “робота+телефон” без компа — тоді на загальній мапі ми побачимо роботу тільки по шматочках.
А от якщо “робота” отримує підвищений пріоритет, тоді в нас буде “робота” на 40 годин, всередині неї “комп” - 35 годин, “телефон” - 5 годин. А поруч з “роботою” будуть залишки “компа” - 10 годин, та залишки “телефона”.
Такий інструмент відразу робить мапу корисніше. Взагалі, як я бачу, є тільки кілька цікавих тегів, а решта їх деталізують. Наприклад, “комп” наче й цікаво для загального шокового ефекту, але якщо я хочу його знизити, то дивитися треба явно не на “роботу”.
Ба більше, якщо в “компі” панує тег “ютуб”, то можна і його винести на верхній рівень. Не тільки щоб бачити ютуб разом з компа, телефона, телевізора тощо, а й тому, що на кількість перегляду ютубу ми можемо прямо вплинути. А “комп” навпаки фальш-ціль, скільки не бажай “менше сидіти за компом”, без конкретних змін цього не відбудеться.
Зазначу ще, що почав виводити на мапі число годин… воно, звісно, визначається з дуже широким інтервалом. Наприклад, 51 год сну це насправді плюс-мінус 12. Але читати “39..64 год” мені не наочно. (Хоча то на мапі; в інших місцях як-от графік тегу по тижнях я його показую.)
11.12.2024
Дев-адвент 11: покращення деревної мапи
Сьогодні пройшовся мапою та зробив декілька покращень. Досі немає можливості вплинути на вкладеність, та це, певно, піде наступним, бо без неї користь має свою стелю. А поки працював над вірністю мапи в цілому
🐚 Зараз мапа викладається “зверху та зліва”, тобто найменші елементи опиняються в правому нижньому куті. Десь бачив мапу, яка йде спіраллю до центру, та це наче красиво. Зробив такий метод викладки в себе. От тільки на моїх даних спіраль виглядає неохайно. Тому поки повернув старий метод.
✌️ Помітив, що деколи заходиш в тег, а всередині — точно такий саме за розміром. Тобто ці два теги завжди згадувалися разом — та ніякої вкладеності немає. Тепер, щоб було більш наочно, виявляю такі випадки та групую в один вузол дерева. (Як виявляю? Дивлюся, коли в “батька” та “дитини” однакова кількість згадок.)
🫥 Також помітив, що під час викладки дітей тегу я втрачаю випадки, коли “батько” згадувався наодинці. (Нагадаю, що “діти” в моїй моделі — це теги, які згадувалися разом з “батьком”, але менше за нього разів.) Тепер підсумовую згадки всіх дітей разом, та якщо згадок батька ще більше, то для решти додаю ще одну “дитину” для “батька окремо”.
🔤 Та почав наповнювати мапу текстовим змістом. Це наче й нескладно, але потрібно спочатку переконатися, що текст влізе. Для того є цікавий метод resolve(), який точно вимірює текст в даному графічному контексті. (В JS/CSS такого часто не вистачає!) Потім перевіряю: якщо тексту забагато, залишаю тільки емодзі. В будь-якому разі планую ще додати тултіпи з повним текстом до кожного тегу.