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

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

Підписатись на RSS
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

26.12.2024

Дев-адвент 26: цілісність дизайну та покращення пошуку

Нарешті взявся за остаточне впорядкування дизайну (ну, наскільки ці слова можна застосувати до цього MVP.) Якщо тег — це сірий округлий прямокутник, то таким він повинен бути всюди: тоді ясно, що це тег. Так само проби — більші округлі прямокутники з кольоровою крапкою: пропущено / заповнено / заповнено, але пізніше. Сюди можна витратити необмежену кількість часу, тому поки мета почати.

🎨 Відчуваю, що треба було б зробити палітру стилів, а не робити зміни там-сям, проте цим займуся пізніше. (У SwiftUI “палітра стилів” може існувати у вигляді набору модифікаторів.)

🏔️ Плюс чим далі, тим більше покращення UX тягнуть за собою додаткові покращення. Наприклад: є в мене автотегінг з Apple Health. Раніше було важко побачити його роботу в реальному часі, і це було… нормально. А тепер на головній є блок “незаповнені проби”, в якому видно, як стрибають спочатку порожні, а потім автозаповнені сном проби. Щоб було зрозуміло, тепер доведеться додати блок “автотегінг” з його результатами. І це гарно! Тільки роботи більшає.

🫥 На відео зверху видно новий UX додавання тегу. Просто до кінця списку результатів додаю пунктиром тег, який можна створити та відразу й додати. Також результати пошуку тепер спочатку показують теги зі збігом на початку (бо зазвичай саме так ми їх вводимо), а потім вже решту збігів.

🎅 Якщо все буде добре, планую до 1 січня все ж випуститися хоча б в TestFlight.


25.12.2024

Дев-адвент 25: пошук тегів з форми

Сьогодні переробив пошук з форми. Багато чого не ідеально, але каркас готовий, та вже є зручнішим за попередню реалізацію.

Головна проблема цієї форми — їй дуже тісно. На телефоні мало місця. Особливо коли відкриєш клавіатуру — чого на відео не видно, бо то на емуляторі. Тому в мене пошук був окремим екраном, де йому вже була воля.

Таке рішення позбавляло всяких крайових випадків, де форма розлізлася, але воно ніяк не було зручним. По-перше, треба екран відкрити, а потім закрити. По-друге, на ньому не видно, куди ж ці теги йдуть. Хотілося поєднати пошук прямо з формою.

На те спробував поки що заміняти блок “ймовірних тегів” результатами пошуку. (А ще — ховати — з анімацією! - попередній та наступний теги, бо вони тут зайві.) Та обмежити результати за кількістю. Та показувати теги “потоком”, а не по одному на рядок. Наче непогано — хоча сам UX пошукового рядка ще треба доробити — щоб було легко очистити.

Наступне, що тут треба переробити — це логіку обирання отих “ймовірних тегів”; хочу агрегувати дані про суміжність тегів, щоб знайти “ідеальне поєднання” між наступними, попередніми, та спільними тегами до тих тегів, що вже відомі.


24.12.2024

Дев-адвент 24: форма додавання проби

Освіжив форму додавання проби — ліворуч те як вона виглядала вже багато місяців, праворуч перша ітерація покращень.

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

PS. Найважче в реалізації дизайнів на SwiftUI - це знати інструментарій. Мабуть, так само як і з CSS. Бо коли не знаєш, наприклад, що можна отримати системний колір третинного фону як .background.tertiary, або як все ж працює викладка елементів (зовсім не як в CSS!), то виходитиме складно та незграбно.


23.12.2024

Дев-адвент 23: графік розкладу тегу

🌶️ Ще один з гостро потрібних графіків — це схема тижневого розкладу тегу. Ідея в тому, що за ним можна зрозуміти, де справжній розклад не збігається з бажаним, та скоригувати.

🔘 В мене вже був такий графік, але я малював на ньому кожну пробу окремою крапочкою. За “хмарою” крапок можна було побачити ціле, але коли їх стає забагато, то вже нічого не зрозуміло — тому хотів замінити цей графік на теплову карту.

З першого погляду, це легко — підрахував проби за днями та годинами та й показуй — це як пряма заміна крапок на їхню кількість. Втім, як видно зверху справа, абсолютна кількість нам мало корисна. Краще відносна кількість, тобто як часто використовують цей тег в дану годину, порівняно з іншими тегами

Можемо витягнути відносну кількість з SQL одним запитом: COUNT(NULLIF(tagId != 123, TRUE))/CAST(COUNT(*) AS FLOAT). Взагалі останнім часом починаю роботу в клієнті SQLite, а потім вже переношу запити в застосунок — в SQL швидше виходить ітерувати.

🌚 Графік посередині — вже значно ясніше, з нього відразу зрозуміло, що вночі я сплю. Зліва — те ж саме, але я прибрав мітки з частотою до 5%. На мою думку, так менше шуму, але: з іншими тегами такої ж чіткості не буває, все через вільний графік.

До речі, якщо дивитися на частоту, то також обовʼязково обмежувати дані за часом, бо чим довше вибірка, тим більше розбіжність та менше буде відносна частота кожного тегу (окрім тих, що мають суворий графік, тобто відбуваються точно в однаковий час.) Тому можна показувати, скажімо, дані за останній місяць, а з решти збудувати тренд, що буде ще корисніше.


22.12.2024

Дев-адвент 22: дрібні, але важливі виправлення

Хоч мій трекер збудований на швидкому введенні тегів в несподіваний момент — мушу зізнатися, що вже довгий час (може, з пів року чи більше) сповіщення не відпрацьовували. Тобто, вони зʼявлялися, але перехід в застосунок відбувався без відкриття форми додавання проби. Ба більше, довгий час відкриття застосунку зі сповіщення взагалі викидувало через критичну помилку. 😳

З помилкою я вже цього місяця розібрався. Це була одна з тих бісячих ситуацій у Swift, коли застосунок отримує EXC_BREAKPOINT в асемблерному коді (тобто системному, без вихідного тексту) - та піди зʼясуй, в чому справа. Виявилося, що версія метода делегату didReceive з async не працює з моделлю рівночасності. (Класична проблема з офіційними API, на жаль.) Тому, контрінтуїтивно, повернувся до старої версії — з колбеком — та це мене врятувало. (На жаль, щоб це дізнатися, довелося, як в пазлі, пробувати всі комбінації по черзі.)

Але форма все ще не відкривалася. Причому код, що її відкриває зі сповіщення, був той самий, що й зі списку — тобто проблема була десь посередині. Думав, що в бізнес-логіці — ну, може, я погано шукаю пробу, яку треба відкрити. Може, не встигаю створити (бо створюються тільки проби за минуле, тому по відкриттю на сповіщення про зараз проба створюється “just-in-time”.)

Та виявилося, ні. Це була ще одне непорозуміння життєвого циклу, цього разу — всього застосунку. Бо застосунок у SwiftUI - це нащадок структури App. А в ній, як і в компонентах, в конструкторі стан @State ще не має привʼязки. Та якщо в конструкторі створити делегат сповіщень та передати йому той стан, що є — то він не зможе редагувати справжній стан.

Розвʼязалося все просто (коли вже знаєш, як воно працює!) Переніс створення делегату до обробника onAppear - тобто з конструктора в body. І все! Форма відкривається вчасно.


21.12.2024

Дев-адвент 21: що робити з часом?

Нарешті наважився запустити застосунок на телефоні — там, де справжні дані. (Для того потрібна була стабільна схема бази, щоб не страждати потім з міграціями.) Що можу сказати — чудові покращення! Залишилось додати трохи сполучної тканини, та можна випускати.

Але поки задумався: як же ж дійсно залучити зібрані дані, щоб краще витрачати свої 24 години, 365 днів? Бо я саме заради цього все роблю. Ось кілька думок.

PS. Знаєте Steam Replay - звіт про те, в що грав цього року? В мене він не дуже обʼєктивний — по-перше, обліковку ділю з сином, тому в топі ігор Celeste та Super Meat Boy. По-друге, не всі мої ігри були в Steam… Зате всі є в тезі 🎮 gaming! Хочу зробити аналогічний звіт, але з даних трекера.


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, та там ще багато “стильних” креслень для всіх латинських літер: “𝐡 ℎ 𝒉 𝗁 𝗵 𝘩 𝙝 𝒽 𝓱 𝔥 𝖍 𝚑 𝕙”, які доступні в більшості якісних шрифтів.