Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

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 такого часто не вистачає!) Потім перевіряю: якщо тексту забагато, залишаю тільки емодзі. В будь-якому разі планую ще додати тултіпи з повним текстом до кожного тегу.


10.12.2024

Дев-адвент 10: редагування тегів... та AWS Lambda

Сьогодні не те щоб найцікавіші функції, яких в кожному проєкті вистачає. (Хоча гарно, що простий CRUD на Swift UI робиться без зайвих перешкод.) Тому розкажу про іншу цікаву знахідку.

На вихідних одна AWSLambda накрилася та почала замість 15 секунд тривати 15 хвилин, тобто вилітати за браком часу. Лямбда ця запускається за розкладом та перекачує дані з Kafka ще кудись. Ніяких підстав бути такою повільною в неї не було.

В Кафки особливий підхід до споживачів, а саме: щоб уникнути повторної обробки даних, споживачі в групі ділять між собою потік задач. (Саме для того потоки — теми - topic діляться на розділи - partition.) А це своєю чергою значить, що коли споживач уходить, або зʼявляється новий, Кафка негайно виконує перебалансування групи. А якщо споживач просто відвалився, то щоб переконатися в його відсутності потрібний ще й тайм-аут. (Тому важливо завжди виходити з групи ввічливо.)

(До речі, це значить, що лямбди — не краще поєднання з Кафкою; сталий сервіс однозначно природніше. Втім, в моєму випадку на цілий сервіс задач було мало.)

Що, гадаю вийшло: в якийсь момент чергова лямбда “спіткнулася” - можливо, в Кафці йшли роботи, які самі викликають перебалансування — та зависла. Після того її наздогнала наступна лямбда, та вони почали ділити між собою групу в Кафці. Далі почався каскадний ефект: поки старі лямбди запускалися наново, зʼявлялися ще й нові та викликали нове перебалансування, причому кожна з них мала необмежену кількість спроб, тому вони нікуди не зникали, та проблема тривала аж до ручного втручання.

Виявилося, що запуск лямбд за розкладом — не така тривіальна справа. 1)тайм-аут лямбди повинен бути менше за інтервал розкладу - це, думаю, очевидно. 2) в налаштуваннях рівночасності треба вказати 1 рівночасне виконання, щоб уникнути набігання. 3) в налаштуваннях надійності вказати 0 повторних спроб, бо лямбда все одно запуститься за наступним розкладом. 4) там же ж вказати мінімальний вік подій, бо знов-таки немає сенсу обробляти старий розклад, коли завжди буде новий.