Skip to content

Commit 751bf92

Browse files
committed
Add Flux watchface app
Initial commit of the Flux watchface app, including main application logic, icon, screenshots, metadata, and settings menu. The app features a customizable clock face inspired by Apple's Flux, with color themes and random number alignment.
1 parent 1743ef8 commit 751bf92

File tree

8 files changed

+473
-0
lines changed

8 files changed

+473
-0
lines changed

apps/flux/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Flux Watchface
2+
3+
This app slightly resembles Apples "Flux" watchface.
4+
5+
## Usage
6+
7+
Install it and set it as your default clock.
8+
It has no widgets.
9+
You can change the colors in the settings.
10+
11+
## Features
12+
13+
- Customizeable themes (4 color inputs)
14+
- Random number alignment (every minute or on reload)
15+
- 24h clock (12h not yet supported)
16+
- Color filling effect in 4 directions
17+
- 12 different colors
18+
19+
## Controls
20+
21+
BTN to go to launcher. No other functions.
22+
23+
## Requests
24+
25+
If you have a feature request or a bug, please message me at [sossinaydev@gmail.com](mailto:sossinaydev@gmail.com "Click to send an email").
26+
27+
## Creator
28+
29+
Yanis Ocaña, sossinay
30+
[pixelnet.ocaña.ch](https://pixelnet.ocaña.ch)

apps/flux/app-icon.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/flux/app.js

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
const font = {
2+
"0": [
3+
[[0,0],[1,0]],
4+
[[1,0],[1,1]],
5+
[[1,1],[0,1]],
6+
[[0,1],[0,0]]
7+
],
8+
9+
"1": [
10+
[[1,0],[1,1]]
11+
],
12+
13+
"2": [
14+
[[0,0],[1,0]],
15+
[[1,0],[1,0.5]],
16+
[[1,0.5],[0,0.5]],
17+
[[0,0.5],[0,1]],
18+
[[0,1],[1,1]]
19+
],
20+
21+
"3": [
22+
[[0,0],[1,0]],
23+
[[1,0],[1,1]],
24+
[[0,0.5],[1,0.5]],
25+
[[0,1],[1,1]]
26+
],
27+
28+
"4": [
29+
[[0,0],[0,0.5]],
30+
[[1,0],[1,1]],
31+
[[0,0.5],[1,0.5]]
32+
],
33+
34+
"5": [
35+
[[1,0],[0,0]],
36+
[[0,0],[0,0.5]],
37+
[[0,0.5],[1,0.5]],
38+
[[1,0.5],[1,1]],
39+
[[1,1],[0,1]]
40+
],
41+
42+
"6": [
43+
[[1,0],[0,0]],
44+
[[0,0],[0,1]],
45+
[[0,1],[1,1]],
46+
[[1,1],[1,0.5]],
47+
[[1,0.5],[0,0.5]]
48+
],
49+
50+
"7": [
51+
[[0,0],[1,0]],
52+
[[1,0],[1,1]]
53+
],
54+
55+
"8": [
56+
[[0,0],[1,0]],
57+
[[1,0],[1,1]],
58+
[[1,1],[0,1]],
59+
[[0,1],[0,0]],
60+
[[0,0.5],[1,0.5]]
61+
],
62+
63+
"9": [
64+
[[0,0],[1,0]],
65+
[[0,0],[0,0.5]],
66+
[[0,0.5],[1,0.5]],
67+
[[1,0],[1,1]]
68+
],
69+
70+
"NaN": [
71+
[[],[]]
72+
]
73+
};
74+
75+
const corner_base_positions = [
76+
[[10, 10], [78, 78]], // top-left (unchanged)
77+
[[93, 10], [165, 78]], // top-right (x + 5)
78+
[[10, 93], [78, 165]], // bottom-left (y + 5)
79+
[[93, 93], [165, 165]] // bottom-right (x + 5, y + 5)
80+
];
81+
82+
let corner_positions = [];
83+
let previous_corner_positions = [];
84+
85+
const padding = 10;
86+
const width = 175;
87+
const height = 175;
88+
89+
const colors = {
90+
"Black": [0,0,0],
91+
"White": [1,1,1],
92+
"Red": [1,0,0],
93+
"Blue": [0,0,1],
94+
"Green": [0,1,0],
95+
"Yellow": [1,1,0],
96+
"Orange": [1,0.6,0],
97+
"Purple": [0.7,0,1],
98+
"Lime": [0,1,0.5],
99+
"Cyan": [0, 1, 1],
100+
"Light Blue": [0,0.5,1],
101+
"Pink": [1,0.5,1]
102+
};
103+
104+
105+
const color_schemes = {
106+
"Lime": [[0,0,0],[1,1,1],[0,1,0.5],[0,1,0.5]],
107+
"Red": [[0,0,0],[1,1,1],[0,1,0.5],[0,1,0.5]]
108+
};
109+
110+
let bg_color = [0,0,0];
111+
let fg_color = [1,1,1];
112+
let bg_color_topo = [1,0,0.5];
113+
let fg_color_topo = [0,1,0.5];
114+
115+
function load_color_scheme(color_scheme) {
116+
bg_color = color_scheme[0];
117+
fg_color = color_scheme[1];
118+
bg_color_topo = color_scheme[2];
119+
fg_color_topo = color_scheme[3];
120+
}
121+
122+
function randomize_numbers(){
123+
previous_corner_positions = JSON.parse(JSON.stringify(corner_positions));
124+
corner_positions = JSON.parse(JSON.stringify(corner_base_positions));
125+
126+
if (!previous_corner_positions || previous_corner_positions.length === 0) {
127+
previous_corner_positions = JSON.parse(JSON.stringify(corner_positions));
128+
}
129+
130+
// Your offsets and modifications follow here as before
131+
let x_offset = Math.floor(Math.random() * 61) - 30;
132+
let left_y_offset = Math.floor(Math.random() * 61) - 30;
133+
let right_y_offset = Math.floor(Math.random() * 61) - 30;
134+
135+
[0,1,2,3].forEach(function(i) {
136+
let min_x = corner_positions[i][0][0];
137+
let min_y = corner_positions[i][0][1];
138+
let max_x = corner_positions[i][1][0];
139+
let max_y = corner_positions[i][1][1];
140+
141+
if (min_x !== padding) {
142+
min_x += x_offset;
143+
}
144+
if (max_x !== width - padding) {
145+
max_x += x_offset;
146+
}
147+
148+
let yoff = (i % 2) > 0 ? right_y_offset : left_y_offset;
149+
150+
if (min_y !== padding) {
151+
min_y += yoff;
152+
}
153+
if (max_y !== height - padding) {
154+
max_y += yoff;
155+
}
156+
157+
// Update corner_positions with new offsets
158+
corner_positions[i][0][0] = min_x;
159+
corner_positions[i][0][1] = max_x;
160+
corner_positions[i][1][0] = min_y;
161+
corner_positions[i][1][1] = max_y;
162+
});
163+
}
164+
165+
function drawThickLine(x1, y1, x2, y2, width, topoY) {
166+
let half = Math.floor(width / 2);
167+
168+
// Expand to thickness by offsetting both ends
169+
if (x1 === x2) {
170+
// Vertical line
171+
x1 -= half;
172+
x2 += half;
173+
} else if (y1 === y2) {
174+
// Horizontal line
175+
y1 -= half;
176+
y2 += half;
177+
} else {
178+
// Diagonal fallback
179+
for (let i = -half; i <= half; i++) {
180+
g.drawLine(x1 + i, y1, x2 + i, y2);
181+
}
182+
return;
183+
}
184+
185+
// Order the coordinates for fillRect
186+
let left = Math.min(x1, x2);
187+
let right = Math.max(x1, x2);
188+
let top = Math.min(y1, y2);
189+
let bottom = Math.max(y1, y2);
190+
191+
// Split based on topoY (topographic height)
192+
if (top < topoY && bottom > topoY) {
193+
// Split into two regions
194+
g.setColor(fg_color_topo[0], fg_color_topo[1], fg_color_topo[2]);
195+
g.fillRect(left, top, right, topoY);
196+
197+
g.setColor(fg_color[0], fg_color[1], fg_color[2]);
198+
g.fillRect(left, topoY + 1, right, bottom);
199+
} else if (bottom <= topoY) {
200+
// Entire line is above topo
201+
g.setColor(fg_color_topo[0], fg_color_topo[1], fg_color_topo[2]);
202+
g.fillRect(left, top, right, bottom);
203+
} else {
204+
// Entire line is below topo
205+
g.setColor(fg_color[0], fg_color[1], fg_color[2]);
206+
g.fillRect(left, top, right, bottom);
207+
}
208+
}
209+
210+
211+
212+
213+
function draw_numbers(transition_value, topo_position){
214+
//console.log(previous_corner_positions);
215+
topo_position *= height;
216+
let d = new Date();
217+
218+
let hour = d.getHours();
219+
let padded = ("0" + hour).slice(-2);
220+
221+
let tl = padded[0];
222+
let tr = padded[1];
223+
224+
let min = d.getMinutes();
225+
padded = ("0" + min).slice(-2);
226+
227+
228+
let bl = padded[0];
229+
let br = padded[1];
230+
231+
g.clear();
232+
g.setColor(bg_color_topo[0],bg_color_topo[1],bg_color_topo[2]);
233+
g.fillRect(0,0,width,topo_position);
234+
g.setColor(bg_color[0],bg_color[1],bg_color[2]);
235+
g.fillRect(0,topo_position,width,height);
236+
237+
[tl,tr,bl,br].forEach(function(corner, i) {
238+
let path = font[corner];
239+
path.forEach(function(line){
240+
let pcp = previous_corner_positions[i].slice();
241+
let ccp = corner_positions[i].slice();
242+
243+
let x1 = (ccp[0][0] - pcp[0][0]) * transition_value + pcp[0][0];
244+
let x2 = (ccp[0][1] - pcp[0][1]) * transition_value + pcp[0][1];
245+
let y1 = (ccp[1][0] - pcp[1][0]) * transition_value + pcp[1][0];
246+
let y2 = (ccp[1][1] - pcp[1][1]) * transition_value + pcp[1][1];
247+
248+
249+
250+
let point_1 = [line[0][0]*(x2-x1) + x1, line[0][1]*(y2-y1) + y1];
251+
let point_2 = [line[1][0]*(x2-x1) + x1, line[1][1]*(y2-y1) + y1];
252+
253+
drawThickLine(point_1[0], point_1[1], point_2[0], point_2[1], 10, topo_position);
254+
});
255+
});
256+
}
257+
258+
let powersaver = false;
259+
let update_timeout = null;
260+
261+
let prev_min = -1;
262+
function update_clock() {
263+
let d = new Date();
264+
let min = d.getMinutes();
265+
266+
if (min != prev_min) {
267+
randomize_numbers();
268+
prev_min = min;
269+
}
270+
271+
if (powersaver) {
272+
if (direction == 1) {
273+
draw_numbers(1, 2);
274+
}
275+
else if (direction == 0) {
276+
draw_numbers(1, 0);
277+
}
278+
update_timeout = setTimeout(update_clock, 5000);
279+
}
280+
else {
281+
let seconds = d.getSeconds() + d.getMilliseconds() / 1000;
282+
if (direction == 1) {
283+
draw_numbers(Math.min(seconds, 1) / 1, 1.00-(seconds/60));
284+
}
285+
else if (direction == 0) {
286+
draw_numbers(Math.min(seconds, 1) / 1, seconds / 60);
287+
}
288+
if (seconds < 1.5) {
289+
update_timeout = setTimeout(update_clock, 50);
290+
} else {
291+
update_timeout = setTimeout(update_clock, 1000);
292+
}
293+
}
294+
}
295+
296+
let settings = require("Storage").readJSON("flux.settings.json", 1) || {};
297+
console.log(settings);
298+
let direction = settings.direction || 0;
299+
bg_color = colors[settings.bg || "Black"];
300+
fg_color = colors[settings.fg || "White"];
301+
bg_color_topo = colors[settings.bg2 || "Green"];
302+
fg_color_topo = colors[settings.fg2 || "Black"];
303+
if (direction == 1) {
304+
let temp = bg_color;
305+
bg_color = bg_color_topo;
306+
bg_color_topo = temp;
307+
308+
temp = fg_color;
309+
fg_color = fg_color_topo;
310+
fg_color_topo = temp;
311+
}
312+
313+
314+
update_clock();
315+
316+
Bangle.on('backlight', function(isOn) {
317+
if (isOn) {
318+
powersaver = false;
319+
clearTimeout(update_timeout);
320+
update_clock();
321+
} else {
322+
powersaver = true;
323+
let d = new Date();
324+
let seconds = d.getSeconds() + d.getMilliseconds() / 1000;
325+
draw_numbers(Math.min(seconds, 1) / 1, 0);
326+
}
327+
});
328+
329+
Bangle.setUI("clock");

apps/flux/app.png

2.45 KB
Loading

0 commit comments

Comments
 (0)