iOS Lock Screen, Powered by Microsoft
Где компромиссное решение в сто строк кода заменяется бескомпромиссным в пять прямоугольников со скруглёнными углами
Первым виджетом, который я набросал в Scriptable, был список приложений с виндовой фотографией дня (она же — "фотография на фоне bing.com")
Мне нравится "свежесть" экрана блокировки Windows и хотелось иметь что-то подобное и на телефоне. В бетах iOS 13 во встроенном приложении Shortcuts ("Команды") даже было действие для установки произвольной картинки изображением на домашний экран и/или экран блокировки. Но оно не дожило до релиза iOS 13 и не вернулось к релизу iOS 14
Компромисс
Так что единственной возможностью иметь что-то близкое к динамической обоине из интернета было сделать обновляющийся виджет. Ссылку на волшебный JSON со ссылкой на фотографию дня нашёл, само собой, на Stack Overflow. Список ссылок/приложений поверх картинки добавил чуть позже, как чтобы домашний экран не терял своё главное назначение — запуск этих приложений
Спустя ещё некоторое время, добавил кэширование фотографии дня, чтобы телефон не загружал её каждый раз, когда iOS решала обновить виджет. Я не сохранял промежуточные версии скрипта, но сейчас он выглядит как-то так (пропустить простынку кода и перейти к более интересной части):
const APPS = [
{ url: "photos-redirect://", name: "photos" },
{ url: "App-prefs://", name: "settings" },
{ separator: true },
{ url: "bear://" },
{ url: "things://" },
{ url: "reeder://" },
{ url: "twitterrific://" },
{ separator: true },
{ url: "spotify://" },
{ url: "overcast://" },
// { url: "music://" },
{ separator: true },
{ url: "onepassword://", name: "1password" },
{ url: "otpauth://", name: "authenticator" },
// { url: "diia.app://", name: "diia" },
];
const NOW = new Date;
const widget = new ListWidget();
widget.setPadding(0, 0, 0, 0);
widget.backgroundColor = Color.black();
// refresh every 4 hours
widget.refreshAfterDate = new Date(+NOW + 1000 * 60 * 60 * 4);
function formatDate(date, format) {
const formatter = new DateFormatter()
formatter.dateFormat = format
return formatter.string(date)
}
async function getTodaysImage() {
const fm = FileManager.local()
const imagesDirectory = `${fm.cacheDirectory()}/${Script.name()}`
if (!fm.fileExists(imagesDirectory)) {
fm.createDirectory(imagesDirectory, true)
}
const startdate = formatDate(NOW, "YYYYMMdd")
const metadataPath = `${imagesDirectory}/${startdate}.json`
let todaysImage
if (fm.fileExists(metadataPath)) {
todaysImage = JSON.parse(fm.readString(metadataPath))
} else {
const bingReq = new Request(
"https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1"
);
const { images } = await bingReq.loadJSON();
todaysImage = images[0];
if (startdate === todaysImage.startdate) {
fm.writeString(metadataPath, JSON.stringify(todaysImage))
}
}
const imagePath = `${imagesDirectory}/${todaysImage.startdate}.jpg`
if (fm.fileExists(imagePath)) {
return {
image: fm.readImage(imagePath),
copyrightlink: todaysImage.copyrightlink
}
}
const image = await new Request(
"https://www.bing.com" + todaysImage.url
).loadImage()
fm.writeImage(imagePath, image)
return {
image,
copyrightlink: todaysImage.copyrightlink,
};
}
const { image, copyrightlink } = await getTodaysImage();
widget.backgroundImage = image;
const row = widget.addStack();
row.layoutHorizontally();
const copyrightColumn = row.addStack();
copyrightColumn.layoutVertically();
copyrightColumn.addSpacer(null);
copyrightColumn.setPadding(16, 16, 16, 16);
const copyrightSymbol = SFSymbol.named("c.circle");
copyrightSymbol.applyThinWeight();
const c = copyrightColumn.addImage(copyrightSymbol.image);
c.url = copyrightlink;
c.resizable = false;
c.tintColor = Color.white();
row.addSpacer(null);
const column = row.addStack();
column.layoutVertically();
column.setPadding(14, 0, 12, 0);
for (const { separator, url, name } of APPS) {
if (separator) {
column.addSpacer(null);
continue;
}
const appRow = column.addStack();
appRow.addSpacer(null);
appRow.setPadding(2, 16, 4, 16);
const t = appRow.addText(name || url.replace(/:\/\/$/, ""));
t.rightAlignText();
t.textColor = Color.white();
t.shadowColor = Color.black();
t.shadowRadius = 4;
t.font = new Font("Menlo", 18);
appRow.url = url;
}
widget.presentLarge();
Получил, что хотел
К счастью, в iOS 14.3 в Shortcuts вернулось действие "Установить X в качестве обоев", так что "динамический экран блокировки" стал возможен без необходимости в компромиссах. Этот shortcut делает необходимый минимум и ничего больше:
- Постучаться по ссылке
https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1
- Получить из ответа адрес сегодняшней фотографии. Shortcuts считает по-человечески, начиная с единицы, так что "
url
первого элемента спискаimages
" доступно по ключуimages.1.url
, без программистских "мы считаем с нуля потому что наши деды считали с нуля" - Постучаться по ссылке из прошлого шага, дописав к ней
https://bing.com/
1. Для комбинирования фиксированного текста с результатами предыдущего действия есть специальное действие, но можно "срезать угол" и сделать это с помощью "волшебной переменной" - Собственно, установить новую обоину. Чтобы телефон не просил никаких подтверждений и тупо использовал центр фотографии, я выключил опцию "Показать окно просмотра"
Чтобы автоматически запускать эту команду каждый день (например, когда заходит солнце), в Shortcuts есть вкладка "Автоматизация":
И готово! Теперь каждый вечер мой экран блокировки обновляется красивой фотографией ☺️
Ссылки
P.S. // 2021-05-02
Иногда, автоматический кроппинг порождает шедевры:
/
в конце необязательный, но с ним скриншот читается немного получше ↩︎