├── Doc_Img
├── IMG_Widget.PNG
└── IMG_Widget_20201122.PNG
├── InvisibleWidgetPicture.js
├── WidgetHelloV2.js
├── WigetBlur.js
└── readme.md
/Doc_Img/IMG_Widget.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elizhd/IOS_Scriptable/da85b60c82c604066ea26d21680b83f701cdeb54/Doc_Img/IMG_Widget.PNG
--------------------------------------------------------------------------------
/Doc_Img/IMG_Widget_20201122.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elizhd/IOS_Scriptable/da85b60c82c604066ea26d21680b83f701cdeb54/Doc_Img/IMG_Widget_20201122.PNG
--------------------------------------------------------------------------------
/InvisibleWidgetPicture.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: red; icon-glyph: magic;
4 | // This widget was created by Max Zeryck @mzeryck
5 |
6 | if (config.runsInWidget) {
7 | let widget = new ListWidget()
8 | widget.backgroundImage = files.readImage(path)
9 |
10 | // You can your own code here to add additional items to the "invisible" background of the widget.
11 |
12 | Script.setWidget(widget)
13 | Script.complete()
14 |
15 | /*
16 | * The code below this comment is used to set up the invisible widget.
17 | * ===================================================================
18 | */
19 | } else {
20 |
21 | // Determine if user has taken the screenshot.
22 | var message
23 | message = "Before you start, go to your home screen and enter wiggle mode. Scroll to the empty page on the far right and take a screenshot."
24 | let exitOptions = ["Continue", "Exit to Take Screenshot"]
25 | let shouldExit = await generateAlert(message, exitOptions)
26 | if (shouldExit) return
27 |
28 | // Get screenshot and determine phone size.
29 | let img = await Photos.fromLibrary()
30 | let height = img.size.height
31 | let phone = phoneSizes()[height]
32 | if (!phone) {
33 | message = "It looks like you selected an image that isn't an iPhone screenshot, or your iPhone is not supported. Try again with a different image."
34 | await generateAlert(message, ["OK"])
35 | return
36 | }
37 |
38 | // Prompt for widget size and position.
39 | message = "What size of widget are you creating?"
40 | let sizes = ["Small", "Medium", "Large"]
41 | let size = await generateAlert(message, sizes)
42 | let widgetSize = sizes[size]
43 |
44 | message = "What position will it be in?"
45 | message += (height == 1136 ? " (Note that your device only supports two rows of widgets, so the middle and bottom options are the same.)" : "")
46 |
47 | // Determine image crop based on phone size.
48 | let crop = {
49 | w: "",
50 | h: "",
51 | x: "",
52 | y: ""
53 | }
54 | if (widgetSize == "Small") {
55 | crop.w = phone.small
56 | crop.h = phone.small
57 | let positions = ["Top left", "Top right", "Middle left", "Middle right", "Bottom left", "Bottom right"]
58 | let position = await generateAlert(message, positions)
59 |
60 | // Convert the two words into two keys for the phone size dictionary.
61 | let keys = positions[position].toLowerCase().split(' ')
62 | crop.y = phone[keys[0]]
63 | crop.x = phone[keys[1]]
64 |
65 | } else if (widgetSize == "Medium") {
66 | crop.w = phone.medium
67 | crop.h = phone.small
68 |
69 | // Medium and large widgets have a fixed x-value.
70 | crop.x = phone.left
71 | let positions = ["Top", "Middle", "Bottom"]
72 | let position = await generateAlert(message, positions)
73 | let key = positions[position].toLowerCase()
74 | crop.y = phone[key]
75 |
76 | } else if (widgetSize == "Large") {
77 | crop.w = phone.medium
78 | crop.h = phone.large
79 | crop.x = phone.left
80 | let positions = ["Top", "Bottom"]
81 | let position = await generateAlert(message, positions)
82 |
83 | // Large widgets at the bottom have the "middle" y-value.
84 | crop.y = position ? phone.middle : phone.top
85 | }
86 |
87 | // Crop image and finalize the widget.
88 | let imgCrop = cropImage(img, new Rect(crop.x, crop.y, crop.w, crop.h))
89 |
90 | message = "Your widget background is ready. Would you like to use it in a Scriptable widget or export the image?"
91 | const exportPhotoOptions = ["Export to Files", "Export to Photos"]
92 | const exportPhoto = await generateAlert(message, exportPhotoOptions)
93 |
94 | if (exportPhoto) {
95 | Photos.save(imgCrop)
96 | } else {
97 | await DocumentPicker.exportImage(imgCrop)
98 | }
99 |
100 | Script.complete()
101 | }
102 |
103 | // Generate an alert with the provided array of options.
104 | async function generateAlert(message, options) {
105 |
106 | let alert = new Alert()
107 | alert.message = message
108 |
109 | for (const option of options) {
110 | alert.addAction(option)
111 | }
112 |
113 | let response = await alert.presentAlert()
114 | return response
115 | }
116 |
117 | // Crop an image into the specified rect.
118 | function cropImage(img, rect) {
119 |
120 | let draw = new DrawContext()
121 | draw.size = new Size(rect.width, rect.height)
122 |
123 | draw.drawImageAtPoint(img, new Point(-rect.x, -rect.y))
124 | return draw.getImage()
125 | }
126 |
127 | // Pixel sizes and positions for widgets on all supported phones.
128 | function phoneSizes() {
129 | let phones = {
130 | "2688": {
131 | "small": 507,
132 | "medium": 1080,
133 | "large": 1137,
134 | "left": 81,
135 | "right": 654,
136 | "top": 228,
137 | "middle": 858,
138 | "bottom": 1488
139 | },
140 |
141 | "1792": {
142 | "small": 338,
143 | "medium": 720,
144 | "large": 758,
145 | "left": 54,
146 | "right": 436,
147 | "top": 160,
148 | "middle": 580,
149 | "bottom": 1000
150 | },
151 |
152 | "2436": {
153 | "small": 465,
154 | "medium": 987,
155 | "large": 1035,
156 | "left": 69,
157 | "right": 591,
158 | "top": 213,
159 | "middle": 783,
160 | "bottom": 1353
161 | },
162 |
163 | "2208": {
164 | "small": 471,
165 | "medium": 1044,
166 | "large": 1071,
167 | "left": 99,
168 | "right": 672,
169 | "top": 114,
170 | "middle": 696,
171 | "bottom": 1278
172 | },
173 |
174 | "1334": {
175 | "small": 296,
176 | "medium": 642,
177 | "large": 648,
178 | "left": 54,
179 | "right": 400,
180 | "top": 60,
181 | "middle": 412,
182 | "bottom": 764
183 | },
184 |
185 | "1136": {
186 | "small": 282,
187 | "medium": 584,
188 | "large": 622,
189 | "left": 30,
190 | "right": 332,
191 | "top": 59,
192 | "middle": 399,
193 | "bottom": 399
194 | }
195 | }
196 | return phones
197 | }
--------------------------------------------------------------------------------
/WidgetHelloV2.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: cyan; icon-glyph: child;
4 | // To use, add a parameter to the widget with a format of: image.png|padding-top|text-color
5 | // The image should be placed in the iCloud Scriptable folder (case-sensitive).
6 | // The padding-top spacing parameter moves the text down by a set amount.
7 | // The text color parameter should be a hex value.
8 |
9 | // For example, to use the image bkg_fall.PNG with a padding of 40 and a text color of red,
10 | // the parameter should be typed as: bkg_fall.png|40|#ff0000
11 |
12 | // All parameters are required and separated with "|"
13 |
14 | // Parameters allow different settings for multiple widget instances.
15 |
16 | let widgetHello = new ListWidget();
17 | var today = new Date();
18 |
19 | var widgetInputRAW = args.widgetParameter;
20 |
21 | try {
22 | widgetInputRAW.toString();
23 | } catch (e) {
24 | throw new Error("Please long press the widget and add a parameter.");
25 | }
26 |
27 | var widgetInput = widgetInputRAW.toString();
28 |
29 | var inputArr = widgetInput.split("|");
30 |
31 | // iCloud file path
32 | var scriptableFilePath = "/var/mobile/Library/Mobile Documents/iCloud~dk~simonbs~Scriptable/Documents/BKG_IMG/";
33 | var removeSpaces1 = inputArr[0].split(" "); // Remove spaces from file name
34 | var removeSpaces2 = removeSpaces1.join('');
35 | var tempPath = removeSpaces2.split(".");
36 | var backgroundImageURLRAW = scriptableFilePath + tempPath[0];
37 |
38 | var fm = FileManager.iCloud();
39 | var backgroundImageURL = scriptableFilePath + tempPath[0] + ".";
40 | var backgroundImageURLInput = scriptableFilePath + removeSpaces2;
41 |
42 | // For users having trouble with extensions
43 | // Uses user-input file path is the file is found
44 | // Checks for common file format extensions if the file is not found
45 | if (fm.fileExists(backgroundImageURLInput) == false) {
46 | var fileTypes = ['png', 'jpg', 'jpeg', 'tiff', 'webp', 'gif'];
47 |
48 | fileTypes.forEach(function (item) {
49 | if (fm.fileExists((backgroundImageURL + item.toLowerCase())) == true) {
50 | backgroundImageURL = backgroundImageURLRAW + "." + item.toLowerCase();
51 | } else if (fm.fileExists((backgroundImageURL + item.toUpperCase())) == true) {
52 | backgroundImageURL = backgroundImageURLRAW + "." + item.toUpperCase();
53 | }
54 | });
55 | } else {
56 | backgroundImageURL = scriptableFilePath + removeSpaces2;
57 | }
58 |
59 | var spacing = parseInt(inputArr[1]);
60 |
61 | // Long-form days and months
62 | var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
63 | var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
64 |
65 | // Greetings arrays per time period.
66 | var greetingsMorning = [
67 | 'Good morning.'
68 | ];
69 | var greetingsAfternoon = [
70 | 'Good afternoon.'
71 | ];
72 | var greetingsEvening = [
73 | 'Good evening.'
74 | ];
75 | var greetingsNight = [
76 | 'Bedtime.'
77 | ];
78 | var greetingsLateNight = [
79 | 'Go to sleep!'
80 | ];
81 |
82 | // Holiday customization
83 | var holidaysByKey = {
84 | // month,week,day: datetext
85 | "11,4,4": "Happy Thanksgiving!"
86 | }
87 |
88 | var holidaysByDate = {
89 | // month,date: greeting
90 | "1,1": "Happy " + (today.getFullYear()).toString() + "!",
91 | "10,31": "Happy Halloween!",
92 | "12,25": "Merry Christmas!"
93 | }
94 |
95 | var holidayKey = (today.getMonth() + 1).toString() + "," + (Math.ceil(today.getDate() / 7)).toString() + "," + (today.getDay()).toString();
96 |
97 | var holidayKeyDate = (today.getMonth() + 1).toString() + "," + (today.getDate()).toString();
98 |
99 | // Date Calculations
100 | var weekday = days[today.getDay()];
101 | var month = months[today.getMonth()];
102 | var date = today.getDate();
103 | var hour = today.getHours();
104 |
105 | // Append ordinal suffix to date
106 | function ordinalSuffix(input) {
107 | if (input % 10 == 1 && date != 11) {
108 | return input.toString() + "st";
109 | } else if (input % 10 == 2 && date != 12) {
110 | return input.toString() + "nd";
111 | } else if (input % 10 == 3 && date != 13) {
112 | return input.toString() + "rd";
113 | } else {
114 | return input.toString() + "th";
115 | }
116 | }
117 |
118 | // Generate date string
119 | var datefull = weekday + ", " + month + " " + ordinalSuffix(date);
120 |
121 | // Support for multiple greetings per time period
122 | function randomGreeting(greetingArray) {
123 | return Math.floor(Math.random() * greetingArray.length);
124 | }
125 |
126 | var greeting = new String("Howdy.")
127 | if (hour < 5 && hour >= 1) { // 1am - 5am
128 | greeting = greetingsLateNight[randomGreeting(greetingsLateNight)];
129 | } else if (hour >= 23 || hour < 1) { // 11pm - 1am
130 | greeting = greetingsNight[randomGreeting(greetingsNight)];
131 | } else if (hour < 12) { // Before noon (5am - 12pm)
132 | greeting = greetingsMorning[randomGreeting(greetingsMorning)];
133 | } else if (hour >= 12 && hour <= 17) { // 12pm - 5pm
134 | greeting = greetingsAfternoon[randomGreeting(greetingsAfternoon)];
135 | } else if (hour > 17 && hour < 23) { // 5pm - 11pm
136 | greeting = greetingsEvening[randomGreeting(greetingsEvening)];
137 | }
138 |
139 | // Overwrite greeting if calculated holiday
140 | if (holidaysByKey[holidayKey]) {
141 | greeting = holidaysByKey[holidayKey];
142 | }
143 |
144 | // Overwrite all greetings if specific holiday
145 | if (holidaysByDate[holidayKeyDate]) {
146 | greeting = holidaysByDate[holidayKeyDate];
147 | }
148 |
149 | // Try/catch for color input parameter
150 | try {
151 | inputArr[2].toString();
152 | } catch (e) {
153 | throw new Error("Please long press the widget and add a parameter.");
154 | }
155 |
156 | let themeColor = new Color(inputArr[2].toString());
157 |
158 |
159 | // Widget Cache Path Settings
160 | const wBasePath = "/var/mobile/Library/Mobile Documents/iCloud~dk~simonbs~Scriptable/Documents/WidgetCache/";
161 |
162 | // Open weather API_KEY
163 | const API_WEATHER = "1412c61a30b2f29c47bce78c3523722d"; //Load api here
164 | // Suzhou: 101190401 Nanjing: 101190101
165 | const CITY_NAME = "Suzhou"; // add city ID
166 |
167 | // Get Location
168 |
169 | // Location.setAccuracyToBest();
170 | // let curLocation = await Location.current();
171 |
172 | let weatherUrl = "https://api.openweathermap.org/data/2.5/weather?q=" +
173 | CITY_NAME + "&appid=" + API_WEATHER + "&units=metric";
174 | // let weatherUrl = "http://api.openweathermap.org/data/2.5/weather?lat=" +curLocation.latitude + "&lon=" + curLocation.longitude +"&appid=" + API_WEATHER + "&units=metric";
175 | // let weatherUrl = "http://api.openweathermap.org/data/2.5/weather?id=" + CITY_WEATHER + "&APPID=" + API_WEATHER + "&units=metric";
176 |
177 |
178 | const dataJSON = await fetchWeatherData(weatherUrl);
179 | const cityName = dataJSON.name;
180 | const weatherArray = dataJSON.weather;
181 | const iconData = weatherArray[0].icon;
182 | const weatherName = weatherArray[0].description;
183 | const curTempObj = dataJSON.main;
184 | const curTemp = curTempObj.temp;
185 | const highTemp = curTempObj.temp_max;
186 | const lowTemp = curTempObj.temp_min;
187 | const feelsLike = curTempObj.feels_like;
188 |
189 | //Completed loading weather data
190 |
191 |
192 | /* ------------------------------------------------------------------------------ */
193 | /* Assemble Widget */
194 | /* ------------------------------------------------------------------------------ */
195 | if (config.runsInWidget) {
196 | // Top spacing
197 | widgetHello.addSpacer(parseInt(spacing));
198 | // Background image
199 | widgetHello.backgroundImage = Image.fromFile(backgroundImageURL);
200 |
201 | /* ------------------------------------------------------ */
202 | /* Greeting label */
203 | let hello = widgetHello.addText(greeting);
204 | hello.font = Font.boldSystemFont(40);
205 | hello.textColor = themeColor;
206 |
207 | /* Date label */
208 | let datetext = widgetHello.addText(datefull);
209 | datetext.font = Font.boldSystemFont(25);
210 | datetext.textColor = themeColor;
211 | widgetHello.addSpacer(10);
212 |
213 | /* ------------------------------------------------------ */
214 | /* Add weather */
215 | let weatherStack = widgetHello.addStack();
216 | weatherStack.layoutVertically();
217 | weatherStack.setPadding(10, 20, 0, 0);
218 | weatherStack.centerAlignContent();
219 |
220 | // Weather Image
221 | var img = Image.fromFile(await fetchImageLocal(iconData + "_ico"));
222 |
223 | let wSubStack = weatherStack.addStack();
224 | wSubStack.layoutHorizontally();
225 | wSubStack.setPadding(5, 0, 10, 0);
226 |
227 | // Weather image in stack
228 | let wUpperStack = wSubStack.addStack();
229 | wUpperStack.layoutHorizontally();
230 |
231 | let widgetImg = wUpperStack.addImage(img);
232 | widgetImg.imageSize = new Size(40, 40);
233 | widgetImg.leftAlignImage();
234 |
235 |
236 | // City and temp in stack
237 | let cityTempStack = wUpperStack.addStack();
238 | cityTempStack.layoutVertically();
239 | cityTempStack.setPadding(0, 20, 0, 0);
240 |
241 | let cityText = cityTempStack.addText(cityName);
242 | cityText.font = Font.boldSystemFont(20);
243 | cityText.textColor = themeColor;
244 | cityText.textOpacity = 1;
245 | cityText.leftAlignText();
246 |
247 | let tempText = cityTempStack.addText(Math.round(curTemp).toString() + "\u2103");
248 | tempText.font = Font.boldSystemFont(20);
249 | tempText.textColor = new Color('#0278AE');
250 | tempText.textOpacity = 1;
251 | tempText.leftAlignText();
252 |
253 | // Weather details in stack
254 | let wDetailStack = weatherStack.addStack();
255 | wDetailStack.layoutVertically();
256 | wDetailStack.setPadding(0, 5, 0, 0);
257 |
258 | let details1 = weatherName.replace(weatherName[0], weatherName[0].toUpperCase()) +
259 | " today" + ".";
260 |
261 | let wDetailText1 = wDetailStack.addText(details1);
262 | wDetailText1.font = Font.regularSystemFont(15);
263 | wDetailText1.textColor = themeColor;
264 | wDetailText1.textOpacity = 1;
265 | wDetailText1.leftAlignText();
266 |
267 | let details2 = "It feels like " + Math.round(feelsLike) + "\u2103," +
268 | " the high will be " + Math.round(highTemp) + "\u2103" + ".";
269 |
270 | let wDetailText2 = wDetailStack.addText(details2);
271 | wDetailText2.font = Font.regularSystemFont(15);
272 | wDetailText2.textColor = themeColor;
273 | wDetailText2.textOpacity = 1;
274 | wDetailText2.leftAlignText();
275 |
276 | weatherStack.addSpacer(20);
277 |
278 | /*-----------------------------------------------------------*/
279 | /* Year Progress */
280 |
281 | let yearStack = widgetHello.addStack();
282 | yearStack.layoutVertically();
283 |
284 | const yearFont = new Font('Menlo', 16);
285 | const yearColor = new Color('#80DEEA');
286 |
287 | // Year icon in stack
288 | const yearProgressIcon = yearStack.addText("◕ " + new Date().getFullYear());
289 | yearProgressIcon.font = yearFont;
290 | yearProgressIcon.textColor = yearColor;
291 | yearProgressIcon.textOpacity = 1;
292 | yearProgressIcon.leftAlignText();
293 |
294 | // Year label in stack
295 | let yProgressStack = yearStack.addStack();
296 | yProgressStack.layoutHorizontally();
297 | yProgressStack.setPadding(0, 25, 0, 0);
298 |
299 | const yearProgress = yProgressStack.addText(renderYearProgress());
300 | yearProgress.font = yearFont;
301 | yearProgress.textColor = yearColor;
302 | yearProgress.textOpacity = 1;
303 | yearProgress.leftAlignText();
304 | widgetHello.addSpacer(20);
305 |
306 | /* ------------------------------------------------------ */
307 | /* Battery labels group */
308 | let batteryStack = widgetHello.addStack();
309 | batteryStack.layoutVertically();
310 |
311 | let batteryTitleStack = batteryStack.addStack();
312 | batteryStack.layoutVertically();
313 | const batteryIcon = batteryTitleStack.addImage(provideBatteryIcon())
314 | batteryIcon.imageSize = new Size(16, 16);
315 |
316 | const batteryFont = new Font("Menlo", 16);
317 | let batteryLevel = Device.batteryLevel();
318 |
319 |
320 | var batteryColor;
321 | if (batteryLevel >= 0.6)
322 | batteryColor = new Color("#A8DF65");
323 | else if (batteryLevel >= 0.2)
324 | batteryColor = new Color("#FFCC00");
325 | else
326 | batteryColor = new Color("#FFD571");
327 |
328 |
329 | batteryTitleText = batteryTitleStack.addText(" Battery\t");
330 | batteryTitleText.font = batteryFont;
331 | batteryTitleText.textColor = batteryColor;
332 | batteryIcon.tintColor = batteryColor;
333 | batteryTitleText.textOpacity = 1;
334 | batteryTitleText.leftAlignText();
335 |
336 | // Battery Progress in stack
337 | let bProgressStack = batteryStack.addStack();
338 | bProgressStack.layoutHorizontally();
339 | bProgressStack.setPadding(0, 25, 0, 0);
340 |
341 |
342 | const batteryLine = bProgressStack.addText(renderBattery(batteryLevel));
343 | batteryLine.font = batteryFont;
344 | batteryLine.textColor = batteryColor;
345 | batteryLine.textOpacity = 1;
346 | batteryLine.leftAlignText();
347 |
348 |
349 | let batterytext = bProgressStack.addText(
350 | "[" + Math.round(batteryLevel * 100) + "%] ");
351 | batterytext.font = batteryFont;
352 | batterytext.textColor = batteryColor;
353 | batterytext.textOpacity = (1);
354 | batterytext.leftAlignText();
355 |
356 | /* ------------------------------------------------------ */
357 | // Bottom Spacer
358 | widgetHello.addSpacer();
359 | widgetHello.setPadding(0, 20, 10, 0);
360 |
361 | // Set widget
362 | Script.setWidget(widgetHello);
363 | Script.complete();
364 |
365 |
366 | /* ------------------------ */
367 | /* Assemble Widget Finished */
368 | /* ------------------------ */
369 | }
370 |
371 |
372 | /* ---------------------------------------------------------------------- */
373 | /* Functions */
374 | /* ---------------------------------------------------------------------- */
375 |
376 | function provideBatteryIcon() {
377 |
378 | // If we're charging, show the charging icon.
379 | if (Device.isCharging()) {
380 | return SFSymbol.named("battery.100.bolt").image
381 | }
382 |
383 | // Set the size of the battery icon.
384 | const batteryWidth = 87
385 | const batteryHeight = 41
386 |
387 | // Start our draw context.
388 | let draw = new DrawContext()
389 | draw.opaque = false
390 | draw.respectScreenScale = true
391 | draw.size = new Size(batteryWidth, batteryHeight)
392 |
393 | // Draw the battery.
394 | draw.drawImageInRect(SFSymbol.named("battery.0").image, new Rect(0, 0, batteryWidth, batteryHeight))
395 |
396 | // Match the battery level values to the SFSymbol.
397 | const x = batteryWidth * 0.1525
398 | const y = batteryHeight * 0.247
399 | const width = batteryWidth * 0.602
400 | const height = batteryHeight * 0.505
401 |
402 | // Prevent unreadable icons.
403 | let level = Device.batteryLevel()
404 | if (level < 0.05) {
405 | level = 0.05
406 | }
407 |
408 | // Determine the width and radius of the battery level.
409 | const current = width * level
410 | let radius = height / 6.5
411 |
412 | // When it gets low, adjust the radius to match.
413 | if (current < (radius * 2)) {
414 | radius = current / 2
415 | }
416 |
417 | // Make the path for the battery level.
418 | let barPath = new Path()
419 | barPath.addRoundedRect(new Rect(x, y, current, height), radius, radius)
420 | draw.addPath(barPath)
421 | draw.setFillColor(Color.black())
422 | draw.fillPath()
423 | return draw.getImage()
424 | }
425 |
426 | // Render battery with data
427 | function renderBattery(batteryLevel) {
428 | const left = "▓".repeat(Math.floor(batteryLevel * 25));
429 | const used = "░".repeat(25 - left.length)
430 | const batteryAscii = left + used + " ";
431 | return batteryAscii;
432 | }
433 |
434 | // Render year progress
435 | function renderYearProgress() {
436 | const now = new Date()
437 | const start = new Date(now.getFullYear(), 0, 1) // Start of this year
438 | const end = new Date(now.getFullYear() + 1, 0, 1) // End of this year
439 | const progress = (now - start) / (end - start);
440 | return renderProgress(progress)
441 | }
442 |
443 | function renderProgress(progress) {
444 | const used = '▓'.repeat(Math.floor(progress * 25))
445 | const left = '░'.repeat(25 - used.length)
446 | return `${used}${left} [${Math.floor(progress * 100)}%]`;
447 | }
448 |
449 |
450 | // Download weather img
451 | async function fetchImgUrl(url) {
452 | const request = new Request(url)
453 | var res = await request.loadImage();
454 | return res;
455 | }
456 |
457 | async function downloadWeatherImg(path) {
458 | const url = "http://a.animedlweb.ga/weather/weathers25_2.json";
459 | const data = await fetchWeatherPicData(url);
460 | var dataimg = null;
461 | var name = null;
462 | if (path.includes("bg")) {
463 | dataimg = data.background;
464 | name = path.replace("_bg", "");
465 | } else {
466 | dataimg = data.icon;
467 | name = path.replace("_ico", "");
468 | }
469 | var imgurl = null;
470 | switch (name) {
471 | case "01d":
472 | imgurl = dataimg._01d;
473 | break;
474 | case "01n":
475 | imgurl = dataimg._01n;
476 | break;
477 | case "02d":
478 | imgurl = dataimg._02d;
479 | break;
480 | case "02n":
481 | imgurl = dataimg._02n;
482 | break;
483 | case "03d":
484 | imgurl = dataimg._03d;
485 | break;
486 | case "03n":
487 | imgurl = dataimg._03n;
488 | break;
489 | case "04d":
490 | imgurl = dataimg._04d;
491 | break;
492 | case "04n":
493 | imgurl = dataimg._04n;
494 | break;
495 | case "09d":
496 | imgurl = dataimg._09d;
497 | break;
498 | case "09n":
499 | imgurl = dataimg._09n;
500 | break;
501 | case "10d":
502 | imgurl = dataimg._10d;
503 | break;
504 | case "10n":
505 | imgurl = dataimg._10n;
506 | break;
507 | case "11d":
508 | imgurl = dataimg._11d;
509 | break;
510 | case "11n":
511 | imgurl = dataimg._11n;
512 | break;
513 | case "13d":
514 | imgurl = dataimg._13d;
515 | break;
516 | case "13n":
517 | imgurl = dataimg._13n;
518 | break;
519 | case "50d":
520 | imgurl = dataimg._50d;
521 | break;
522 | case "50n":
523 | imgurl = dataimg._50n;
524 | break;
525 | }
526 | const image = await fetchImgUrl(imgurl);
527 | fm.writeImage(wBasePath + path + ".png", image);
528 | }
529 |
530 | // Get Weather Json
531 | async function fetchWeatherPicData(url) {
532 |
533 | const cachePath = wBasePath + "weather-pic-cache";
534 | const cacheExists = fm.fileExists(cachePath);
535 | const cacheDate = cacheExists ? fm.modificationDate(cachePath) : 0;
536 | let weatherDataRaw;
537 | // If cache exists and it's been less than 60 seconds since last request, use cached data.
538 | if (cacheExists && (new Date().getTime() - cacheDate.getTime()) < 60000) {
539 | const cache = fm.readString(cachePath);
540 | weatherDataRaw = JSON.parse(cache);
541 | } else {
542 | // Otherwise, use the API to get new weather data.
543 | const request = new Request(url);
544 | weatherDataRaw = await request.loadJSON();
545 | fm.writeString(cachePath, JSON.stringify(weatherDataRaw));
546 |
547 | }
548 |
549 | return weatherDataRaw;
550 | }
551 |
552 | // Get Weather Json
553 | async function fetchWeatherData(url) {
554 |
555 | const cachePath = wBasePath + "weather-cache";
556 | const cacheExists = fm.fileExists(cachePath);
557 | const cacheDate = cacheExists ? fm.modificationDate(cachePath) : 0;
558 | let weatherDataRaw;
559 | // If cache exists and it's been less than 60 seconds since last request, use cached data.
560 | if (cacheExists && (new Date().getTime() - cacheDate.getTime()) < 60000) {
561 | const cache = fm.readString(cachePath);
562 | weatherDataRaw = JSON.parse(cache);
563 | } else {
564 | // Otherwise, use the API to get new weather data.
565 | const request = new Request(url);
566 | weatherDataRaw = await request.loadJSON();
567 | fm.writeString(cachePath, JSON.stringify(weatherDataRaw));
568 |
569 | }
570 |
571 | return weatherDataRaw;
572 | }
573 |
574 |
575 | // Load image from local drive
576 | async function fetchImageLocal(path) {
577 | var finalPath = wBasePath + path + ".png";
578 | if (fm.fileExists(finalPath) == true) {
579 | console.log("file exists: " + finalPath);
580 | return finalPath;
581 | } else {
582 | //throw new Error("Error file not found: " + path);
583 | if (fm.fileExists(wBasePath) == false) {
584 | console.log("Directry not exist creating one.");
585 | fm.createDirectory(wBasePath);
586 | }
587 | console.log("Downloading file: " + finalPath);
588 | await downloadWeatherImg(path);
589 | if (fm.fileExists(finalPath) == true) {
590 | console.log("file exists after download: " + finalPath);
591 | return finalPath;
592 | } else {
593 | throw new Error("Error file not found: " + path);
594 | }
595 | }
596 | }
--------------------------------------------------------------------------------
/WigetBlur.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: yellow; icon-glyph: magic;
4 |
5 | // This script was created by Max Zeryck.
6 |
7 | // The amount of blurring. Default is 150.
8 | let blur = 150
9 |
10 | // Determine if user has taken the screenshot.
11 | var message
12 | message = "Before you start, go to your home screen and enter wiggle mode. Scroll to the empty page on the far right and take a screenshot."
13 | let options = ["Continue to select image","Exit to take screenshot","Update code"]
14 | let response = await generateAlert(message,options)
15 |
16 | // Return if we need to exit.
17 | if (response == 1) return
18 |
19 | // Update the code.
20 | if (response == 2) {
21 |
22 | // Determine if the user is using iCloud.
23 | let files = FileManager.local()
24 | const iCloudInUse = files.isFileStoredIniCloud(module.filename)
25 |
26 | // If so, use an iCloud file manager.
27 | files = iCloudInUse ? FileManager.iCloud() : files
28 |
29 | // Try to download the file.
30 | try {
31 | const req = new Request("https://raw.githubusercontent.com/mzeryck/Widget-Blur/main/widget-blur.js")
32 | const codeString = await req.loadString()
33 | files.writeString(module.filename, codeString)
34 | message = "The code has been updated. If the script is open, close it for the change to take effect."
35 | } catch {
36 | message = "The update failed. Please try again later."
37 | }
38 | options = ["OK"]
39 | await generateAlert(message,options)
40 | return
41 | }
42 |
43 | // Get screenshot and determine phone size.
44 | let img = await Photos.fromLibrary()
45 | let height = img.size.height
46 | let phone = phoneSizes()[height]
47 | if (!phone) {
48 | message = "It looks like you selected an image that isn't an iPhone screenshot, or your iPhone is not supported. Try again with a different image."
49 | await generateAlert(message,["OK"])
50 | return
51 | }
52 |
53 | // Extra setup needed for 2436-sized phones.
54 | if (height == 2436) {
55 |
56 | let files = FileManager.local()
57 | let cacheName = "mz-phone-type"
58 | let cachePath = files.joinPath(files.libraryDirectory(), cacheName)
59 |
60 | // If we already cached the phone size, load it.
61 | if (files.fileExists(cachePath)) {
62 | let typeString = files.readString(cachePath)
63 | phone = phone[typeString]
64 |
65 | // Otherwise, prompt the user.
66 | } else {
67 | message = "What type of iPhone do you have?"
68 | let types = ["iPhone 12 mini", "iPhone 11 Pro, XS, or X"]
69 | let typeIndex = await generateAlert(message, types)
70 | let type = (typeIndex == 0) ? "mini" : "x"
71 | phone = phone[type]
72 | files.writeString(cachePath, type)
73 | }
74 | }
75 |
76 | // Prompt for widget size and position.
77 | message = "What size of widget are you creating?"
78 | let sizes = ["Small","Medium","Large"]
79 | let size = await generateAlert(message,sizes)
80 | let widgetSize = sizes[size]
81 |
82 | message = "What position will it be in?"
83 | message += (height == 1136 ? " (Note that your device only supports two rows of widgets, so the middle and bottom options are the same.)" : "")
84 |
85 | // Determine image crop based on phone size.
86 | let crop = { w: "", h: "", x: "", y: "" }
87 | if (widgetSize == "Small") {
88 | crop.w = phone.small
89 | crop.h = phone.small
90 | let positions = ["Top left","Top right","Middle left","Middle right","Bottom left","Bottom right"]
91 | let position = await generateAlert(message,positions)
92 |
93 | // Convert the two words into two keys for the phone size dictionary.
94 | let keys = positions[position].toLowerCase().split(' ')
95 | crop.y = phone[keys[0]]
96 | crop.x = phone[keys[1]]
97 |
98 | } else if (widgetSize == "Medium") {
99 | crop.w = phone.medium
100 | crop.h = phone.small
101 |
102 | // Medium and large widgets have a fixed x-value.
103 | crop.x = phone.left
104 | let positions = ["Top","Middle","Bottom"]
105 | let position = await generateAlert(message,positions)
106 | let key = positions[position].toLowerCase()
107 | crop.y = phone[key]
108 |
109 | } else if(widgetSize == "Large") {
110 | crop.w = phone.medium
111 | crop.h = phone.large
112 | crop.x = phone.left
113 | let positions = ["Top","Bottom"]
114 | let position = await generateAlert(message,positions)
115 |
116 | // Large widgets at the bottom have the "middle" y-value.
117 | crop.y = position ? phone.middle : phone.top
118 | }
119 |
120 | // Prompt for blur style.
121 | message = "Do you want a fully transparent widget, or a translucent blur effect?"
122 | let blurOptions = ["Transparent","Light blur","Dark blur","Just blur"]
123 | let blurred = await generateAlert(message,blurOptions)
124 |
125 | // We always need the cropped image.
126 | let imgCrop = cropImage(img)
127 |
128 | // If it's blurred, set the blur style.
129 | if (blurred) {
130 | const styles = ["", "light", "dark", "none"]
131 | const style = styles[blurred]
132 | imgCrop = await blurImage(img,imgCrop,style)
133 | }
134 |
135 | message = "Your widget background is ready. Choose where to save the image:"
136 | const exportPhotoOptions = ["Export to the Photos app","Export to the Files app"]
137 | const exportToFiles = await generateAlert(message,exportPhotoOptions)
138 |
139 | if (exportToFiles) {
140 | await DocumentPicker.exportImage(imgCrop)
141 | } else {
142 | Photos.save(imgCrop)
143 | }
144 |
145 | Script.complete()
146 |
147 | // Generate an alert with the provided array of options.
148 | async function generateAlert(message,options) {
149 |
150 | let alert = new Alert()
151 | alert.message = message
152 |
153 | for (const option of options) {
154 | alert.addAction(option)
155 | }
156 |
157 | let response = await alert.presentAlert()
158 | return response
159 | }
160 |
161 | // Crop an image into the specified rect.
162 | function cropImage(image) {
163 |
164 | let draw = new DrawContext()
165 | let rect = new Rect(crop.x,crop.y,crop.w,crop.h)
166 | draw.size = new Size(rect.width, rect.height)
167 |
168 | draw.drawImageAtPoint(image,new Point(-rect.x, -rect.y))
169 | return draw.getImage()
170 | }
171 |
172 | async function blurImage(img,imgCrop,style) {
173 | const js = `
174 | /*
175 | StackBlur - a fast almost Gaussian Blur For Canvas
176 | Version: 0.5
177 | Author: Mario Klingemann
178 | Contact: mario@quasimondo.com
179 | Website: http://quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
180 | Twitter: @quasimondo
181 | In case you find this class useful - especially in commercial projects -
182 | I am not totally unhappy for a small donation to my PayPal account
183 | mario@quasimondo.de
184 | Or support me on flattr:
185 | https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript
186 | Copyright (c) 2010 Mario Klingemann
187 | Permission is hereby granted, free of charge, to any person
188 | obtaining a copy of this software and associated documentation
189 | files (the "Software"), to deal in the Software without
190 | restriction, including without limitation the rights to use,
191 | copy, modify, merge, publish, distribute, sublicense, and/or sell
192 | copies of the Software, and to permit persons to whom the
193 | Software is furnished to do so, subject to the following
194 | conditions:
195 | The above copyright notice and this permission notice shall be
196 | included in all copies or substantial portions of the Software.
197 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
198 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
199 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
200 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
201 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
202 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
203 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
204 | OTHER DEALINGS IN THE SOFTWARE.
205 | */
206 | var mul_table = [
207 | 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
208 | 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
209 | 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
210 | 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
211 | 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
212 | 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
213 | 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
214 | 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
215 | 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
216 | 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
217 | 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
218 | 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
219 | 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
220 | 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
221 | 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
222 | 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259];
223 |
224 |
225 | var shg_table = [
226 | 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
227 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
228 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
229 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
230 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
231 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
232 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
233 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
234 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
235 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
236 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
237 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
238 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
239 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
240 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
241 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ];
242 | function stackBlurCanvasRGB( id, top_x, top_y, width, height, radius )
243 | {
244 | if ( isNaN(radius) || radius < 1 ) return;
245 | radius |= 0;
246 |
247 | var canvas = document.getElementById( id );
248 | var context = canvas.getContext("2d");
249 | var imageData;
250 |
251 | try {
252 | try {
253 | imageData = context.getImageData( top_x, top_y, width, height );
254 | } catch(e) {
255 |
256 | // NOTE: this part is supposedly only needed if you want to work with local files
257 | // so it might be okay to remove the whole try/catch block and just use
258 | // imageData = context.getImageData( top_x, top_y, width, height );
259 | try {
260 | netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
261 | imageData = context.getImageData( top_x, top_y, width, height );
262 | } catch(e) {
263 | alert("Cannot access local image");
264 | throw new Error("unable to access local image data: " + e);
265 | return;
266 | }
267 | }
268 | } catch(e) {
269 | alert("Cannot access image");
270 | throw new Error("unable to access image data: " + e);
271 | }
272 |
273 | var pixels = imageData.data;
274 |
275 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum,
276 | r_out_sum, g_out_sum, b_out_sum,
277 | r_in_sum, g_in_sum, b_in_sum,
278 | pr, pg, pb, rbs;
279 |
280 | var div = radius + radius + 1;
281 | var w4 = width << 2;
282 | var widthMinus1 = width - 1;
283 | var heightMinus1 = height - 1;
284 | var radiusPlus1 = radius + 1;
285 | var sumFactor = radiusPlus1 * ( radiusPlus1 + 1 ) / 2;
286 |
287 | var stackStart = new BlurStack();
288 | var stack = stackStart;
289 | for ( i = 1; i < div; i++ )
290 | {
291 | stack = stack.next = new BlurStack();
292 | if ( i == radiusPlus1 ) var stackEnd = stack;
293 | }
294 | stack.next = stackStart;
295 | var stackIn = null;
296 | var stackOut = null;
297 |
298 | yw = yi = 0;
299 |
300 | var mul_sum = mul_table[radius];
301 | var shg_sum = shg_table[radius];
302 |
303 | for ( y = 0; y < height; y++ )
304 | {
305 | r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0;
306 |
307 | r_out_sum = radiusPlus1 * ( pr = pixels[yi] );
308 | g_out_sum = radiusPlus1 * ( pg = pixels[yi+1] );
309 | b_out_sum = radiusPlus1 * ( pb = pixels[yi+2] );
310 |
311 | r_sum += sumFactor * pr;
312 | g_sum += sumFactor * pg;
313 | b_sum += sumFactor * pb;
314 |
315 | stack = stackStart;
316 |
317 | for( i = 0; i < radiusPlus1; i++ )
318 | {
319 | stack.r = pr;
320 | stack.g = pg;
321 | stack.b = pb;
322 | stack = stack.next;
323 | }
324 |
325 | for( i = 1; i < radiusPlus1; i++ )
326 | {
327 | p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 );
328 | r_sum += ( stack.r = ( pr = pixels[p])) * ( rbs = radiusPlus1 - i );
329 | g_sum += ( stack.g = ( pg = pixels[p+1])) * rbs;
330 | b_sum += ( stack.b = ( pb = pixels[p+2])) * rbs;
331 |
332 | r_in_sum += pr;
333 | g_in_sum += pg;
334 | b_in_sum += pb;
335 |
336 | stack = stack.next;
337 | }
338 |
339 |
340 | stackIn = stackStart;
341 | stackOut = stackEnd;
342 | for ( x = 0; x < width; x++ )
343 | {
344 | pixels[yi] = (r_sum * mul_sum) >> shg_sum;
345 | pixels[yi+1] = (g_sum * mul_sum) >> shg_sum;
346 | pixels[yi+2] = (b_sum * mul_sum) >> shg_sum;
347 |
348 | r_sum -= r_out_sum;
349 | g_sum -= g_out_sum;
350 | b_sum -= b_out_sum;
351 |
352 | r_out_sum -= stackIn.r;
353 | g_out_sum -= stackIn.g;
354 | b_out_sum -= stackIn.b;
355 |
356 | p = ( yw + ( ( p = x + radius + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2;
357 |
358 | r_in_sum += ( stackIn.r = pixels[p]);
359 | g_in_sum += ( stackIn.g = pixels[p+1]);
360 | b_in_sum += ( stackIn.b = pixels[p+2]);
361 |
362 | r_sum += r_in_sum;
363 | g_sum += g_in_sum;
364 | b_sum += b_in_sum;
365 |
366 | stackIn = stackIn.next;
367 |
368 | r_out_sum += ( pr = stackOut.r );
369 | g_out_sum += ( pg = stackOut.g );
370 | b_out_sum += ( pb = stackOut.b );
371 |
372 | r_in_sum -= pr;
373 | g_in_sum -= pg;
374 | b_in_sum -= pb;
375 |
376 | stackOut = stackOut.next;
377 | yi += 4;
378 | }
379 | yw += width;
380 | }
381 |
382 | for ( x = 0; x < width; x++ )
383 | {
384 | g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0;
385 |
386 | yi = x << 2;
387 | r_out_sum = radiusPlus1 * ( pr = pixels[yi]);
388 | g_out_sum = radiusPlus1 * ( pg = pixels[yi+1]);
389 | b_out_sum = radiusPlus1 * ( pb = pixels[yi+2]);
390 |
391 | r_sum += sumFactor * pr;
392 | g_sum += sumFactor * pg;
393 | b_sum += sumFactor * pb;
394 |
395 | stack = stackStart;
396 |
397 | for( i = 0; i < radiusPlus1; i++ )
398 | {
399 | stack.r = pr;
400 | stack.g = pg;
401 | stack.b = pb;
402 | stack = stack.next;
403 | }
404 |
405 | yp = width;
406 |
407 | for( i = 1; i <= radius; i++ )
408 | {
409 | yi = ( yp + x ) << 2;
410 |
411 | r_sum += ( stack.r = ( pr = pixels[yi])) * ( rbs = radiusPlus1 - i );
412 | g_sum += ( stack.g = ( pg = pixels[yi+1])) * rbs;
413 | b_sum += ( stack.b = ( pb = pixels[yi+2])) * rbs;
414 |
415 | r_in_sum += pr;
416 | g_in_sum += pg;
417 | b_in_sum += pb;
418 |
419 | stack = stack.next;
420 |
421 | if( i < heightMinus1 )
422 | {
423 | yp += width;
424 | }
425 | }
426 |
427 | yi = x;
428 | stackIn = stackStart;
429 | stackOut = stackEnd;
430 | for ( y = 0; y < height; y++ )
431 | {
432 | p = yi << 2;
433 | pixels[p] = (r_sum * mul_sum) >> shg_sum;
434 | pixels[p+1] = (g_sum * mul_sum) >> shg_sum;
435 | pixels[p+2] = (b_sum * mul_sum) >> shg_sum;
436 |
437 | r_sum -= r_out_sum;
438 | g_sum -= g_out_sum;
439 | b_sum -= b_out_sum;
440 |
441 | r_out_sum -= stackIn.r;
442 | g_out_sum -= stackIn.g;
443 | b_out_sum -= stackIn.b;
444 |
445 | p = ( x + (( ( p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2;
446 |
447 | r_sum += ( r_in_sum += ( stackIn.r = pixels[p]));
448 | g_sum += ( g_in_sum += ( stackIn.g = pixels[p+1]));
449 | b_sum += ( b_in_sum += ( stackIn.b = pixels[p+2]));
450 |
451 | stackIn = stackIn.next;
452 |
453 | r_out_sum += ( pr = stackOut.r );
454 | g_out_sum += ( pg = stackOut.g );
455 | b_out_sum += ( pb = stackOut.b );
456 |
457 | r_in_sum -= pr;
458 | g_in_sum -= pg;
459 | b_in_sum -= pb;
460 |
461 | stackOut = stackOut.next;
462 |
463 | yi += width;
464 | }
465 | }
466 |
467 | context.putImageData( imageData, top_x, top_y );
468 |
469 | }
470 | function BlurStack()
471 | {
472 | this.r = 0;
473 | this.g = 0;
474 | this.b = 0;
475 | this.a = 0;
476 | this.next = null;
477 | }
478 |
479 | // https://gist.github.com/mjackson/5311256
480 | function rgbToHsl(r, g, b){
481 | r /= 255, g /= 255, b /= 255;
482 | var max = Math.max(r, g, b), min = Math.min(r, g, b);
483 | var h, s, l = (max + min) / 2;
484 | if(max == min){
485 | h = s = 0; // achromatic
486 | }else{
487 | var d = max - min;
488 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
489 | switch(max){
490 | case r: h = (g - b) / d + (g < b ? 6 : 0); break;
491 | case g: h = (b - r) / d + 2; break;
492 | case b: h = (r - g) / d + 4; break;
493 | }
494 | h /= 6;
495 | }
496 | return [h, s, l];
497 | }
498 | function hslToRgb(h, s, l){
499 | var r, g, b;
500 | if(s == 0){
501 | r = g = b = l; // achromatic
502 | }else{
503 | var hue2rgb = function hue2rgb(p, q, t){
504 | if(t < 0) t += 1;
505 | if(t > 1) t -= 1;
506 | if(t < 1/6) return p + (q - p) * 6 * t;
507 | if(t < 1/2) return q;
508 | if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
509 | return p;
510 | }
511 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
512 | var p = 2 * l - q;
513 | r = hue2rgb(p, q, h + 1/3);
514 | g = hue2rgb(p, q, h);
515 | b = hue2rgb(p, q, h - 1/3);
516 | }
517 | return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
518 | }
519 |
520 | function lightBlur(hsl) {
521 |
522 | // Adjust the luminance.
523 | let lumCalc = 0.35 + (0.3 / hsl[2]);
524 | if (lumCalc < 1) { lumCalc = 1; }
525 | else if (lumCalc > 3.3) { lumCalc = 3.3; }
526 | const l = hsl[2] * lumCalc;
527 |
528 | // Adjust the saturation.
529 | const colorful = 2 * hsl[1] * l;
530 | const s = hsl[1] * colorful * 1.5;
531 |
532 | return [hsl[0],s,l];
533 |
534 | }
535 |
536 | function darkBlur(hsl) {
537 | // Adjust the saturation.
538 | const colorful = 2 * hsl[1] * hsl[2];
539 | const s = hsl[1] * (1 - hsl[2]) * 3;
540 |
541 | return [hsl[0],s,hsl[2]];
542 |
543 | }
544 | // Set up the canvas.
545 | const img = document.getElementById("blurImg");
546 | const canvas = document.getElementById("mainCanvas");
547 | const w = img.naturalWidth;
548 | const h = img.naturalHeight;
549 | canvas.style.width = w + "px";
550 | canvas.style.height = h + "px";
551 | canvas.width = w;
552 | canvas.height = h;
553 | const context = canvas.getContext("2d");
554 | context.clearRect( 0, 0, w, h );
555 | context.drawImage( img, 0, 0 );
556 |
557 | // Get the image data from the context.
558 | var imageData = context.getImageData(0,0,w,h);
559 | var pix = imageData.data;
560 |
561 | // Set the image function, if any.
562 | var imageFunc;
563 | var style = "${style}";
564 | if (style == "dark") { imageFunc = darkBlur; }
565 | else if (style == "light") { imageFunc = lightBlur; }
566 | for (let i=0; i < pix.length; i+=4) {
567 | // Convert to HSL.
568 | let hsl = rgbToHsl(pix[i],pix[i+1],pix[i+2]);
569 |
570 | // Apply the image function if it exists.
571 | if (imageFunc) { hsl = imageFunc(hsl); }
572 |
573 | // Convert back to RGB.
574 | const rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
575 |
576 | // Put the values back into the data.
577 | pix[i] = rgb[0];
578 | pix[i+1] = rgb[1];
579 | pix[i+2] = rgb[2];
580 | }
581 | // Draw over the old image.
582 | context.putImageData(imageData,0,0);
583 | // Blur the image.
584 | stackBlurCanvasRGB("mainCanvas", 0, 0, w, h, ${blur});
585 |
586 | // Perform the additional processing for dark images.
587 | if (style == "dark") {
588 |
589 | // Draw the hard light box over it.
590 | context.globalCompositeOperation = "hard-light";
591 | context.fillStyle = "rgba(55,55,55,0.2)";
592 | context.fillRect(0, 0, w, h);
593 | // Draw the soft light box over it.
594 | context.globalCompositeOperation = "soft-light";
595 | context.fillStyle = "rgba(55,55,55,1)";
596 | context.fillRect(0, 0, w, h);
597 | // Draw the regular box over it.
598 | context.globalCompositeOperation = "source-over";
599 | context.fillStyle = "rgba(55,55,55,0.4)";
600 | context.fillRect(0, 0, w, h);
601 |
602 | // Otherwise process light images.
603 | } else if (style == "light") {
604 | context.fillStyle = "rgba(255,255,255,0.4)";
605 | context.fillRect(0, 0, w, h);
606 | }
607 | // Return a base64 representation.
608 | canvas.toDataURL();
609 | `
610 |
611 | // Convert the images and create the HTML.
612 | let blurImgData = Data.fromPNG(img).toBase64String()
613 | let html = `
614 |
615 |
616 | `
617 |
618 | // Make the web view and get its return value.
619 | let view = new WebView()
620 | await view.loadHTML(html)
621 | let returnValue = await view.evaluateJavaScript(js)
622 |
623 | // Remove the data type from the string and convert to data.
624 | let imageDataString = returnValue.slice(22)
625 | let imageData = Data.fromBase64String(imageDataString)
626 |
627 | // Convert to image and crop before returning.
628 | let imageFromData = Image.fromData(imageData)
629 | return cropImage(imageFromData)
630 | }
631 |
632 | // Pixel sizes and positions for widgets on all supported phones.
633 | function phoneSizes() {
634 | let phones = {
635 |
636 | // 12 Pro Max
637 | "2778": {
638 | small: 510,
639 | medium: 1092,
640 | large: 1146,
641 | left: 96,
642 | right: 678,
643 | top: 246,
644 | middle: 882,
645 | bottom: 1518
646 | },
647 |
648 | // 12 and 12 Pro
649 | "2532": {
650 | small: 474,
651 | medium: 1014,
652 | large: 1062,
653 | left: 78,
654 | right: 618,
655 | top: 231,
656 | middle: 819,
657 | bottom: 1407
658 | },
659 |
660 | // 11 Pro Max, XS Max
661 | "2688": {
662 | small: 507,
663 | medium: 1080,
664 | large: 1137,
665 | left: 81,
666 | right: 654,
667 | top: 228,
668 | middle: 858,
669 | bottom: 1488
670 | },
671 |
672 | // 11, XR
673 | "1792": {
674 | small: 338,
675 | medium: 720,
676 | large: 758,
677 | left: 54,
678 | right: 436,
679 | top: 160,
680 | middle: 580,
681 | bottom: 1000
682 | },
683 |
684 |
685 | // 11 Pro, XS, X, 12 mini
686 | "2436": {
687 |
688 | x: {
689 | small: 465,
690 | medium: 987,
691 | large: 1035,
692 | left: 69,
693 | right: 591,
694 | top: 213,
695 | middle: 783,
696 | bottom: 1353,
697 | },
698 |
699 | mini: {
700 | small: 465,
701 | medium: 987,
702 | large: 1035,
703 | left: 69,
704 | right: 591,
705 | top: 231,
706 | middle: 801,
707 | bottom: 1371,
708 | }
709 |
710 | },
711 |
712 | // Plus phones
713 | "2208": {
714 | small: 471,
715 | medium: 1044,
716 | large: 1071,
717 | left: 99,
718 | right: 672,
719 | top: 114,
720 | middle: 696,
721 | bottom: 1278
722 | },
723 |
724 | // SE2 and 6/6S/7/8
725 | "1334": {
726 | small: 296,
727 | medium: 642,
728 | large: 648,
729 | left: 54,
730 | right: 400,
731 | top: 60,
732 | middle: 412,
733 | bottom: 764
734 | },
735 |
736 |
737 | // SE1
738 | "1136": {
739 | small: 282,
740 | medium: 584,
741 | large: 622,
742 | left: 30,
743 | right: 332,
744 | top: 59,
745 | middle: 399,
746 | bottom: 399
747 | },
748 |
749 | // 11 and XR in Display Zoom mode
750 | "1624": {
751 | small: 310,
752 | medium: 658,
753 | large: 690,
754 | left: 46,
755 | right: 394,
756 | top: 142,
757 | middle: 522,
758 | bottom: 902
759 | },
760 |
761 | // Plus in Display Zoom mode
762 | "2001" : {
763 | small: 444,
764 | medium: 963,
765 | large: 972,
766 | left: 81,
767 | right: 600,
768 | top: 90,
769 | middle: 618,
770 | bottom: 1146
771 | },
772 | }
773 | return phones
774 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Widget Hello
2 |
3 |
4 | ## Screenshot
5 | ### 2020.11.22 Change Battery Icon
6 | 
7 |
8 | ---
9 |
10 | ### 2020.10.9
11 | 
12 |
13 |
14 |
15 | ## Reference
16 | 1. [Github - mzeryck/mz_invisible_widget.js](https://gist.github.com/mzeryck/3a97ccd1e059b3afa3c6666d27a496c9)
17 | 2. [Github - xkerwin/Scriptbale](https://github.com/xkerwin/Scriptbale)
18 | 3. [Github - mzeryck/mz_invisible_widget.js](https://gist.github.com/mzeryck/3a97ccd1e059b3afa3c6666d27a496c9)
--------------------------------------------------------------------------------