Forum Archive

Today Widget - Just some comments

Phuket2

Sorry if this has been covered before.
But I tried to use the Today Widget, today (I have tried before, but long time ago). Anyway, I was suprised about what I could do.
I was able to load a CustomView using @JonB PYUILoader (loading a pyui file into a Custom Class) , I was able to use the update method in the custom class to update a timer value. Interact with a database (a single call for now).

Anyway, it all just works. Well execpt for the bg_color. The way I am doing it, I end up with a white bg_color. I tried setting the bg_color = None, that does not work. I will have to find out how to do that.

but the below code is not runable on anothers device, I just wanted to start something and see if things like update method would work.
I am not sure if different devices get more memory to run today widgets or not.
So the below code is not meant to be smart/polished. Just wanted to see if it would work.

#!python3

import appex
import ui
import arrow
from tinydb import TinyDB, where


class PYUILoader(ui.View):
    # this acts as a normal Custom ui.View class
    # the root view of the class is the pyui file read in
    def WrapInstance(obj):
        class Wrapper(obj.__class__):
            def __new__(cls):
                return obj
        return Wrapper

    def __init__(self, pyui_fn, *args, **kwargs):
        bindings = globals().copy()
        bindings[self.__class__.__name__] = self.WrapInstance()

        ui.load_view(pyui_fn, bindings)

        # call after so our kwargs modify attrs
        super().__init__(*args, **kwargs)


def GetTimerFinished(db_fn, timer_name):
    with TinyDB(db_fn) as db:
        rec = db.search(where('timer_name') == timer_name)
        return rec[0]


def SetTimerFinished(db_fn, timer_name, days=0, hours=0, minutes=0):
    utc = arrow.utcnow()
    finish_time = utc.shift(days=+days, hours=+hours, minutes=+minutes)
    rec = dict(timer_name=timer_name,
               entered=utc.for_json(),
               finish_time=finish_time.for_json(),
               elapased=False,
               )
    with TinyDB(db_fn) as db:
        db.upsert(rec, where('timer_name') == rec['timer_name'])
        return rec


class AppexTestClass(PYUILoader):
    def __init__(self, pyui_fn, db_fn, timer_name, *args, **kwargs):
        super().__init__(pyui_fn, *args, **kwargs)
        #self.bg_color = None
        self.db_fn = db_fn
        self.timer_name = timer_name
        self.local_finish_time = 0
        rec = GetTimerFinished(db_fn, self.timer_name)

        self.update_interval = 1
        self.count = 0
        self.expired = False
        self.calc_time()
        self['f_timer_name'].text = rec['timer_name']

    def calc_time(self):
        timer_rec = GetTimerFinished(self.db_fn, self.timer_name)
        self.timer_name = timer_rec['timer_name']
        utc = arrow.get(timer_rec['finish_time'])
        local_finish_time = utc.to('Asia/Bangkok')
        self.local_finish_time = local_finish_time

        if self.local_finish_time < arrow.now():
            self.expired = True

    def time_remaining(self):
        return self.local_finish_time - arrow.now()

    def update(self):
        if self.expired:
            self['f_time_left'].text = 'Expired'
            return
        self['f_time_left'].text =\
            self.format_timedelta(self.local_finish_time - arrow.now())

    def format_timedelta(self, td):
        hours, remainder = divmod(td.total_seconds(), 3600)
        minutes, seconds = divmod(remainder, 60)
        hours, minutes, seconds = int(hours), int(minutes), int(seconds)
        if hours < 10:
            hours = '0%s' % int(hours)
        if minutes < 10:
            minutes = '0%s' % minutes
        if seconds < 10:
            seconds = '0%s' % seconds
        return '%s:%s:%s' % (hours, minutes, seconds)


def main():
    db_fn = 'my_timers.json'
    pyui_fn = 'appex_test_ui.pyui'
    timer_name = 'Wonder Wars'
    #SetTimerFinished(db_fn, timer_name, hours=5, minutes=35 )
    v = AppexTestClass(pyui_fn, db_fn, timer_name)
    appex.set_widget_view(v)


if __name__ == '__main__':
    main()

Screen shot

ccc
    def format_timedelta(self, td):
        hours, remainder = divmod(td.total_seconds(), 3600)
        minutes, seconds = divmod(remainder, 60)
        return '%02.0f:%02.0f:%02.0f' % (hours, minutes, seconds)
Phuket2

@ccc , thanks. Actually I just copied that code from Stackflow. I hadn't realised before that there is no built formatting for TimeDelta objects. The method posted is also good enough as its limited to by hours. I got another code snippet off ActiveCode that handles Weeks all the way down to microsecond's if you need it. The code is here.
I am still looking at the arrow docs to see if it has support to format TimeDelta objects. As far as I see it doesn't. When you do a calculation operation in arrow, you are returned a TimeDelta object, not an arrow object. Just for fun, I will look at Maya to see if it supports it. Would not make me swap if it did though because arrow ships with Pythonista. Oh, arrow has the humanise method, not the same but still handy.

I went through the code above more closely and see I had quite a few mistakes I fixed up. Mainly the way I built it up. Wasn't sure what would work. I am suprised it works. Using the PYUILoader and update functions etc... But I guess its a nice testament to how well things are put together in Pythonista.

The other thing that I was able to do was add a table to the view. I didn't try interacting with it. Just added a TableView on in the Designer. I think this maybe getting to the edge though, as I often got 'Unable to Load' message in the Today view. I didn't realise at first, but that message is a button. If you tap it the view loads.

Screen Shot of TableView in Today widget. Maybe this is no news for a lot of people. I am not sure I have seen a scrolling table in a Today Widget before (I mean from an company).

chriswilson

@Phuket2

Regarding the clear background thing: I’ve just started playing with the Today Widget as well and I used this code to load a standard PYUI file (as might be done for a normal script). It also sets a clear background. I hope it’s helpful!

v = ui.load_view()
v.background_color = ui.set_color("clear")
appex.set_widget_view(v)
Phuket2

@chriswilson , thanks for the reply. However, I am not sure "clear" is actually recognised as a legitimate param (if its a css name then it would be), but calling ui.set_color(x) if x is not recognised it will set the bg_color to (0.0, 0.0, 0.0, 0.0) regardless - tuple of RGBA. Eg. You could call ui.set_color(None) or ui.set_color("Hey_Jude") and it will still return (0.0, 0.0, 0.0, 0.0).

Again, maybe "clear" is recognised, but maybe its not, and the default behaviour for an un-recognised param is being returned

chriswilson

@Phuket2

Ah I see. Good to know. I tried this and it got me a nice transparent background to my widget, but of course setting alpha to zero would do the same thing!

I was basing this on the Swift iOS colours (UIColor class which has a UIColor.clear) rather than CSS. I’m not sure if this is what is going on behind the scenes of the UI module though.

Phuket2

@chriswilson, I just looked up the css colours and there is no 'clear' defined. It makes sense as its only RGB. But it still could have been an alias representation, you never know :)
css colors
But as you say its still good to find out.