--!name Multi Timer
--!icon clock
--!category Tools
local ab={"Tea","Coffee","Eggs","Pasta","Rice","Pizza","Oven","Bread","Laundry","Dryer","Game turn","Workout","Focus","Break","Nap","Parking"}local aA={3,4,8,10,18,12,30,40,60,50,2,30,25,5,20,120}local I,an,O=8,16,24*60 local f={}local S={}local T,U=0,0 local k,z,N,J,w="main",1,1,1,1 local x,i,ac,n local function L(A,ba,a6)return math.max(ba,math.min(a6,A))end local function _(a,b)return a.e and math.max(0,a.e-b)or a.l end local function X(a,b)return a.f or(a.e~=nil and a.e<=b)end local function aK(c)return(c+59)//60 end local function aG(c)local h=aK(c);return h//60 ..":"..string.format("%02d",h%60)end local function aH(h)return h//60 .." h"..(h%60>0 and" "..h%60 .." min"or"")end local function D(c)local h=aK(c)return h<60 and h.." min"or aH(h)end local function ap(a,b)local h=math.max(0,b-a.e)//60 if h<1 then return"just now"end if h<1440 then return(h<60 and h.." min"or aH(h)).." ago"end return h//1440 ..(h<2880 and" day ago"or" days ago")end local function Q(b)return b>=1700000000 end local function s(c)return os.date("%H:%M",c)end local aU={["0"]="####.##.##.####",["1"]=".#.##..#..#.###",["2"]="###..#####..###",["3"]="###..####..####",["4"]="#.##.####..#..#",["5"]="####..###..####",["6"]="####..####.####",["7"]="###..#..#..#..#",["8"]="####.#####.####",["9"]="####.####..####",}local V=19 local aY=("#"):rep(V)..("/"..("."):rep(V)):rep(3).."/"..("#"):rep(V)local function as(c)local v={}for l=1,5 do local o={}for e=1,#c do local d=c:sub(e,e)o[e]=d==":"and(".#.#."):sub(l,l)or aU[d]:sub(l*3-2,l*3)end v[l]=table.concat(o,".")end local M=math.max(2,(V-#v[1])//2)for l=1,5 do v[l]=("."):rep(M)..v[l]..("."):rep(math.max(2,V-#v[l]-M))end return table.concat(v,"/")end local function G()local aw={"M1|"..T.."|"..table.concat(S,",")}for B,a in ipairs(f)do aw[#aw+1]=a.n.."|"..a.d.."|"..(a.f and"f"..a.e or a.e and"e"..a.e or"p"..a.l)end snail.save(table.concat(aw,"\n"))end local function bg()for e,h in ipairs(aA)do S[e]=h end local c=snail.load()if type(c)~="string"or c:sub(1,3)~="M1|"then return end local y,M=c:match("^M1|(%d+)|([%d,]*)")T=tonumber(y)or 0 local e=0 for A in(M or""):gmatch("%d+")do e=e+1 if aA[e]then S[e]=L(tonumber(A),1,O)end end for u,m,h,A in c:gmatch("\n([^|\n]+)|(%d+)|([efp])(%d+)")do if#f<I then U=U+1 local a={n=u:sub(1,an),d=L(tonumber(m),60,O*60),k=U}if h=="p"then a.l=L(tonumber(A),1,O*60)else a.e,a.f=tonumber(A),h=="f"or nil end f[#f+1]=a end end end local function bd(C,P)local aj,ak=C.f and 0 or C.e and 1 or 2,P.f and 0 or P.e and 1 or 2 if aj~=ak then return aj<ak end local aI=aj==0 and-C.e or aj==1 and C.e or C.l local aJ=ak==0 and-P.e or ak==1 and P.e or P.l if aI~=aJ then return aI<aJ end return C.k<P.k end local function K(au)au=au or f[z]table.sort(f,bd)for e,a in ipairs(f)do if a==au then z=e end end end local function ay()local b=os.time()local m=b-T local at=false if T>0 and(m<0 or m>30*86400 or(not Q(T)and Q(b)))then for B,a in ipairs(f)do if a.e then a.e=a.e+m end end at=true end T=b for B,a in ipairs(f)do if a.e and not a.f and a.e<=b then a.f,at,n=true,true,nil end end if at then K()G()end end local function aq()local b,W=os.time(),nil for B,a in ipairs(f)do local c if a.f then if b-a.e<86400 then c=60-(b-a.e)%60 end elseif a.e then c=(a.e-b)%60 if c==0 then c=60 end end if c and(not W or c<W)then W=c end end snail.tick(W and math.max(1,W)*1000+50 or 0)end local function aQ(af,bh)local function aO(c)for B,a in ipairs(f)do if a~=bh and a.n==c then return true end end end if not aO(af)then return af end local a0=af:gsub(" %d+$","")for g=2,I+1 do local c=a0:sub(1,an-#tostring(g)-1).." "..g if not aO(c)then return c end end return af end local function aN()for B,a in ipairs(f)do if a.e and not a.f then return true end end return false end local function a1()if#f>=I then n,k,i=I.." timers is the most","main",nil return end U=U+1 local b=os.time()local a={n=aQ(i.n),d=i.m*60,e=b+i.m*60,k=U}if i.p then S[i.p]=i.m end f[#f+1]=a K(a)k,i="main",nil n=a.n..": "..D(a.d)..(Q(b)and", ready at "..s(a.e)or"")G()end local function aL(a,aa)local b=os.time()if aa and a.e and not a.f then a.l,a.e=_(a,b),nil elseif not aa and a.l then a.e,a.l=b+a.l,nil end end local function ao(a,c)local b=os.time()if a.f then if c<0 then return end a.e,a.f=b+c,nil else local o=_(a,b)o=L(o+c,math.min(o,60),O*60)if a.e then a.e=b+o else a.l=o end end K()G()end local function bf(a)for e,l in ipairs(f)do if l==a then table.remove(f,e);break end end G()end local aS="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwxyz|~"local function bi(e)return aS:sub(e+1,e+1)end local aV={"1234567890<","QWERTYUIOP'","ASDFGHJKL-+","^ZXCVBNM,./","()[_____]?>"}local aW={"1234567890<",":         >"}local aX="'-+,./()?"local j local q,t,H=1,2,1 local function av()return j.num and aW or aV end local function F(a,l)return av()[l]:sub(a,a)end local function ae(p,c)local ai=p=="m"j={f=p,s=c,at=#c,num=ai,max=ai and 5 or an,back=k}H=(c==""and not ai)and 1 or 0 if ai then q,t=1,1 elseif t==1 or t>5 then q,t=1,2 end k="kb"end local function aE()local p,ar=j.f,j.back local c=(j.s:gsub("^%s+",""):gsub("%s+$",""))k,j=ar,nil if p=="m"then local y,h=c:match("^(%d+):(%d+)$")local A=y and tonumber(y)*60+tonumber(h)or tonumber(c:match("^%d+$")or"")if A and A>=1 and A<=O then i.m=A elseif c~=""then n="Type minutes like 90, or 1:30"end elseif ar=="timer"then if c~=""then x.n=aQ(c,x)G()end elseif ar=="set"then if c~=""then i.n=c end elseif c~=""then i.n,k,w=c,"set",1 end end local function be()local d=F(q,t)local c,s=j.s,j.at if d=="<"then if s>0 then j.s,j.at=c:sub(1,s-1)..c:sub(s+1),s-1 end elseif d=="^"then H=(H+1)%3 elseif d=="["then j.at=math.max(0,s-1)elseif d=="]"then j.at=math.min(#c,s+1)elseif d==">"then aE()elseif#c>=j.max then n="That's as long as it can be"else if d=="_"then d=" "elseif H==0 then d=d:lower()end j.s,j.at=c:sub(1,s)..d..c:sub(s+1),s+1 if H==1 and d:match("%a")then H=0 end end end local function a7(g)if g=="top"then aE()elseif g=="ok"then be()elseif g=="left"or g=="right"then local m,d=g=="left"and-1 or 1,F(q,t)local u repeat q=(q-1+m)%11+1 u=F(q,t)until u~=" "and(d~="_"or u~="_")elseif g=="up"or g=="down"then t=(t-1+(g=="up"and-1 or 1))%#av()+1 if F(q,t)==" "then for R=1,10 do if q-R>=1 and F(q-R,t)~=" "then q=q-R;break end if q+R<=11 and F(q+R,t)~=" "then q=q+R;break end end end end end local function a8()local az=av()local v,bc={},F(q,t)=="_"for l=1,#az do local b={}for a=1,11 do local d=az[l]:sub(a,a)local Z=l==t and(a==q or(bc and d=="_"))local y,M,e=Z and 1 or 0,aX:find(d,1,true)if d==" "then e=nil;d="."elseif d:match("%u")then e=Z and d:byte()-65 elseif d:match("%d")then e=Z and 26+d:byte()-48 elseif M then e=(Z and 35+M)or(d=="/"and 45)elseif d==":"then e=Z and 66 elseif d=="<"then e=46+y elseif d=="^"then e=48+H*2+y elseif d=="["then e=54+y elseif d=="]"then e=56+y elseif d=="_"then e=58+y*3+(F(a-1,l)~="_"and 0 or F(a+1,l)~="_"and 2 or 1)else e=64+y end b[a]=e and bi(e)or d=="."and"."or("{"..d.."}")end v[l]=table.concat(b)end return table.concat(v,"/")end local a_={"pause","add","restart","rename","remove"}local aT={"restart","add","rename","remove"}local function aD(a)return a.f and aT or a_ end local function aC()local b,aM,Y={"new"},false,false for B,a in ipairs(f)do if a.f then Y=true else aM=true end end if aM then b[#b+1]="all"end if Y then b[#b+1]="clear"end return b end local function bb(g,m,E)local aB=aC()local u=#f+#aB z=L(z,1,u)local a=f[z]if m then z=(z-1+m)%u+1 elseif a then if g=="ok"then k,x,N="timer",a,1 elseif E then ao(a,E*60)end elseif g=="ok"then local r=aB[z-#f]if r=="new"then if#f>=I then n=I.." timers is the most"else k,J="pick",1 end elseif r=="all"then local aa=aN()for B,l in ipairs(f)do aL(l,aa)end n=aa and"All paused"or"All running again"K()G()elseif r=="clear"then local d=0 for e=#f,1,-1 do if f[e].f then table.remove(f,e);d=d+1 end end n=d==1 and"Cleared 1 finished timer"or"Cleared "..d.." finished timers"G()end end end local function bj(g,m,E,C)local a=x local am=aD(a)N=L(N,1,#am)if m then N=(N-1+m)%#am+1 elseif g=="top"then k,x="main",nil K(a)elseif E then ao(a,E*60)elseif g=="ok"then local r=am[N]if r=="pause"then aL(a,a.e~=nil)n=a.e and"Running again"or"Paused"K()G()elseif r=="add"then ao(a,300)n="Added 5 minutes"elseif r=="restart"then a.e,a.l,a.f=os.time()+a.d,nil,nil n="Restarted: "..D(a.d)K()G()elseif r=="rename"then ae("n",a.n)elseif a.f or C=="remove"then bf(a)n,k,x=a.n.." removed","main",nil else ac="remove"end end end function key(g)ay()if k=="kb"then n=nil a7(g)elseif not(k=="main"and g=="top")then n=nil local C=ac ac=nil local m=(g=="up"and-1)or(g=="down"and 1)or nil local E=(g=="left"and-1)or(g=="right"and 1)or nil if k=="main"then bb(g,m,E)elseif k=="pick"then local u=#ab+1 if m then J=(J-1+m)%u+1 elseif g=="top"then k="main"elseif g=="ok"then if ab[J]then i,k,w={n=ab[J],m=S[J],p=J},"set",1 else i={n="",m=10}ae("n","")end end elseif k=="set"then if m then w=(w-1+m)%4+1 elseif g=="top"then k,i="pick",nil elseif E then i.m=L(i.m+E*(w==2 and 60 or 1),1,O)elseif g=="ok"then if w<=2 then a1()elseif w==3 then ae("m","")else ae("n",i.n)end end else bj(g,m,E,C)end end aq()end function tick()ay()aq()end function start()bg()ay()K()snail.ink("fast")aq()end local function v(b,x,ax)local aP=math.max(1,math.min(x-ax//2,#b-ax+1))for e=aP,math.min(#b,aP+ax-1)do snail.row(b[e],e==x)end end local function al(b)local p,d=nil,0 for B,a in ipairs(f)do if X(a,b)then d=d+1 if not p or a.e>p.e then p=a end end end if not p then return nil end if d>1 then return p.n.." and "..(d-1).." more are ready"end return p.n.." is ready - "..ap(p,b)end local function ad(c)snail.center(true)snail.small(c)snail.center(false)end local function aF(a,b)if X(a,b)then snail.board(aY,nil,a.n.." is ready")ad("Finished "..ap(a,b)..(Q(b)and", at "..s(a.e)or""))else snail.board(as(aG(_(a,b))))ad(a.l and a.n.." - paused, "..D(a.l).." left"or a.n.." - "..(Q(b)and"ready at "..s(a.e)or D(_(a,b)).." left"))end end local function a9(a,b)if X(a,b)then return a.n.."   READY - "..ap(a,b)end if a.l then return a.n.."   paused, "..D(a.l)end return a.n.."   "..D(_(a,b))end local aR={new="+ New timer",clear="Clear finished timers"}local function a2()local p=j.f snail.title(p=="m"and"Time for "..i.n or j.back=="timer"and"Rename "..x.n or"Name the timer")snail.status(n or(j.num and""or H==2 and"CAPS"or H==1 and"Shift"or""))snail.text(j.s:sub(1,j.at).."|"..j.s:sub(j.at+1))snail.small(p=="m"and"Minutes like 45 or 90, or hours:minutes like 1:30. Up to 24:00."or"Up to 16 letters, like Sourdough or Sam's turn.")snail.board(a8())snail.hint("arrows move  BACK done  OK press")end local function a3(b)snail.title("New timer")snail.status(n or al(b)or(#f.." of "..I.." timers"))local o={}for e,u in ipairs(ab)do o[e]=u.."   "..D(S[e]*60)end o[#o+1]="+ Type a name"v(o,J,14)snail.hint("BACK timers  OK choose  UP/DN move")end local aZ={"OK start  L -1 min  R +1 min","OK start  L -1 h  R +1 h","OK type  L -1 min  R +1 min","OK type  L -1 min  R +1 min"}local function a4(b)local c=i.m*60 snail.title(i.n)snail.status(n or al(b)or"How long?")snail.board(as(aG(c)))ad(D(c)..(Q(b)and", ready at "..s(b+c)or""))snail.row("Start "..i.n.." - "..D(c),w==1)snail.row("Hours: "..i.m//60,w==2)snail.row("Type the time, like 90 or 1:30",w==3)snail.row("Name: "..i.n,w==4)snail.hint("BACK list  "..aZ[w].."  UP/DN move")end local function a5(b)local a=x local Y=X(a,b)snail.title(a.n)snail.status(n or(not Y and al(b))or(Y and"Ready"or a.e and"Running"or"Paused"))aF(a,b)for e,r in ipairs(aD(a))do local c if r=="pause"then c=a.e and"Pause"or"Resume"elseif r=="add"then c=a.f and"Snooze 5 minutes"or"Add 5 minutes"elseif r=="restart"then c="Restart ("..D(a.d)..")"elseif r=="rename"then c="Rename"elseif a.f then c="Clear it"else c=ac and"Press OK again to remove it"or"Remove"end snail.row(c,e==N)end snail.hint(a.f and"BACK timers  OK choose  R +1 min  UP/DN move"or"BACK timers  OK choose  L -1 min  R +1 min  UP/DN move")end function draw()local b=os.time()if k=="kb"then return a2()end if k=="pick"then return a3(b)end if k=="set"then return a4(b)end if k=="timer"then return a5(b)end snail.title("Multi Timer")local ah,ag=0,0 for B,a in ipairs(f)do if a.l then ag=ag+1 elseif not X(a,b)then ah=ah+1 end end snail.status(n or al(b)or(#f==0 and"No timers yet")or(ah>0 and ah.." running"..(ag>0 and", "..ag.." paused"or"")or"All paused"))local p=f[z]or f[1]if p then aF(p,b)else snail.board(as("0:00"))ad("Tea, eggs, pasta, laundry: up to "..I.." at once.")end local o={}for e,a in ipairs(f)do o[e]=a9(a,b)end for B,r in ipairs(aC())do o[#o+1]=r=="all"and(aN()and"Pause all"or"Resume all")or aR[r]end v(o,z,10)if#o<=8 then snail.small("Timers keep counting while the app is closed.")end local a=f[z]snail.hint(not a and"BACK leave  OK choose  UP/DN move"or a.f and"BACK leave  OK open  R +1 min  UP/DN move"or"BACK leave  OK open  L -1 min  R +1 min  UP/DN move")end
