Created
October 10, 2020 19:44
-
-
Save cvpe/eb667c6256f150b3dbd115ce986c8767 to your computer and use it in GitHub Desktop.
WeightWatchers.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
todo | |
.. Priorité | |
ok - record Aliment points_base et base et option ingredients | |
.. - 1️⃣ revoir add_food | |
.. - ❗️pas créer aliment si nom existe déjà | |
check dans ecran où on tape le nom et le ok | |
.. - ecrans où on doit checker | |
.. - nom: pas vide, inexistant | |
.. - kcal: vide? ou numerique | |
.. - ... | |
ok - 🆗 aliment type base | |
ok - call add_food | |
ok - test et check si dans liste aliments | |
ok - appel direct à use_food | |
ok - use dans repas | |
ok - 🆗 aliment à ingrédients | |
ok - call add_food | |
ok - test et check si dans liste aliments | |
ok - appel direct à use_food | |
ok - use dans repas | |
.. - 2️⃣ aliment à barcode | |
.. - call add_food | |
.. - test et check si dans liste aliments | |
.. - appel direct à use_food | |
.. - use dans repas | |
.. - 3️⃣ select recette => ecran usage special multi aliments/quantités | |
ok - nom/pts par unité/qté textfield points calculés | |
.. - 🛑crash at ok..... | |
memo tf dans cc.values du textfield du dialog | |
.. - utiliser recette en tout (melange) ou par element | |
aussi bien en creation (fields total gr, pts) qu'en usage' | |
ex: galettes = ingredients mais total 860 gr = 225 points | |
usage: 25 gr => calcul points | |
.. - add elements recette to meal | |
.. - 4️⃣ creer recette, comme un repas en y ajoutant free ou aliment | |
.. 🛑 bugs | |
.. - search in db record does not work | |
.. ➖ bonus? | |
.. - ajouter 'groupe' aux aliments ex: boissons => sections | |
.. ➖ technique | |
.. - py et db sur icloud | |
.. - tests sur iphone | |
.. ➖ db update | |
.. - enlever le recreate db de chaque run | |
ok - technical button | |
ok - tableview | |
ok - section = rec_type | |
ok - row = rec_key | |
ok - sort sur key: param/user/poids/points/recettes/aliments | |
ok - textfield de recherche | |
.. - 0️⃣select => ui.View | |
.. - textfield rec_key | |
.. - textfield rec_subkey | |
.. - textview rec_data | |
.. - ou tableview si array de dict comme les repas? | |
.. - boutons add/delete/update | |
.. - update aliment => montre detail, meme ingredients | |
.. ➖ fichier départ | |
ok - user | |
ok - poids | |
ok - points | |
ok - aliments ean, titre, ingredients, unite=100gr, type portion revoir | |
.. ex: ean seul, le reste sera automatique | |
.. ex: pommes de terre,... | |
.. - ean seul => web, genere record aliment avec thumb | |
.. - repas? | |
.. ➖ boissons via menu particulier ou parmi aliments? | |
ok ➖ 🆗 ecran principal | |
ok - ui objets dans ui.view, pas dialog, | |
ok - semaine en cours, current, jours faits, jour a faire | |
ex: Ve 23 et en dessous points du jour | |
couleur vert/rouge suivant points si fait, bg si today | |
ok - bouton date => change jour | |
ok - alors boutons < et > pour semaine precedente et suivante | |
ok - si aucun jour dans db, tout gris et vide | |
ok - date de today au debut | |
ok - call set_day | |
ok - créer record Points si existe pas (voir plus bas) | |
ok - boutons jour prec, suivant, max aujourd'hui | |
ok - calcul compteurs de points et les afficher | |
ok - grille des points jour/hebdo | |
ok - pour demain doit etre 0 si dimanche | |
ok - points utilises ce jour *** rouge si > 23 | |
ok - tableview sections=petitdej, dej, souper, collations | |
ok - header: points chaque repas | |
ok - TableView sans header et footer | |
ok - row repas, deux labels | |
ok - row | |
ok - soit aliment | |
ok subtitle: nn points 30 gr de xxxxxxx | |
ok - utiliser f"" format | |
ok - soit recette dont on voit les aliments | |
ok - db record: ajoute option 'recette' | |
ok - affichage en 2eme ligne "ingrédient de la recette xxx" | |
ok - db record | |
Points|date|reserve hebdo matin|[aliments,...] | |
les points consommés sont dans les aliments, donc on peut | |
afficher: consommés,reserve hebdo restante, points restants | |
aliment = {'repas':1, aliment':nom, 'points':3, 'quantité':25,'points base':12, 'base':(100,'gr')} | |
ok - did_select aliment | |
ok - no action si row de repas | |
ok - --> ecran usage | |
ok - init segment repas où se trouve cet aliment | |
ok - delete | |
ok - ask confirmation | |
ok - change repas | |
ok - save record | |
ok - aliment zero point: pas de quantité et special pour 0 points | |
.. ➖ paramètres | |
ok - bouton | |
.. - db record 'Param', , ,{'units':(gr,cl,cs,...)} | |
.. - tableview | |
.. - unités | |
.. - créer liste qu'on utilisera dans segments ou rollpicker | |
.. - gr | |
.. - cl | |
.. - ml | |
.. - cuillère à soupe (cs) = 15 gr/cl | |
.. - cuillère à café (cc) = 5 gr/cl | |
.. - pièce | |
.. - noms des repas | |
ok ➖ user | |
ok - bouton | |
ok - db record | |
ok - dialog infos | |
ok - record db | |
.. - add | |
.. - update | |
ok ➖ 🆗 poids | |
ok - bouton aujourd'hui -> ajoute, init dateroller à aujourd'hui | |
ok - db record | |
ok - graphique | |
ok - légende | |
ok - support 0 record | |
ok - graphique 1 pt = x | |
ok - bug: trier dates des poids | |
ok - select, puis comme add_button | |
ok - comment faire delete? switch? | |
ok - confirm | |
ok - add => refresh liste et graphique (retour et recall dialog) | |
ok - check poids introduit existe et numerique | |
ok - checks dans action dans my_form_dialog | |
ok - add: check new date n'existe pas | |
ok - upd: si date et poids inchangés, alert et aucune action | |
ok - upd: check new date n'existe pas | |
ok - actions sur db dans main view | |
ok - add: add record | |
ok - del: del record | |
ok - upd: upd record meme si date change | |
ok ➖ 🆗 evolution points | |
ok - bouton calendrier | |
.. - graphique optionnel? | |
ok - tableview non editable | |
ok - support 0 record | |
ok - graphique 1 pt = x | |
ok - points et param 23, pas reserve | |
.. - bouton detail? => liste complete des aliments | |
.. - use un de ces aliments dans repas d'aujourd'hui (ecran usage) | |
ok - graphique | |
ok - légendes | |
ok - points du jour | |
ok - points reserve utilises | |
ok - horizontale | |
ok - select => set date de ecran principal | |
.. ➖ 1️⃣ ecran usage d'un aliment | |
.. - via def avec parametres tout ce qui est necessaire | |
.. - titre en parametre | |
ok - pour aliment zero point | |
ok - pour aliment existant selecté | |
.. - pour aliment calculatrice | |
.. - pour aliment via barcode | |
ok - pour aliment d'un repas existant selecté | |
.. - quantité | |
.. - roller | |
.. - check numerique si via textfield | |
ok - qui devrait changer les points directement dans le dialog | |
.. - choix poids ou portion | |
.. - sauf si zero points | |
ok - calcul et affichage des points | |
ok - choix pour quel repas (segment?) | |
ok - repas en cours par defaut | |
ok - si select segment, set repas en cours | |
ok ➖ 🆗 liste aliments zero points | |
ok - bouton | |
ok - db record | |
ok - tableview | |
ok - select => usage | |
ok - add aliment to day | |
ok ➖ 🆗 liste aliments (non zero) | |
ok - bouton | |
ok - db record | |
'Aliment', nom, barcode, | |
ex: {'points':12, 'base':(100,'gr'), 'kcal':359,'saturated_fat':0.5,'sugars':1.5,'proteins':12.5, 'thumb':None/data, 'portion':('pièce',7,'gr'), 'groupe':'féculents'} | |
ok - tableview thumb nom | |
ok - sort sur nom | |
ok - row: nom / 12 points pour 100 gr / thumb | |
ok - mettre subtitle à la fin de la row | |
ok - row moins large | |
ok - use f format | |
ok - select => usage => add to day meal | |
.. ➖ def add aliment dans db | |
.. - revoir parametres => record dictionnary | |
.. - si existe: demander si update, ou changement nom | |
.. - attention pas nom de aliment zeropoint!!!!!!! | |
.. - oui => update db | |
.. - ❗️si changement points, scan passé check si deja utilise dans | |
repas ou jour | |
.. - non => retour dans ecran appelant | |
ok - si existe pas: add dans db | |
.. ➖ ingredients | |
ok - bouton | |
ok - dialog ingredients + nom donné? | |
.. - ❗️pour un poids/volume/... choix base | |
ok - smartpoints calculé pendant frappe | |
.. - check numerique et autre | |
ok - add dans db | |
.. - select => usage | |
.. ➖ scan | |
ok - bouton | |
.. - possibilite taper barcode | |
ok - scan barcode scanner.py | |
ok - get infos du web.... | |
ok - display titre, image, infos graisses...., bouton voir page detail | |
ok - webview | |
ok - poids une portion, ex: 1 speculoos | |
ok - points une portion | |
.. - memo db ean/titre/ingredients/unit/100/thumb? pour list view | |
.. ➖ liste de recettes connus | |
ok - bouton | |
ok - db record | |
'recette' nom '' [{'aliment':'xxxx','quantité':20,'unit':'gr','points':5},... | |
ok - tableview | |
ok - tri | |
ok - recherche | |
ok - select => usage, modifiable par exemple, un peu plus de fromage | |
ok - meme que usage un seul aliment? mais plusieurs lignes? | |
.. - check numerique | |
.. - comment créer un recette? à partir d'un repas? | |
.. - dans ecran principal, bouton 'creation recette' | |
.. - puis des check apparaissent au lieu des points sur aliments | |
.. - puis sorte d'ecran usage où il faut choisir nom + rollers qté | |
.. - ❗️ comme ajouter aliment a day mais ici a recette | |
.. - quid si aliment zeropoint dans recette, pas memes zones? | |
ok ➖ 🆗 search: | |
ok - locate at first | |
ok - boutons next, previous | |
ok ➖ 🆗 at close, backup db local sur icloud drive | |
''' | |
import ast | |
import collections | |
import console | |
from ctypes import c_void_p | |
from datetime import datetime, timedelta | |
import dialogs | |
from functools import partial | |
import importlib | |
import io | |
import json | |
from objc_util import * | |
import os | |
from PIL import Image | |
import requests | |
import shutil | |
import sqlite3 | |
import swizzle | |
import sys | |
import ui | |
import unicodedata | |
import matplotlib.pyplot as plt | |
import matplotlib.dates as mdates | |
import matplotlib | |
import numpy as np | |
from io import BytesIO | |
PY3 = sys.version_info[0] >= 3 | |
if PY3: | |
basestring = str | |
NSLocale = ObjCClass('NSLocale') | |
fr_locale = NSLocale.alloc().initWithLocaleIdentifier_(ns('fr_FR')) | |
# needed for barcode scan: begin | |
AVCaptureSession = ObjCClass('AVCaptureSession') | |
AVCaptureDevice = ObjCClass('AVCaptureDevice') | |
AVCaptureDeviceInput = ObjCClass('AVCaptureDeviceInput') | |
AVCaptureMetadataOutput = ObjCClass('AVCaptureMetadataOutput') | |
AVCaptureVideoPreviewLayer = ObjCClass('AVCaptureVideoPreviewLayer') | |
dispatch_get_current_queue = c.dispatch_get_current_queue | |
dispatch_get_current_queue.restype = c_void_p | |
def captureOutput_didOutputMetadataObjects_fromConnection_(_self, _cmd, _output, _metadata_objects, _conn): | |
global MainView | |
objects = ObjCInstance(_metadata_objects) | |
for obj in objects: | |
s = str(obj.stringValue()) | |
if s != MainView.barcode: | |
MainView.barcode = s | |
#print(s) | |
MainView.scan_view.close() | |
MetadataDelegate = create_objc_class('MetadataDelegate', methods=[captureOutput_didOutputMetadataObjects_fromConnection_], protocols=['AVCaptureMetadataOutputObjectsDelegate']) | |
# needed for barcode scan: end | |
def my_show_datepicker(self,mode): | |
original_show_datepicker(self,mode) | |
date_picker_pntr = ObjCInstance(self.date_picker) | |
date_picker_pntr.setLocale_(fr_locale) | |
importlib.reload(dialogs) # to avoid infinite recursion | |
original_show_datepicker = dialogs._FormDialogController.show_datepicker | |
dialogs._FormDialogController.show_datepicker = my_show_datepicker | |
def my_tableview_did_select(tv, section, row): | |
global cc | |
#print(section, row) | |
#print(cc.container_view.name) | |
if 'ajoute' in cc.container_view.name: | |
pass | |
elif 'la db' in cc.container_view.name: | |
# selection of a db record | |
cc.select = (section, row) | |
cc.container_view.close() | |
return | |
elif 'zero' in cc.container_view.name: | |
# selection of a zero point aliment | |
cc.select = (section, row) | |
cc.container_view.close() | |
return | |
elif 'aliments' in cc.container_view.name: | |
# selection of existing aliment | |
cc.select = (section, row) | |
cc.container_view.close() | |
return | |
elif 'poids' in cc.container_view.name: | |
ui.delay(poids_button_action,0.1) | |
return | |
elif 'Calendrier' in cc.container_view.name: | |
cc.select = (section, row) | |
cc.container_view.close() | |
return | |
elif 'recettes' in cc.container_view.name: | |
cc.select = (section, row) | |
cc.container_view.close() | |
return | |
cc.original_tableview_did_select(tv, section, row) | |
def my_tableview_cell_for_row(tableview, section, row): | |
global cc | |
if cc.container_view.name == 'Liste des aliments': | |
orig_cell = cc.cells[section][row] | |
cell = ui.TableViewCell() | |
cell.bg_color = 'white' | |
h = cc.view.row_height | |
field = cc.sections[section][1][row] | |
# thumb 5 name 5 subtitle 5 | |
# original row text | |
t = orig_cell.text_label.text | |
l1 = ui.Label(name='l1') | |
l1.text_color = orig_cell.text_label.text_color | |
l1.font = ('Menlo',16) | |
l1.text = t | |
w = (tableview.width - h - 3*5)*3/4 | |
l1.frame = (h+5,0,w,h) | |
cell.content_view.add_subview(l1) | |
# eventual row subtitle | |
if 'subtitle' in field: | |
l2 = ui.Label() | |
l2.text_color = 'blue' | |
l2.font = ('Menlo',12) | |
l2.text = field['subtitle'] | |
x = l1.x + l1.width + 5 | |
w = tableview.width - x - 5 | |
l2.frame = (x,0,w,h) | |
cell.content_view.add_subview(l2) | |
# eventual thumb | |
if orig_cell.image_view.image: | |
thumb = ui.ImageView() | |
thumb.frame = (tableview.width-h,0,h,h) | |
thumb.image = orig_cell.image_view.image | |
thumb.content_mode = ui.CONTENT_SCALE_ASPECT_FIT | |
cell.content_view.add_subview(thumb) | |
return cell | |
elif 'Ajoute recette' in cc.container_view.name: | |
if 'Ingrédients' not in cc.sections[section][0]: | |
return cc.cells[section][row] | |
cell = ui.TableViewCell() | |
cell.bg_color = 'white' | |
field = cc.sections[section][1][row] | |
# viande 200 gr --> 4 points | |
# 2 points / 100 gr | |
# l1 et l2 tf l3 l4 l5 l6 | |
# subtitle | |
h = cc.view.row_height | |
h1 = h*2/3 | |
l5 = ui.Label(name='l5') | |
l5.frame = (tableview.width-5-80,0,80,h) | |
l5.font = ('Menlo',12) | |
l5.text = str(field['points']) + ' points' | |
l5.alignment = ui.ALIGN_RIGHT | |
cell.content_view.add_subview(l5) | |
l4 = ui.Label() | |
l4.frame = (l5.x-30,0,30,h) | |
l4.font = ('Menlo',12) | |
l4.text = '-->' | |
cell.content_view.add_subview(l4) | |
l3 = ui.Label(name='l3') | |
l3.text_color = 'blue' | |
l3.font = ('Menlo',12) | |
l3.text = field['units'] | |
l3.frame = (l4.x-40,0,40,h) | |
cell.content_view.add_subview(l3) | |
tf = ui.TextField(name='ingredient_of_recette') | |
tf.font = ('Menlo',16) | |
tf.alignment = ui.ALIGN_RIGHT | |
tf.frame = (l3.x-100-5,(h-20)/2,100,20) | |
tf.text = str(field['quantity']) | |
tf.delegate = c | |
tf.l5 = l5 | |
tf.values = field['key'] | |
tf.points_per_unit = field['points_per_unit'] | |
cell.content_view.add_subview(tf) | |
t = field['title'] | |
l1 = ui.Label(name='l1') | |
l1.text_color = 'black' | |
l1.font = ('Menlo',16) | |
l1.text = t | |
l1.frame = (5,0,tf.x-5,h1) | |
cell.content_view.add_subview(l1) | |
l2 = ui.Label() | |
l2.text_color = 'blue' | |
l2.font = ('Menlo',10) | |
l2.text = field['subtitle'] | |
l2.frame = (l1.x,l1.y+l1.height,l1.width,h-h1) | |
cell.content_view.add_subview(l2) | |
return cell | |
else: | |
return cc.cells[section][row] | |
def ui2pil(ui_img): | |
return Image.open(io.BytesIO(ui_img.to_png())) | |
def pil2ui(imgIn): | |
with io.BytesIO() as bIO: | |
imgIn.save(bIO, 'PNG') | |
imgOut = ui.Image.from_data(bIO.getvalue()) | |
del bIO | |
return imgOut | |
def smartpoints(kcal,saturated_fat,sugars,proteins): | |
# Smart Points Food Value Calculation | |
# (Calories * .0305) + (Sat Fat * .275) + (Sugar * .12) - (Protein * .098) | |
# https://www.calculator.net/weight-watchers-points-calculator.html | |
sp = 0.0305*kcal + 0.275*saturated_fat + 0.12*sugars - 0.098*proteins | |
return int(sp) # or round(sp)? | |
def scroll_to(loc): | |
global cc | |
section, row = loc | |
tblo = ObjCInstance(cc.view) # ui.TableView | |
NSIndexPath = ObjCClass("NSIndexPath") | |
nsindex = NSIndexPath.indexPathForRow_inSection_(row,section) | |
UITableViewScrollPositionTop = 1 | |
tblo.scrollToRowAtIndexPath_atScrollPosition_animated_(nsindex, UITableViewScrollPositionTop, True) | |
def my_textfield_did_change(textfield): | |
global cc | |
#print(textfield.name) | |
if '🔍' in textfield.name: | |
# field is search field | |
# sort, searched first, gray other ones, not selectable | |
txt = textfield.text | |
txt = unicodedata.normalize('NFKD', txt).encode('ASCII', 'ignore') | |
txt = str(txt,'utf-8').upper() | |
cc.search_locations = [] | |
cc.search_locations_current = -1 | |
for s in range(0,len(cc.sections)): | |
for i in range(0,len(cc.cells[s])): # loop on rows of section s | |
cell = cc.cells[s][i] # ui.TableViewCell of row i | |
# cell is only a label, or has a TextField subview | |
# but if we use cell_for_row, like in 'Liste aliments', | |
# then an ow label is used instead of cell.text_label | |
tf = cell.text_label | |
t = tf.text | |
t = unicodedata.normalize('NFKD', t).encode('ASCII', 'ignore') | |
t = str(t,'utf-8').upper() | |
if txt not in t: | |
tf.text_color = 'lightgray' | |
else: | |
tf.text_color = 'black' | |
cc.search_locations.append((s,i)) | |
if cc.search_locations_current < 0: | |
cc.search_locations_current = 0 | |
scroll_to(cc.search_locations[cc.search_locations_current]) | |
cc.view.reload_data() | |
elif textfield.name == 'ingredient_of_recette': | |
textfield.l5.text = str(int(float(textfield.text)*textfield.points_per_unit)) + ' points' | |
cc.values[textfield.values] = textfield.l5.text | |
elif textfield.name in ['Kcal', 'Graisses saturées', 'Sucre', 'Protéines']: | |
for s in range(0,len(cc.sections)): | |
for i in range(0,len(cc.cells[s])): # loop on rows of section 1 | |
cell = cc.cells[s][i] # ui.TableViewCell of row i | |
if len(cell.content_view.subviews) > 0: | |
tf = cell.content_view.subviews[0] # ui.TextField of value in row | |
if type(tf) is ui.TextField: | |
t = cell.text_label.text | |
try: | |
v = float(tf.text) | |
except: | |
v = 0 | |
if t == 'Kcal': | |
kcal = v | |
elif t == 'Graisses saturées': | |
saturated_fat = v | |
elif t == 'Sucre': | |
sugars = v | |
elif t == 'Protéines': | |
proteins = v | |
elif t == 'Points': | |
tf.text = str(sp) | |
tf.text_color = 'red' | |
cc.original_textfield_did_change(tf) | |
if 'Ingrédients' in cc.sections[s][0]: | |
sp = smartpoints(kcal,saturated_fat,sugars,proteins) | |
elif 'quantité (en' in textfield.name: | |
for s in range(0,len(cc.sections)): | |
if 'Points' not in cc.sections[s][0]: | |
continue | |
# Section 'Points' | |
for i in range(0,len(cc.cells[s])): # loop on rows of section 1 | |
cell = cc.cells[s][i] # ui.TableViewCell of row i | |
if len(cell.content_view.subviews) > 0: | |
tf = cell.content_view.subviews[0] # ui.TextField of value in row | |
if type(tf) is ui.TextField: | |
t = cell.text_label.text.strip() # "12 points par 100 gr" | |
i = t.find(' ') | |
np_base = int(t[:i]) # 12 | |
t = t[i:].replace(' points par ','').strip() # 100 gr | |
i = t.find(' ') | |
qt_base = int(t[:i]) # 100 | |
np = int(np_base*float('0'+textfield.text)/qt_base) | |
tf.text = str(np) | |
tf.text_color = 'red' | |
cc.original_textfield_did_change(tf) | |
break | |
cc.original_textfield_did_change(textfield) | |
def my_form_dialog(title='', fields=None, sections=None, done_button_title='ok', dims=None, graphique_button=False, graphic_data=None, poids_buttons=False, web_button=False, base_button=False, calc_button=False, scan_button=False, ui_image=False, web_url=None, editable=True, legends=None, ylabel=None, search=None, row_height=None): | |
global cc, MainView | |
if not sections and not fields: | |
raise ValueError('sections or fields are required') | |
if not sections: | |
sections = [('', fields)] | |
if not isinstance(title, basestring): | |
raise TypeError('title must be a string') | |
for section in sections: | |
if not isinstance(section, collections.Sequence): | |
raise TypeError('Sections must be sequences (title, fields)') | |
if len(section) < 2: | |
raise TypeError('Sections must have 2 or 3 items (title, fields[, footer]') | |
if not isinstance(section[0], basestring): | |
raise TypeError('Section titles must be strings') | |
if not isinstance(section[1], collections.Sequence): | |
raise TypeError('Expected a sequence of field dicts') | |
for field in section[1]: | |
if not isinstance(field, dict): | |
raise TypeError('fields must be dicts') | |
cc = dialogs._FormDialogController(title, sections, done_button_title=done_button_title) | |
cc.view.allows_multiple_selection = False | |
cc.original_tableview_did_select = cc.tableview_did_select | |
cc.tableview_did_select = my_tableview_did_select | |
cc.original_textfield_did_change = cc.textfield_did_change | |
cc.textfield_did_change = my_textfield_did_change | |
cc.tableview_cell_for_row = my_tableview_cell_for_row | |
if row_height: | |
cc.view.row_height = row_height | |
if dims: | |
cc.container_view.frame = (0, 0, dims[0],dims[1]) | |
#cc.view.row_height = 32 | |
if search: | |
w,h = cc.container_view.width, cc.container_view.height | |
cc.view.frame = (0,50,w,h-50) | |
cc.view.flex = '' | |
cc.loupe = ui.Label() | |
cc.loupe.frame = (0,0,50,50) | |
cc.loupe.text = ' 🔍 ' | |
cc.loupe.background_color = 'white' | |
cc.container_view.add_subview(cc.loupe) | |
cc.search = ui.TextField(name='🔍') | |
cc.search.placeholder = 'tapez ici un texte à chercher' | |
cc.search.frame = (50,0,w-150,50) | |
cc.search.delegate = c | |
cc.container_view.add_subview(cc.search) | |
cc.prev_search = ui.Button() | |
cc.prev_search.background_color = 'white' | |
cc.prev_search.frame = (cc.search.x+cc.search.width,0,50,50) | |
cc.prev_search.image = ui.Image.named('iob:ios7_arrow_back_32') | |
def prev_search_action(sender): | |
if cc.search_locations_current >= 0: | |
cc.search_locations_current -= 1 | |
if cc.search_locations_current < 0: | |
cc.search_locations_current = len(cc.search_locations)-1 | |
scroll_to(cc.search_locations[cc.search_locations_current]) | |
cc.prev_search.action = prev_search_action | |
cc.container_view.add_subview(cc.prev_search) | |
cc.next_search = ui.Button() | |
cc.next_search.background_color = 'white' | |
cc.next_search.frame = (cc.search.x+cc.search.width+50,0,50,50) | |
cc.next_search.image = ui.Image.named('iob:ios7_arrow_forward_32') | |
def next_search_action(sender): | |
if cc.search_locations_current >= 0: | |
cc.search_locations_current += 1 | |
if cc.search_locations_current == len(cc.search_locations): | |
cc.search_locations_current = 0 | |
scroll_to(cc.search_locations[cc.search_locations_current]) | |
cc.next_search.action = next_search_action | |
cc.container_view.add_subview(cc.next_search) | |
cc.search_locations_current = -1 | |
if graphique_button: | |
graphique_button = ui.ButtonItem() | |
graphique_button.tint_color = 'green' | |
graphique_button.title = 'graphique' | |
graphique_button.action = graphique_button_action | |
# right buttons is a tuple, the way to add an element is "+(element,)" | |
cc.container_view.right_button_items = cc.container_view.right_button_items + (graphique_button,) | |
cc.legends = legends | |
cc.ylabel = ylabel | |
if poids_buttons: | |
poids_add_button = ui.ButtonItem() | |
poids_add_button.tint_color = 'green' | |
poids_add_button.title = "ajoute" | |
poids_add_button.action = poids_button_action | |
# right buttons is a tuple, the way to add an element is "+(element,)" | |
cc.container_view.right_button_items = cc.container_view.right_button_items + (poids_add_button,) | |
if web_button: | |
web_button = ui.ButtonItem() | |
web_button.tint_color = 'green' | |
web_button.title = 'détails' | |
web_button.action = web_button_action | |
cc.url = web_url | |
# right buttons is a tuple, the way to add an element is "+(element,)" | |
cc.container_view.right_button_items = cc.container_view.right_button_items + (web_button,) | |
if base_button: | |
base_button = ui.ButtonItem() | |
base_button.tint_color = 'green' | |
base_button.title = '🥑' | |
base_button.action = base_button_action | |
# right buttons is a tuple, the way to add an element is "+(element,)" | |
cc.container_view.right_button_items = cc.container_view.right_button_items + (base_button,) | |
if calc_button: | |
calc_button = ui.ButtonItem() | |
calc_button.tint_color = 'green' | |
calc_button.image = ui.Image.named('iob:ios7_calculator_outline_32').with_rendering_mode(ui.RENDERING_MODE_ORIGINAL) | |
calc_button.action = calc_button_action | |
# right buttons is a tuple, the way to add an element is "+(element,)" | |
cc.container_view.right_button_items = cc.container_view.right_button_items + (calc_button,) | |
if scan_button: | |
scan_button = ui.ButtonItem() | |
scan_button.tint_color = 'green' | |
scan_button.image = ui.Image.from_data(MainView.UIImage_data).with_rendering_mode(ui.RENDERING_MODE_ORIGINAL) | |
scan_button.action = scan_button_action | |
# right buttons is a tuple, the way to add an element is "+(element,)" | |
cc.container_view.right_button_items = cc.container_view.right_button_items + (scan_button,) | |
for s in range(0,len(cc.sections)): | |
for i in range(0,len(cc.cells[s])): # loop on rows of section s | |
cell = cc.cells[s][i] # ui.TableViewCell of row i | |
#cell.text_label.font = ('Courier',16) # font with 'echappement fixe | |
# some fields types are subviews of the cell: | |
# text,number,url,email,password,switch | |
# but check, date and time are not set as subviews of cell.content_view | |
if len(cell.content_view.subviews) > 0: | |
tf = cell.content_view.subviews[0] # ui.TextField of value in row | |
if type(tf) is ui.TextField: | |
tf.alignment=ui.ALIGN_RIGHT | |
if not editable or 'readonly' in sections[s][1][i]: | |
tf.enabled = False | |
# attention: tf.name not set for date fields | |
if 'segments' in sections[s][1][i]: | |
item = cc.sections[s][1][i] # section s, 1=items, row i | |
segmented = ui.SegmentedControl() | |
segmented.name = cell.text_label.text | |
segmented.frame = tf.frame | |
segmented.width = 400 | |
segmented.x = cc.view.width - segmented.width - 8 | |
segmented.segments = item['segments'] | |
value = item.get('value', '') | |
segmented.selected_index = item['segments'].index(value) | |
cell.content_view.remove_subview(tf) | |
del cc.values[tf.name] | |
del tf | |
cell.content_view.add_subview(segmented) | |
if ui_image: | |
cc.view.height = cc.container_view.height*2/3 | |
cc.view.border_width = 2 | |
cc.view.border_color = 'blue' | |
y = cc.view.y + cc.view.height - 56 | |
w = cc.container_view.width | |
h = cc.container_view.height - y - 58 | |
x = 0 | |
image_view = ui.ImageView(name='graphic_area', frame=(x,y,w,h)) | |
image_view.background_color = 'lightgray' | |
image_view.border_width = 2 | |
image_view.border_color ='red' | |
image_view.content_mode = ui.CONTENT_SCALE_ASPECT_FIT | |
cc.container_view.add_subview(image_view) | |
if type(graphic_data) is list: | |
cc.graphic_data = graphic_data | |
graphique_button_action('no sender') | |
else: | |
image_view.image = ui.Image.from_data(graphic_data) | |
cc.poids = False | |
cc.select = False | |
cc.base = False | |
cc.calc = False | |
cc.scan = False | |
cc.container_view.present('sheet') | |
cc.container_view.wait_modal() | |
# Get rid of the view to avoid a retain cycle: | |
cc.container_view = None | |
if cc.poids: | |
return cc.poids | |
if cc.select: | |
return cc.select | |
if cc.base: | |
return 'base' | |
if cc.calc: | |
return 'calc' | |
if cc.scan: | |
return 'scan' | |
if cc.was_canceled: | |
return None | |
for s in range(0,len(cc.sections)): | |
for i in range(0,len(cc.cells[s])): # loop on rows of section 0 | |
cell = cc.cells[s][i] # ui.TableViewCell of row i | |
# some fields types are subviews of the cell: | |
# text,number,url,email,password,switch | |
# but check, date and time are not set as subviews of cell.content_view | |
for tf in cell.content_view.subviews: | |
if 'SegmentedControl' in str(type(tf)): | |
item = cc.sections[s][1][i] # section s, 1=items, row i | |
if tf.selected_index >= 0: | |
cc.values[tf.name] = item['segments'][tf.selected_index] | |
return cc.values | |
def base_button_action(sender): | |
global cc | |
cc.base = True | |
cc.container_view.close() | |
def calc_button_action(sender): | |
global cc | |
cc.calc = True | |
cc.container_view.close() | |
def scan_button_action(sender): | |
global cc | |
cc.scan = True | |
cc.container_view.close() | |
def graphique_button_action(sender): | |
global cc | |
w = cc.container_view['graphic_area'].width/(326/2) | |
h = cc.container_view['graphic_area'].height/(326/2) | |
h = 6 | |
w = h * (cc.container_view['graphic_area'].width/cc.container_view['graphic_area'].height) | |
# figsize is in inch | |
# ipad and ipad mini: | |
# pixels: 2048 x 1536 | |
# dpi: 264 or 326 (mini) | |
# fonts for texts | |
font = {'family' : 'serif','color' : 'darkred','weight' : 'normal','size' : 16,} | |
fig,ax = plt.subplots(figsize=(w,h),dpi=326) # return figure and axes | |
#plt.title('test')#, fontdict=fonttitle) | |
plt.subplots_adjust(left=0.1,right=0.9) | |
plt.xlabel('', fontdict=font) | |
plt.ylabel(cc.ylabel, fontdict=font) | |
for curve in range(len(cc.graphic_data)): | |
x = [] | |
y = [] | |
for ymd,wgt in cc.graphic_data[curve]: | |
x.append(mdates.date2num(datetime.strptime(ymd,'%Y%m%d'))) | |
y.append(wgt) | |
ax.plot_date(x,y,'-', label=cc.legends[curve],linewidth=2, color=['blue','red'][curve]) | |
if len(x) == 1: | |
plt.scatter(x[0],y[0], s=40, c=['blue','red'][curve], marker=['o','s','+'][curve]) | |
plt.legend(loc=6,fontsize=14) | |
# format the ticks | |
if len(x) == 1: | |
dt = datetime.strptime(ymd,'%Y%m%d') | |
dmy = dt.strftime('%d/%m/%Y') | |
ax.set_xticks(x) | |
ax.set_xticklabels([dmy]) | |
else: | |
formatter = mdates.DateFormatter("%d/%m/%Y") | |
ax.xaxis.set_major_formatter(formatter) | |
locator = mdates.WeekdayLocator(byweekday=mdates.MO) | |
ax.xaxis.set_major_locator(locator) | |
plt.gcf().autofmt_xdate() | |
ax.yaxis.tick_left() | |
ax2 = ax.twinx() | |
ax2.set_ylim(ax.get_ylim()) | |
ax2.yaxis.tick_right() | |
ax2.set_ylabel(ax.get_ylabel(),fontdict=font) | |
ax.grid(True) | |
b = BytesIO() | |
plt.savefig(b) | |
plt.close(fig) # free memory of figure | |
cc.container_view['graphic_area'].image = ui.Image.from_data(b.getvalue()) | |
def poids_button_action(sender=None): | |
global MainView,cc | |
if not sender: | |
# called by a row selection | |
title = "Modification ou suppression d'un poids" | |
section,row = cc.view.selected_row | |
item = cc.sections[section][1][row] | |
dmy = item['title'] #dd/mm/yyyy | |
dt = datetime.strptime(dmy, '%d/%m/%Y ') | |
key = item.get('key', item.get('title', None)) | |
wgt = cc.values[key].replace('kg','').strip() | |
wg = wgt.replace(',','.') | |
wg = float(wg) | |
else: | |
# called by add button | |
title = 'Ajoute Poids' | |
dt = datetime.now() | |
wgt = '' | |
fields = [] | |
fields.append({'title':'date', 'type':'date', 'format':'%d/%m/%Y', 'value':dt}) | |
fields.append({'title':'poids', 'type':'number', 'value':wgt}) | |
if not sender: | |
# called by a row selection | |
fields.append({'title':'suppression?', 'type':'switch'}) | |
f = dialogs.form_dialog(title=title, fields=fields, done_button_title='ok') | |
#print(f) | |
if not f: | |
# cancel button | |
return | |
if not sender: | |
# called by a row selection | |
if f['suppression?']: | |
# delete asked | |
b = console.alert('suppression', 'confirmez-vous?', 'oui', 'non', hide_cancel_button=True) | |
if b == 2: | |
console.alert('suppression annulée', '', 'ok', hide_cancel_button=True) | |
return | |
dtnew = f['date'] # datetime | |
#........try............. | |
try: | |
wgnew = f['poids'].replace(',','.') | |
wgnew = float(wgnew) | |
except: | |
console.alert('poids incorrect', '', 'ok', hide_cancel_button=True) | |
return | |
if not sender: | |
# called by a row selection | |
if f['suppression?']: | |
# delete asked | |
cc.poids = ('del',(dt,wg)) | |
else: | |
# update | |
if dtnew == dt and wgnew == wg: | |
console.alert('modification inutile', 'même date et même poids', 'ok', hide_cancel_button=True) | |
return | |
if dtnew != dt: | |
# date modifiêe | |
ymd = dtnew.strftime('%Y%m%d') | |
MainView.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_key=?", ('Poids', ymd)) | |
if MainView.sql_cursor.fetchone(): | |
# record Poids of this date already exists | |
console.alert('modification impossible', 'cette nouvelle date existe déjà', 'ok', hide_cancel_button=True) | |
return | |
cc.poids = ('upd',(dt,wg,dtnew,wgnew)) | |
else: | |
# add | |
ymd = dtnew.strftime('%Y%m%d') | |
MainView.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_key=?", ('Poids', ymd)) | |
if MainView.sql_cursor.fetchone(): | |
# record Poids of this date already exists | |
console.alert('ajoute impossible', 'cette date existe déjà', 'ok', hide_cancel_button=True) | |
return | |
cc.poids = ('add',(dtnew,wgnew)) | |
ui.delay(cc.container_view.close,0.1) | |
def web_button_action(sender): | |
global cc | |
wv = ui.WebView() | |
wv.frame = cc.container_view.frame | |
wv.load_url(cc.url) | |
wv.present('sheet') | |
wv.wait_modal() | |
class MyView(ui.View): | |
def __init__(self,w,h): | |
self.width = w | |
self.height = h | |
o = ObjCClass('UIImage').systemImageNamed_('barcode') | |
with ui.ImageContext(32,32) as ctx: | |
o.drawAtPoint_(CGPoint(0,0)) | |
UIImagePNGRepresentation = c.UIImagePNGRepresentation | |
UIImagePNGRepresentation.restype = c_void_p | |
UIImagePNGRepresentation.argtypes = [c_void_p] | |
self.UIImage_data = nsdata_to_bytes(ObjCInstance(UIImagePNGRepresentation(o))) | |
b_db = ui.ButtonItem() | |
#b_param.image = ui.Image.named('iob:ios7_gear_outline_32') | |
b_db.title = '🛠' | |
b_db.tint_color = 'black' | |
b_db.action = self.db_action | |
b_param = ui.ButtonItem() | |
#b_param.image = ui.Image.named('iob:ios7_gear_outline_32') | |
b_param.title = '⚙️' | |
b_param.tint_color = 'black' | |
b_param.action = self.param_action | |
b_user = ui.ButtonItem() | |
#b_user.image = ui.Image.named('iob:ios7_person_outline_32') | |
b_user.title ='👤' | |
b_user.tint_color = 'black' | |
b_user.action = self.user_action | |
b_poids = ui.ButtonItem() | |
b_poids.title = '⚖️' | |
b_poids.action = self.poids_action | |
b_points = ui.ButtonItem() | |
b_points.title = '📅' | |
b_points.action = self.points_action | |
b_aliments = ui.ButtonItem() | |
b_aliments.title = '🍅' | |
b_aliments.action = self.aliments_action | |
b_zeropoints = ui.ButtonItem() | |
b_zeropoints.title = '🆓' | |
b_zeropoints.action = self.zeropoints_action | |
b_recettes = ui.ButtonItem() | |
b_recettes.title = '🍽' | |
b_recettes.action = self.recettes_action | |
self.left_button_items = (b_db, b_param, b_user, b_poids, b_points) | |
self.right_button_items = (b_aliments, b_zeropoints, b_recettes) | |
# day view: begin--------------------------------- | |
self.jours = ['Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi','Dimanche'] | |
day_view = ui.View(name='day_view') | |
day_view.frame = (0,0,w,h) | |
day_view.flex = 'WH' | |
day_view.border_width = 1 | |
day_view.border_color = 'blue' | |
self.add_subview(day_view) | |
# | < | Lu 12 | ... | Di 18 | > | | |
d = 10 | |
week_prev = ui.Button(name='week_prev') | |
week_prev.frame = (d,d,32,32) | |
week_prev.image = ui.Image.named('iob:ios7_arrow_back_32') | |
week_prev.action = self.week_prev_action | |
day_view.add_subview(week_prev) | |
week_next = ui.Button(name='week_next') | |
week_next.frame = (self.width-d-32,d,32,32) | |
week_next.image = ui.Image.named('iob:ios7_arrow_forward_32') | |
week_next.action = self.week_next_action | |
day_view.add_subview(week_next) | |
x_grid = week_prev.x + week_prev.width + d | |
w_grid = week_next.x - d - x_grid | |
days_grid = ui.View(name='days_grid') | |
days_grid.frame = (x_grid,d,w_grid,32) | |
days_grid.border_width = 1 | |
days_grid.border_color = 'lightgray' | |
day_view.add_subview(days_grid) | |
w_day = int(w_grid/7) | |
x = 0 | |
for iday in range(7): | |
if iday == 6: | |
w_day = days_grid.width - x | |
b = ui.Button(name='day'+str(iday)) | |
b.font = ('Menlo',10) | |
if iday == 3: | |
b.tint_color = 'green' | |
else: | |
b.tint_color = 'lightgray' | |
b.frame = (x,0,w_day,32) | |
b.border_width = 1 | |
b.border_color = 'lightgray' | |
b.action = self.day_button_action | |
days_grid.add_subview(b) | |
lp = ui.Label(name='label_pts') | |
lp.frame = (0,22,w_day,10) | |
lp.font = ('Menlo',8) | |
lp.alignment = ui.ALIGN_CENTER | |
b.add_subview(lp) | |
x = x + w_day - 1 | |
y = days_grid.y+days_grid.height+2 | |
dx = 2 | |
nz = 6 # number columns | |
w = int((self.width-2*dx)/6) # width each column | |
d = 5 | |
hc = 32 | |
x = dx | |
for iz in range(0,nz): | |
if iz == (nz-1): | |
w = self.width - x - dx # align right last column | |
if iz == 0 or iz == 3: | |
v1 = ui.View() | |
if iz == 3: | |
ww = self.width - x - dx # align right last column | |
else: | |
ww = 3 * w - 2 | |
v1.frame = (x,y,ww,hc) | |
v1.border_width = 1 | |
v1.border_color = 'lightgray' | |
day_view.add_subview(v1) | |
l1 = ui.Label() | |
l1.frame = (d,d,ww-2*d,v1.height-2*d) | |
l1.text = ['jour','','','réserve hebdomadaire','',''][iz] | |
l1.alignment = ui.ALIGN_CENTER | |
v1.add_subview(l1) | |
v2 = ui.View() | |
v2.frame = (x,v1.y+v1.height-1,w,hc) | |
v2.border_width = 1 | |
v2.border_color = 'lightgray' | |
day_view.add_subview(v2) | |
l2 = ui.Label() | |
l2.frame = (d,d,w-2*d,v1.height-2*d) | |
l2.text = ['capital','consommés','restant','matin','utilisés','pour demain'][iz] | |
l2.alignment = ui.ALIGN_CENTER | |
v2.add_subview(l2) | |
v3 = ui.View(name=l2.text) | |
v3.frame = (x,v2.y+v2.height-1,w,hc) | |
v3.border_width = 1 | |
v3.border_color = 'lightgray' | |
day_view.add_subview(v3) | |
l3 = ui.Label(name='points') | |
l3.alignment= ui.ALIGN_RIGHT | |
l3.frame = (d,d,w-2*d,v1.height-2*d) | |
l3.text = ['1','2','3','4','5','6'][iz] | |
v3.add_subview(l3) | |
x = x + w - 1 | |
pts_hebdo = ui.Label(name='pts_hebdo') | |
pts_hebdo.frame = (10,y,w,32) | |
day_view.add_subview(pts_hebdo) | |
pts_jour = ui.Label(name='pts_jour') | |
pts_jour.frame = (10+w+10,y,w,32) | |
day_view.add_subview(pts_jour) | |
pts_rest = ui.Label(name='pts_rest') | |
pts_rest.frame = (10+w+10+w+10,y,w,32) | |
day_view.add_subview(pts_rest) | |
y = v3.y + v3.height + 2 | |
day_meals = ui.TableView(name='day_meals') | |
day_meals.row_height = 45 | |
tblo = ObjCInstance(day_meals) | |
tblo.sectionHeaderHeight = 0 | |
tblo.sectionFooterHeight = 0 | |
day_meals.frame = (1,y,self.width-2,self.height-y-70) | |
day_meals.border_width = 4 | |
day_meals.border_color = 'blue' | |
day_meals.data_source = self | |
day_meals.delegate = self | |
day_view.add_subview(day_meals) | |
# day view: end----------------------------------- | |
# db | |
x = sys.argv[0] # Script path and name | |
i = x.rfind('/') # index last / | |
db_path = x[:i+1] # expanded path of script | |
os.chdir(db_path) | |
self.full_path = db_path+'WeightWatchers.db' | |
# TABLE Calls fields (call,bands,date,origin,adr,gps) | |
self.sql_connect = sqlite3.connect(self.full_path, check_same_thread=False) | |
self.sql_cursor = self.sql_connect.cursor() | |
# Create db | |
self.CreateDB() | |
# add initial data | |
file = 'WeightWatchers.txt' | |
with open(file,mode='rt',encoding='utf-8') as fil: | |
records = fil.read().split('\n')[:-1] | |
recout = [] | |
for record in records: | |
if record.startswith('#'): | |
continue | |
dbrec = record.split('|') | |
recout.append((dbrec[0],dbrec[1],dbrec[2],dbrec[3])) | |
#print(recout) | |
self.sql_cursor.executemany("INSERT INTO WeightWatchers VALUES (?,?,?,?)",recout) | |
self.sql_connect.commit() | |
#self.GetDB() | |
# Get user infos | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=?", ('User',)) | |
record = self.sql_cursor.fetchone() | |
rec_type,rec_key,rec_subkey,rec_data = record | |
self.user = rec_key | |
self.user_infos = ast.literal_eval(rec_data) | |
self.meals = ['déjeuner','dîner','souper','collation'] | |
#self.meals = ['matin','midi','soir','collation'] | |
self.units = ['gr','cl','pièce'] | |
self.current_meal = 0 | |
self.current_day = datetime.now().date() | |
self.set_day() | |
def day_button_action(self,sender): | |
self.current_day = sender.date | |
self.set_day() | |
def week_prev_action(self,sender): | |
self.current_monday += timedelta(days=-7) | |
self.set_week() | |
def week_next_action(self,sender): | |
self.current_monday += timedelta(days=+7) | |
self.set_week() | |
def set_day(self,ymd=None): | |
if ymd: | |
self.current_day = datetime.strptime(ymd, '%Y%m%d') | |
week_day = self.current_day.weekday() | |
# check if Points record exists | |
ymd = self.current_day.strftime('%Y%m%d') | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_key=?", ('Points',ymd)) | |
record = self.sql_cursor.fetchone() | |
if not record: | |
# Points record does not exist, create it | |
''' | |
record creation | |
si lundi | |
reserve hebdo matin = user info | |
else | |
si jour precedent n'existe pas | |
reserve hebdo matin = user info | |
else | |
reserve hebdo matin = celle du jour precedent | |
si points consommes le jour precedent = user info | |
pass | |
si points consommes le jour precedent < user info | |
reserve hebdo matin += max(4,user info - points veille) | |
si points consommes le jour precedent > user info | |
reserve hebdo matin -= max(reserve hebdo,points veille-user) | |
''' | |
if week_day == 0: | |
# monday | |
res_hebdo_matin = self.user_infos['réserve hebdo'] | |
else: | |
# not a monday | |
ymd_prev = (self.current_day+timedelta(days=-1)).strftime('%Y%m%d') | |
# try to read the record of previous day | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_key=?", ('Points',ymd_prev)) | |
record = self.sql_cursor.fetchone() | |
if not record: | |
# record of previous day does not exist | |
res_hebdo_matin = self.user_infos['réserve hebdo'] | |
else: | |
# record of previous day exists | |
rec_type,rec_key,rec_subkey,rec_data = record | |
res_hebdo_matin = int(rec_subkey) # this one of previous day | |
# compute number of points of this previous day | |
rec_data = ast.literal_eval(rec_data) | |
np = 0 | |
for aliment in rec_data: | |
np += aliment['points'] | |
if np == self.user_infos['points par jour']: | |
pass | |
elif np < self.user_infos['points par jour']: | |
res_hebdo_matin += min(4,self.user_infos['points par jour']-np) | |
elif np > self.user_infos['points par jour']: | |
res_hebdo_matin -= max(res_hebdo_matin,np-self.user_infos['points par jour']) | |
# create new record | |
self.sql_cursor.execute("INSERT INTO WeightWatchers VALUES (?,?,?,?)",('Points', ymd, res_hebdo_matin, str([]))) | |
self.sql_connect.commit() | |
self.current_monday = self.current_day + timedelta(days=-week_day) | |
self.set_week() | |
def set_week(self): | |
current_visible = False | |
for iday in range(7): | |
dt = self.current_monday + timedelta(days=iday) | |
ymd = dt.strftime('%Y%m%d') | |
dmy = dt.strftime('%d/%m/%Y') | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_key=?", ('Points',ymd)) | |
record = self.sql_cursor.fetchone() | |
bn = 'day' + str(iday) | |
self['day_view']['days_grid'][bn].date = dt | |
self['day_view']['days_grid'][bn].title = f"{self.jours[iday][:2]} {dmy}" | |
if dt == self.current_day: | |
# show current day in week | |
cbg = 'lightgray' | |
cti = 'blue' | |
self.current_record = record | |
current_visible = True | |
else: | |
# other days of week | |
cbg = 'white' | |
# check if db record exists | |
if record: | |
# db record exists | |
oth_type,oth_key,oth_subkey,oth_data = record | |
oth_data = ast.literal_eval(oth_data) | |
np = 0 | |
for aliment in oth_data: | |
np += aliment['points'] | |
if np <= self.user_infos['points par jour']: | |
cti = 'green' | |
else: | |
cti = 'red' | |
self['day_view']['days_grid'][bn]['label_pts'].text = f"{np:2d} points" | |
else: | |
# db record does not exist | |
cti = 'gray' | |
self['day_view']['days_grid'][bn]['label_pts'].text = '' | |
# show if day record exists and if points are <= or > maximum of day | |
self['day_view']['days_grid'][bn].background_color = cbg | |
self['day_view']['days_grid'][bn].tint_color = cti | |
for sv in self['day_view'].subviews: | |
if sv.name not in ['days_grid','week_prev','week_next']: | |
sv.hidden = not current_visible | |
if not current_visible: | |
return | |
self.set_rows() | |
def set_rows(self): | |
rec_type,rec_key,rec_subkey,rec_data = self.current_record | |
# ['Points','yyyymmdd',reserve hebdo,[aliment,...]] | |
# aliment = {'repas':1, aliment':nom, 'points':3, 'quantité':25,'points base':12, 'base':(100,'gr')} | |
res_hebdo_matin = int(rec_subkey) | |
rec_data = ast.literal_eval(rec_data) | |
#print(rec_data) | |
pts_jour = 0 | |
self.rows = [] | |
for repas in range(1,5): # loop on meals | |
self.rows.append({'header':self.meals[repas-1]}) | |
idx = len(self.rows)-1 | |
np = 0 | |
for aliment in rec_data: | |
if aliment['repas'] == repas: | |
np += aliment['points'] | |
self.rows.append(aliment) | |
pts_jour += np | |
self.rows[idx]['points']=np | |
pts_rest = max(0, self.user_infos['points par jour']-pts_jour) | |
# ['capital','consommés','restant','matin','utilisés','pour demain'] | |
self['day_view']['capital']['points'].text = str(self.user_infos['points par jour']) | |
self['day_view']['consommés']['points'].text = str(pts_jour) | |
if pts_jour > self.user_infos['points par jour']: | |
self['day_view']['consommés']['points'].text_color = 'red' | |
else: | |
self['day_view']['consommés']['points'].text_color = 'green' | |
self['day_view']['restant']['points'].text = str(max(self.user_infos['points par jour']-pts_jour,0)) | |
self['day_view']['matin']['points'].text = str(res_hebdo_matin) | |
self['day_view']['utilisés']['points'].text = str(min(res_hebdo_matin,max(0,pts_jour-self.user_infos['points par jour']))) | |
if self.current_day.weekday() == 6: # Sunday | |
self['day_view']['pour demain']['points'].text = '0' | |
else: | |
self['day_view']['pour demain']['points'].text = str(min(self.user_infos['jour vers réserve'],int(self['day_view']['restant']['points'].text))) | |
self['day_view']['day_meals'].reload_data() | |
def tableview_number_of_sections(self, tableview): | |
return 1 | |
def tableview_title_for_header(self, tableview, section): | |
return None | |
def tableview_title_for_footer(self, tableview, section): | |
return None | |
def tableview_number_of_rows(self, tableview, section): | |
return len(self.rows) | |
def tableview_cell_for_row(self, tableview, section, row): | |
cell = ui.TableViewCell() | |
cell.content_view.border_width = 1 | |
cell.content_view.border_color = 'lightgray' | |
h = tableview.row_height | |
aliment = self.rows[row] | |
if 'header' in aliment: | |
# row is name of meal | |
cell.selectable = False | |
repas,np = self.rows[row] | |
cell.bg_color = 'lightgray' | |
t1 = aliment['header'] | |
t2 = f"{aliment['points']:2d} points" | |
h1 = h | |
else: | |
# row is an aliment | |
cell.bg_color = 'white' | |
# 'repas':1, 'aliment':'viande', 'points':3, 'quantité':25,'points base':12, 'base':(100,'gr') | |
# or if zeropoint food | |
# 'repas':3, 'aliment':'Haricots secs', 'points':0, 'zeropoint':True | |
t1 = aliment['aliment'] | |
if 'zeropoint' in aliment: | |
t2 = f"{aliment['points']:2d} points pour zeropoint" | |
else: | |
t2 = f"{aliment['points']:2d} points pour {aliment['quantité']:3d} {aliment['base'][1]}" | |
if 'recette' not in aliment: | |
h1 = h | |
else: | |
h1 = h*2/3 | |
# title | |
l1 = ui.Label() | |
l1.text_color = 'black' | |
l1.font = ('Menlo',16) | |
l1.text = t1 | |
w = (tableview.width - 3*5)*3/4 | |
l1.frame = (5,0,w,h1) | |
cell.content_view.add_subview(l1) | |
# right subtitle | |
l2 = ui.Label() | |
l2.text_color = 'blue' | |
l2.font = ('Menlo',12) | |
l2.text = t2 | |
x = l1.x + l1.width + 5 | |
w = tableview.width - x - 5 | |
l2.frame = (x,0,w,h) | |
cell.content_view.add_subview(l2) | |
if 'recette' in aliment: | |
# under subtitle | |
l3 = ui.Label() | |
l3.text_color = 'red' | |
l3.font = ('Menlo',10) | |
l3.text = 'ingrédient de la recette "' + aliment['recette'] + '"' | |
x = l1.x | |
w = l1.width | |
l3.frame = (x,l1.y+l1.height,w,h-2-h1) | |
cell.content_view.add_subview(l3) | |
selected_cell = ui.View() | |
selected_cell.bg_color = cell.bg_color | |
cell.selected_background_view = selected_cell | |
return cell | |
def tableview_did_select(self, tableview, section, row): | |
# a meal row should not be selectable...but tests show it is | |
#print(row) | |
aliment = self.rows[row] | |
if 'header' in aliment: | |
# row is name of meal | |
pass # no action | |
else: | |
# row is an aliment | |
product = aliment['aliment'] | |
points = aliment['points'] | |
quantity = aliment.get('quantité', None) | |
points_base = aliment.get('points base', None) | |
base = aliment.get('base', None) | |
self.current_meal = aliment['repas']-1 | |
title = 'aliment du ' + self.meals[self.current_meal] + ' du ' + self.current_day.strftime('%d/%m/%Y') | |
self.use_food_from = 'day' | |
ui.delay(partial(self.use_food,product,points,quantity,points_base, base, title=title), 0.1) | |
def CreateDB(self): | |
self.sql_connect.execute("DROP TABLE IF EXISTS WeightWatchers") | |
self.sql_cursor.execute("CREATE TABLE WeightWatchers (rec_type,rec_key,rec_subkey,rec_data)") | |
# zero points | |
file = 'WeightWatchers zero points.txt' | |
with open(file,mode='rt',encoding='utf-8') as fil: | |
zeropoints = fil.read().split('\n')[:-1] | |
section = '' | |
for aliment in zeropoints: | |
if aliment.startswith('@'): | |
groupe = aliment[1:] | |
else: | |
product = aliment | |
barcode = 'zeropoint' | |
points_base = 0 | |
self.add_food(product, barcode, points_base, groupe=groupe) | |
def GetDB(self): | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers") | |
self.all_records = self.sql_cursor.fetchall() | |
for record in self.all_records: | |
print(record) | |
def db_action(self,sender): | |
rec_types = {} | |
sections = [] | |
# get all db records | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers") | |
records = self.sql_cursor.fetchall() | |
for record in records: | |
rec_type,rec_key,rec_subkey,rec_data = record | |
if rec_type not in rec_types: | |
rec_types[rec_type] = [] | |
rec_types[rec_type].append({'title':rec_key}) | |
for rec_type in rec_types: | |
fields = rec_types[rec_type] | |
fields = sorted(fields, key=lambda k:k['title']) | |
sections.append((rec_type,fields)) | |
del rec_types | |
# sort on rec_type | |
sort_order = ['User','Poids','Points','Recette','Aliment'] | |
sections = sorted(sections, key=lambda k: sort_order.index(k[0])) | |
f = my_form_dialog(title='Liste des records de la db', sections=sections, dims=(self.width-60,self.height-10), search=True) | |
#print(f) | |
if not f: | |
# cancel button | |
return | |
section,row = f | |
rec_type = sections[section][0] | |
rec_key = sections[section][1][row]['title'] | |
# Get record | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_key=?", (rec_type,rec_key)) | |
record = self.sql_cursor.fetchone() | |
rec_type,rec_key,rec_subkey,rec_data = record | |
#rec_data = ast.literal_eval(rec_data) | |
tv = ui.TextView() | |
tv.frame = (0,0,500,500) | |
tv.font =('Menlo',15) | |
tv.text = rec_data | |
tv.name = rec_type + ':' + rec_key | |
tv.present('sheet') | |
tv.wait_modal() | |
def param_action(self,sender): | |
pass | |
def user_action(self,sender): | |
fields = [] | |
fields.append({'title':'nom', 'type':'text', 'value':self.user}) | |
fields.append({'title':'sexe', 'type':'text','value':self.user_infos['sexe'], 'segments':['femme','homme']}) | |
t =str(self.user_infos['âge']) | |
fields.append({'title':'âge', 'type':'number', 'value':t}) | |
t =str(self.user_infos['poids']).replace('.',',') | |
fields.append({'title':'poids (kg)', 'type':'number', 'value':t}) | |
t =str(self.user_infos['taille']).replace('.',',') | |
fields.append({'title':'taille (m)', 'type':'number', 'value':t}) | |
fields.append({'title':'plan', 'type':'text', 'value':self.user_infos['plan'], 'segments':['vert','bleu','violet']}) | |
t =str(self.user_infos['points par jour']) | |
fields.append({'title':'points par jour', 'type':'number', 'value':t}) | |
t =str(self.user_infos['réserve hebdo']) | |
fields.append({'title':'réserve hebdo', 'type':'number', 'value':t}) | |
t =str(self.user_infos['jour vers réserve']) | |
fields.append({'title':'transfert max sur réserve', 'type':'number', 'value':t}) | |
updated_fields = my_form_dialog(title='Utilisateur', fields=fields, editable=False) | |
#print(updated_fields) | |
def poids_action(self,sender): | |
while True: | |
fields = [] | |
# get poids db records | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=?", ('Poids',)) | |
records = self.sql_cursor.fetchall() | |
for record in records: | |
# 'Poids','yyyymmdd',poids,[] | |
rec_type,rec_key,rec_subkey,rec_data = record | |
ymd = rec_key | |
try: | |
wgt = float(rec_subkey) | |
recok = True | |
except: | |
recok = False | |
if not recok or type(wgt) is str: | |
# remove bad record | |
print('remove bad record ',rec_type,rec_key,rec_subkey) | |
#self.sql_cursor.execute("DELETE FROM WeightWatchers WHERE rec_type=? and rec_key=?",('Poids',rec_key)) | |
#self.sql_connect.commit() | |
continue | |
val = '{:.2f}'.format(wgt).replace('.',',')+' kg' | |
dt = datetime.strptime(ymd, '%Y%m%d') | |
tt = dt.strftime('%d/%m/%Y ') | |
fields.append({'ymd':ymd, 'wgt':wgt, 'title':tt, 'type':'number', 'value':val}) | |
if fields == []: | |
console.alert('aucun poids introduit', '', 'ok', hide_cancel_button=True) | |
return | |
# sort dates | |
fields = sorted(fields, key=lambda k: k['ymd']) | |
# build graphic data | |
graphic_data = [[]] | |
for field in fields: | |
graphic_data[0].append((field['ymd'], field['wgt'])) | |
f = my_form_dialog(title='évolution du poids', fields=fields, graphique_button=False, graphic_data=graphic_data, poids_buttons=True, ui_image=True, dims=(self.width-60,self.height-10), editable=False, legends=['poids'], ylabel='Kg') | |
#print(f) | |
if not f: | |
# cancel button | |
break # leave loop | |
# returned is ('add',(datetime,weight)) | |
if f[0] == 'add': | |
dt,wg = f[1] | |
ymd = dt.strftime('%Y%m%d') | |
# add poids record in db | |
self.sql_cursor.execute("INSERT INTO WeightWatchers VALUES (?,?,?,?)",('Poids',ymd,str(wg),str([]))) | |
self.sql_connect.commit() | |
elif f[0] == 'del': | |
dt,wg = f[1] | |
ymd = dt.strftime('%Y%m%d') | |
# del poids record in db | |
self.sql_cursor.execute("DELETE FROM WeightWatchers WHERE rec_type=? and rec_key=?",('Poids',ymd)) | |
self.sql_connect.commit() | |
elif f[0] == 'upd': | |
dt,wg,dtnew,wgnew = f[1] | |
ymd = dt.strftime('%Y%m%d') | |
ymdnew = dtnew.strftime('%Y%m%d') | |
# upd poids record in db | |
self.sql_cursor.execute("UPDATE WeightWatchers SET rec_key=? , rec_subkey=? WHERE rec_type=? and rec_key=?",(ymdnew, str(wgnew), 'Poids', ymd)) | |
self.sql_connect.commit() | |
def points_action(self,sender): | |
fields = [] | |
# get points db records | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=?", ('Points',)) | |
records = self.sql_cursor.fetchall() | |
for record in records: | |
# ['Points','yyyymmdd',reserve hebdo,[aliment,...]] | |
# aliment = {'repas':1, aliment':nom, 'points':3, 'quantité':25,'points base':12, 'base':(100,'gr')} | |
rec_type,rec_key,rec_subkey,rec_data = record | |
ymd = rec_key | |
rec_data = ast.literal_eval(rec_data) | |
pts = 0 | |
for aliment in rec_data: | |
pts += aliment['points'] | |
val = '{:2d}'.format(pts) | |
dt = datetime.strptime(ymd, '%Y%m%d') | |
tt = dt.strftime('%d/%m/%Y ') | |
fields.append({'ymd':ymd, 'pts':pts, 'title':tt, 'type':'text', 'value':val}) | |
if fields == []: | |
console.alert('aucun jour déjà introduit', '', 'ok', hide_cancel_button=True) | |
return | |
# sort dates | |
fields = sorted(fields, key=lambda k: k['ymd']) | |
# build graphic data | |
graphic_data = [[],[]] | |
for field in fields: | |
graphic_data[0].append((field['ymd'], field['pts'])) | |
graphic_data[1].append((field['ymd'], int(self.user_infos['points par jour']))) | |
f = my_form_dialog(title='Calendrier des jours WeightWatchers', fields=fields, graphique_button=False, graphic_data=graphic_data, ui_image=True, dims=(self.width-60,self.height-10), editable=False, legends=['points du jour', 'points maximum'], ylabel='points') | |
#print(f) | |
if not f: | |
# cancel button | |
return | |
section,row = f | |
self.set_day(ymd=fields[row]['ymd']) | |
def aliments_action(self,sender): | |
sections = [] | |
fields = [] | |
# get aliments db records | |
cursors = self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_subkey!=?", ('Aliment','zeropoint')) | |
wmax = 0 | |
for record in cursors: | |
# 'Aliment', nom, barcode, {'points':12, 'base':(100,'gr'), kcal':359,'saturated_fat':0.5,'sugars':1.5,'proteins':12.5, 'thumb':None/data, 'portion':('pièce',7,'gr'), 'groupe':'féculents'} | |
rec_type,rec_key,rec_subkey,rec_data = record | |
#print(rec_data) | |
rec_data = ast.literal_eval(rec_data) | |
subtitle = '' | |
if 'points base' in rec_data: | |
subtitle += f"{rec_data['points base']:2d} points" | |
if 'base' in rec_data: | |
subtitle += f" / {rec_data['base'][0]:3d} {rec_data['base'][1]}" | |
thumb_data = rec_data.get('thumb',None) | |
if thumb_data: | |
icon = ui.Image.from_data(thumb_data) | |
wmax = max(wmax,icon.size[0]) # maximum thumbs width | |
else: | |
icon = None | |
fields.append({'title':rec_key, 'subtitle':subtitle, 'icon':icon}) | |
for field in fields: | |
if field['icon']: | |
with ui.ImageContext(wmax,128) as ctx: | |
img = field['icon'] | |
w = img.size[0] | |
img.draw((wmax-w)/2,0,w,128) | |
field['icon'] = ctx.get_image() | |
if fields == []: | |
console.alert('aucun aliment', '', 'ok', hide_cancel_button=True) | |
return | |
# sort aliments | |
fields = sorted(fields, key=lambda k: k['title'].lower()) | |
sections.append(('Liste',fields)) | |
f = my_form_dialog(title='Liste des aliments', sections=sections, base_button=True, calc_button=True, scan_button=True, dims=(self.width-60,self.height-10), search=True, row_height=32) | |
print('return from liste des aliments',f) | |
if not f: | |
# cancel button | |
return | |
if f == 'base': | |
# base button | |
ui.delay(self.base_action, 0.1) | |
return | |
if f == 'calc': | |
# ingredients button | |
ui.delay(self.ingredients_action, 0.1) | |
return | |
if f == 'scan': | |
# ingredients button | |
ui.delay(self.scan_action, 0.1) | |
return | |
section,row = f | |
product = sections[section][1][row]['title'] | |
points = rec_data['points base'] | |
quantity = rec_data['base'][0] | |
points_base = rec_data['points base'] | |
base = rec_data['base'] | |
title = "ajoute aliment au " + self.meals[self.current_meal] + ' du ' + self.current_day.strftime('%d/%m/%Y') | |
self.use_food_from = 'aliment' | |
ui.delay(partial(self.use_food,product,points,quantity,points_base, base, title=title), 0.1) | |
def recettes_action(self,sender): | |
sections = [] | |
fields = [] | |
# get recettes db records | |
cursors = self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=?", ('Recette',)) | |
for record in cursors: | |
# record: ('Recette', nom, '', data) | |
rec_type,rec_key,rec_subkey,rec_data = record | |
#rec_data = ast.literal_eval(rec_data) | |
fields.append({'title':rec_key}) | |
if fields == []: | |
console.alert('aucune recette', '', 'ok', hide_cancel_button=True) | |
return | |
# sort aliments | |
fields = sorted(fields, key=lambda k: k['title'].lower()) | |
sections.append(('Liste',fields)) | |
f = my_form_dialog(title='Liste des recettes', sections=sections, dims=(self.width-60,self.height-10), search=True) | |
#print(f) | |
if not f: | |
# cancel button | |
return | |
section,row = f | |
recette = sections[section][1][row]['title'] | |
# Get recette infos | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_key=?", ('Recette',recette)) | |
record = self.sql_cursor.fetchone() | |
rec_type,rec_key,rec_subkey,rec_data = record | |
rec_data = ast.literal_eval(rec_data) | |
#Recette|nom||[{'aliment':'emmenthal','quantité':20, 'points':5, 'points base':3, 'base':(100,'gr')}, {'aliment':'haché','quantité':100, 'points':8, 'points base':12, 'base':(100,'gr')}] | |
product = [] | |
quantity = [] | |
points = [] | |
points_base = [] | |
base = [] | |
for aliment in rec_data: | |
print(aliment) | |
product.append(aliment['aliment']) | |
points.append(aliment['points']) | |
quantity.append(aliment['quantité']) | |
points_base.append(aliment['points base']) | |
base.append(aliment['base']) | |
title = "Ajoute recette au " + self.meals[self.current_meal] + ' du ' + self.current_day.strftime('%d/%m/%Y') | |
self.use_food_from = 'aliment' | |
ui.delay(partial(self.use_food,product,points,quantity,points_base, base, title=title, recette=recette), 0.1) | |
def zeropoints_action(self,sender): | |
zeropoints = {} | |
# get aliments db records | |
cursors = self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_subkey=?", ('Aliment','zeropoint')) | |
for record in cursors: | |
# record: ('Aliment', product, 'zeropoint',[...,...,...,groupe] | |
rec_type,rec_key,rec_subkey,rec_data = record | |
product = rec_key | |
rec_data = ast.literal_eval(rec_data) | |
groupe = rec_data['groupe'] | |
if groupe not in zeropoints: | |
zeropoints[groupe] = [] | |
zeropoints[groupe].append(product) | |
sections = [] | |
for groupe in zeropoints: | |
fields = [] | |
for aliment in zeropoints[groupe]: | |
fields.append({'title':aliment}) | |
sections.append((groupe,fields)) | |
f = my_form_dialog(title='Liste des aliments à zero point', sections=sections, dims=(self.width-60,self.height-10),search=True) | |
if not f: | |
# cancel button | |
return | |
section,row = f | |
product = sections[section][1][row]['title'] | |
points = 0 | |
quantity = None | |
points_base = 0 | |
base = None | |
title = "ajoute aliment 'zero point' au " + self.meals[self.current_meal] + ' du ' + self.current_day.strftime('%d/%m/%Y') | |
self.use_food_from = 'zeropoints' | |
ui.delay(partial(self.use_food,product,points,quantity,points_base, base, title=title), 0.1) | |
def base_action(self): | |
# dialog with aliment base infos | |
sections = [] | |
fields = [] | |
fields.append({'title':'Nom', 'type':'text'}) | |
sections.append(('Nom',fields)) | |
fields = [] | |
fields.append({'title':'Quantité', 'type':'number'}) | |
fields.append({'title':'Unité', 'type':'text', 'value':'gr', 'segments':self.units}) | |
sections.append(('Pour',fields)) | |
fields = [] | |
fields.append({'title':'Points', 'type':'number'}) | |
sections.append(('Points',fields)) | |
f = my_form_dialog(title='Introduction de nouvel aliment sans ses ingrédients', sections=sections, dims=(self.width-60,self.height-10), editable=True) | |
#print(f) | |
if not f: | |
# cancel button | |
return | |
product = f['Nom'] | |
barcode = '' | |
base = (int(f['Quantité']), f['Unité']) | |
points_base = int(f['Points']) | |
# add aliment to db | |
self.add_food(product, barcode, points_base, base=base) | |
# display immediately usage screen | |
#product known | |
points = points_base | |
quantity = base[0] | |
#points_base known | |
#base known | |
title = "ajoute aliment au " + self.meals[self.current_meal] + ' du ' + self.current_day.strftime('%d/%m/%Y') | |
self.use_food_from = 'aliment' | |
ui.delay(partial(self.use_food,product,points,quantity,points_base, base, title=title), 0.1) | |
def ingredients_action(self): | |
# dialog with nutriments/image/smartpoints | |
sections = [] | |
fields = [] | |
fields.append({'title':'Nom', 'type':'text'}) | |
sections.append(('Nom',fields)) | |
fields = [] | |
fields.append({'title':'Kcal', 'type':'number'}) | |
fields.append({'title':'Graisses saturées', 'type':'text'}) | |
fields.append({'title':'Sucre', 'type':'number'}) | |
fields.append({'title':'Protéines', 'type':'number'}) | |
sections.append(('Ingrédients',fields)) | |
fields = [] | |
fields.append({'title':'Quantité', 'type':'number'}) | |
fields.append({'title':'Unité', 'type':'text', 'value':'gr', 'segments':self.units}) | |
sections.append(('Pour',fields)) | |
fields = [] | |
fields.append({'title':'Points', 'type':'number'}) | |
sections.append(('Points',fields)) | |
f = my_form_dialog(title='Introduction de nouvel aliment par ses ingrédients', sections=sections, dims=(self.width-60,self.height-10), editable=True) | |
print(f) | |
if not f: | |
# cancel button | |
return | |
product = f['Nom'] | |
barcode = '' | |
kcal = int(f['Kcal']) | |
saturated_fat = int(f['Graisses saturées']) | |
sugars = int(f['Sucre']) | |
proteins = int(f['Protéines']) | |
base = (int(f['Quantité']), f['Unité']) | |
points_base = int(f['Points']) | |
# add aliment to db | |
self.add_food(product, barcode, points_base, base=base, kcal=kcal, saturated_fat=saturated_fat, sugars=sugars, proteins=proteins) | |
# display immediately usage screen | |
#product known | |
points = points_base | |
quantity = base[0] | |
#points_base known | |
#base known | |
title = "ajoute aliment au " + self.meals[self.current_meal] + ' du ' + self.current_day.strftime('%d/%m/%Y') | |
self.use_food_from = 'aliment' | |
ui.delay(partial(self.use_food,product,points,quantity,points_base, base, title=title), 0.1) | |
def scan_action(self): | |
#self.barcode = '8076809545846' # pâtes | |
#self.barcode = '5400141194678' # test | |
#self.barcode = '3255537505079' # rhum | |
#self.barcode = '5449000267412' # coca-cola | |
#self.barcode = '0695342612976' # speculoos | |
self.barcode = '3564700340837' # fromage fondu | |
#self.barcode = '5410908000128' # chimay dorée | |
#self.barcode = '5410228258698' # leffe blonde | |
# scan barcode | |
#self.scan_barcode() | |
#print(self.barcode) | |
# check if barcode not yet in db | |
self.exists = False | |
get_web = True | |
cursors = self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_subkey=?", ('Aliment',self.barcode)) | |
for record in cursors: | |
# record exists | |
self.exists = True | |
b = console.alert('aliment déjà connu', "voulez-vous vérifier si des modifications ont eu lieu", 'oui', 'non', hide_cancel_button=True) | |
if b == 2: | |
get_web = False | |
# record: ('Aliment', product, data) | |
rec_type,rec_key,rec_subkey,rec_data = record | |
# barcode: rec_subkey | |
# data: str([kcal, saturated_fat, sugars, proteins, points, portion, sp_portion, thumb_data]) | |
product = rec_key | |
kcal, saturated_fat, sugars, proteins, points, portion, sp_portion, thumb_data = ast.literal_eval(rec_data) | |
image = thumb_data != None | |
graphic_data = thumb_data | |
url = 'https://fr.openfoodfacts.org/produit/' + self.barcode | |
if get_web: | |
# get barcode product name | |
url = 'https://fr.openfoodfacts.org/produit/' + self.barcode | |
response = requests.get(url) | |
ct = str(response.content.decode('utf-8')) | |
if "Il n'y a pas de produit référencé pour le code barre" in ct: | |
# barcode not in fr.openfoodfacts.org | |
console.alert('barcode inconnu', '', 'ok', hide_cancel_button=True) | |
return | |
#print(c) | |
i = ct.find('<title>') | |
j = ct.find('</title>', i) | |
product = ct[i+7:j] | |
# Taille d'une portion | |
t = "Taille d'une portion :</span>" | |
i = ct.find(t) | |
if i >= 0: | |
j = ct.find('</p>',i) | |
portion = ct[i+len(t):j].strip() # 20 gr | |
else: | |
portion = None | |
# get product detail | |
url_det = 'https://world.openfoodfacts.org/api/v0/product/' + self.barcode + '.json' | |
response = requests.get(url_det) | |
ct = str(response.content.decode('utf-8')) | |
ct = json.loads(ct) | |
#print(c) | |
prod = ct['product'] | |
ingr = {'proteins_100g':'?', 'saturated-fat_100g':'?', 'sugars_100g':'?', 'energy-kcal_100g':'?'} | |
if 'nutriments' not in prod: | |
pass | |
else: | |
nutr = prod['nutriments'] | |
for k in nutr.keys(): | |
#print(k,nutr[k]) | |
used = False | |
for x in ingr: | |
if x in k and 'serving' not in k and '_value' not in k and '_unit' not in k: | |
used = True | |
break | |
if not used: | |
continue | |
#print(k,nutr[k]) | |
ingr[k] = nutr[k] | |
if 'image_url' in prod: | |
url_img = prod['image_url'] | |
image = True | |
response = requests.get(url_img) | |
graphic_data=response.content | |
# create a thumb | |
ui_image = ui.Image.from_data(graphic_data) | |
pil_image = ui2pil(ui_image) | |
wi,hi = pil_image.size | |
h = 128 | |
w = int(h*wi/hi) | |
pil_image_icon = pil_image.resize((w,h)) | |
with io.BytesIO() as bIO: | |
pil_image_icon.save(bIO, 'PNG') | |
thumb_data = bIO.getvalue() | |
else: | |
url_img = None | |
image = False | |
graphic_data = None | |
thumb_data = None | |
# energy may be defined in kcal or kj | |
if 'energy-kcal_100g' in nutr: | |
kcal = nutr['energy-kcal_100g'] | |
elif 'energy-kj_100g' in nutr: | |
kcal = int(nutr['energy-kj_100g'] * 0.239) | |
elif 'alcohol_100g' in nutr: | |
kcal = int(nutr['alcohol_100g'] * 7) # %vol 1gr=7 kcal | |
else: | |
kcal = 0 | |
saturated_fat = nutr.get('saturated-fat_100g',0) | |
sugars = nutr.get('sugars_100g',0) | |
proteins = nutr.get('proteins_100g',0) | |
# compute smartpoints | |
points = smartpoints(kcal, saturated_fat, sugars, proteins) | |
# dialog with nutriments/image/smartpoints | |
sections = [] | |
fields = [] | |
t = "{:10d} kcal".format(kcal) | |
fields.append({'title':'Kcal ', 'type':'text', 'value':t}) | |
t = "{:10.2f} g".format(saturated_fat) | |
fields.append({'title':'Graisses saturées', 'type':'text', 'value':t}) | |
t = "{:10.2f} g".format(sugars) | |
fields.append({'title':'Sucre ', 'type':'text', 'value':t}) | |
t = "{:10.2f} g".format(proteins) | |
fields.append({'title':'Protéines ', 'type':'text', 'value':t}) | |
sections.append(('par 100 gr',fields)) | |
fields = [] | |
t = "{:10d}".format(points) | |
fields.append({'title':'Points ', 'type':'text', 'value':t}) | |
sections.append(('Smartpoints 100 gr',fields)) | |
if portion: | |
fields = [] | |
t = portion | |
fields.append({'title':'Portion ', 'type':'text', 'value':t}) | |
sp_portion = int(points*(float(''.join(ch for ch in t if ch.isdigit()))/100)) | |
t = "{:10d}".format(sp_portion) | |
fields.append({'title':'Points ', 'type':'text', 'value':t}) | |
sections.append(('Portion',fields)) | |
else: | |
sp_portion = 0 | |
# save db record Aliment | |
self.add_food(product, self.barcode, kcal, saturated_fat, sugars, proteins, points, portion, sp_portion, thumb_data) | |
f = my_form_dialog(title=product, sections=sections, web_button=True, web_url=url, ui_image=image, graphic_data=graphic_data, dims=(self.width-60,self.height-10), editable=False) | |
return | |
def add_food(self, product, barcode, points_base, base=None, kcal=None, saturated_fat=None, sugars=None, proteins=None, portion=None, thumb=None, groupe=None): | |
# db record | |
# 'Aliment', nom, barcode, {'points base':12, 'base':(100,'gr'), 'kcal':359,'saturated_fat':0.5,'sugars':1.5,'proteins':12.5, 'thumb':data, 'portion':('pièce',7,'gr'), 'groupe':'féculents'} | |
self.sql_cursor.execute("SELECT * FROM WeightWatchers WHERE rec_type=? and rec_key=?", ('Aliment', product)) | |
if self.sql_cursor.fetchone(): | |
# record Poids of this date already exists | |
console.alert('modification impossible', 'cet aliment existe déjà', 'ok', hide_cancel_button=True) | |
return | |
data_dict = {'points base':points_base} | |
if base: | |
data_dict['base'] = base | |
if kcal: | |
data_dict['kcal'] = kcal | |
if saturated_fat: | |
data_dict['saturated_fat'] = saturated_fat | |
if sugars: | |
data_dict['sugars'] = sugars | |
if proteins: | |
data_dict['proteins'] = proteins | |
if thumb: | |
data_dict['thumb'] = thumb | |
if portion: | |
data_dict['portion'] = portion | |
if groupe: | |
data_dict['groupe'] = groupe | |
data_str = str(data_dict) | |
self.sql_cursor.execute("INSERT INTO WeightWatchers VALUES (?,?,?,?)", ('Aliment', product, barcode,data_str)) | |
self.sql_connect.commit() | |
def use_food(self, product, points, quantity, points_base, base, title="Utilisation d'un aliment", recette=None): | |
sections = [] | |
if recette: | |
# recette | |
row_height = 45 | |
fields = [] | |
fields.append({'title':recette}) | |
sections.append(('Recette',fields)) | |
fields = [] | |
for i in range(len(product)): | |
subtitle = f"{points_base[i]:2d} points / {base[i][0]:3d} {base[i][1]}" | |
points_per_unit = points_base[i] / base[i][0] | |
fields.append({'title':product[i], 'key':str(i), 'points':points[i], 'quantity':quantity[i], 'subtitle':subtitle, 'units':base[i][1], 'points_per_unit':points_per_unit, 'type':'number', 'value':str(quantity[i])}) | |
sections.append(('Ingrédients de la recette',fields)) | |
else: | |
# aliment | |
row_height = None | |
fields = [] | |
fields.append({'title':product}) | |
if quantity: | |
title_quantity = 'quantité (en '+base[1]+')' | |
fields.append({'title':title_quantity, 'type':'number', 'value':str(quantity)}) | |
sections.append(('Aliment',fields)) | |
fields = [] | |
if base: | |
title_points = f"{points_base:2d} points par {base[0]:3d} {base[1]}" | |
else: | |
title_points = 'aliment zeropoint' | |
fields.append({'title':title_points, 'type':'text', 'value':str(points), 'readonly':True}) | |
sections.append(('Points',fields)) | |
fields = [] | |
fields.append({'title':'repas', 'type':'text', 'value':self.meals[self.current_meal], 'segments':self.meals}) | |
sections.append(('Repas',fields)) | |
if self.use_food_from == 'day': | |
fields = [] | |
fields.append({'title':"suppression de l'aliment?", 'type':'switch', 'value':False}) | |
sections.append(('Suppression',fields)) | |
prev_current_meal = self.current_meal | |
f = my_form_dialog(title=title, sections=sections, dims=(self.width-60,self.height-10), row_height=row_height) | |
print('return from dialog in use_food',f) | |
if not f: | |
# cancel button | |
return | |
self.current_meal = self.meals.index(f['repas']) | |
if self.use_food_from == 'day': | |
row = self['day_view']['day_meals'].selected_row[1] | |
aliment = self.rows[row] | |
rec_type,rec_key,rec_subkey,rec_data = self.current_record | |
rec_data = ast.literal_eval(rec_data) | |
# ['Points','yyyymmdd',reserve hebdo,[aliment,...]] | |
# aliment = {'repas':1, aliment':nom, 'points':3, 'quantité':25,'points base':12, 'base':(100,'gr')} | |
idx = rec_data.index(aliment) | |
if f.get("suppression de l'aliment?",False): | |
# delete aliment possible and asked | |
b = console.alert("suppression de\n"+product, 'confirmez-vous?', 'oui', 'non', hide_cancel_button=True) | |
if b == 2: | |
console.alert('suppression annulée', '', 'ok', hide_cancel_button=True) | |
return | |
# delete confirmed | |
del rec_data[idx] | |
else: | |
# update aliment | |
aliment['repas'] = self.current_meal + 1 | |
if quantity: | |
aliment['quantité'] = int(f[title_quantity]) | |
aliment['points'] = int(f[title_points]) | |
rec_data[idx] = aliment | |
# upd rec_data record in db | |
rec_data = str(rec_data) | |
self.current_record = (rec_type,rec_key,rec_subkey,rec_data) | |
self.sql_cursor.execute("UPDATE WeightWatchers SET rec_data=? WHERE rec_type=? and rec_key=?",(rec_data, rec_type, rec_key)) | |
self.sql_connect.commit() | |
# re-display current day meals | |
self.set_rows() | |
elif self.use_food_from == 'zeropoints': | |
rec_type,rec_key,rec_subkey,rec_data = self.current_record | |
rec_data = ast.literal_eval(rec_data) | |
# {'repas':3, 'aliment':'Haricots secs', 'points':0, 'zeropoint':True}] | |
aliment = {} | |
aliment['aliment'] = product | |
aliment['repas'] = self.current_meal + 1 | |
aliment['points'] = 0 | |
aliment['zeropoint'] = True | |
rec_data.append(aliment) | |
rec_data = str(rec_data) | |
self.current_record = (rec_type,rec_key,rec_subkey,rec_data) | |
self.sql_cursor.execute("UPDATE WeightWatchers SET rec_data=? WHERE rec_type=? and rec_key=?",(rec_data, rec_type, rec_key)) | |
self.sql_connect.commit() | |
# re-display current day meals | |
self.set_rows() | |
elif self.use_food_from == 'aliment': | |
rec_type,rec_key,rec_subkey,rec_data = self.current_record | |
rec_data = ast.literal_eval(rec_data) | |
# {'repas':1, aliment':nom, 'points':3, 'quantité':25,'points base':12, 'base':(100,'gr')} | |
aliment = {} | |
aliment['aliment'] = product | |
aliment['repas'] = self.current_meal + 1 | |
aliment['quantité'] = int(f[title_quantity]) | |
aliment['points'] = int(f[title_points]) | |
aliment['points base'] = points_base | |
aliment['base'] = base | |
rec_data.append(aliment) | |
rec_data = str(rec_data) | |
self.current_record = (rec_type,rec_key,rec_subkey,rec_data) | |
self.sql_cursor.execute("UPDATE WeightWatchers SET rec_data=? WHERE rec_type=? and rec_key=?",(rec_data, rec_type, rec_key)) | |
self.sql_connect.commit() | |
# re-display current day meals | |
self.set_rows() | |
def scan_barcode(self): | |
self.barcode = '' | |
delegate = MetadataDelegate.new() | |
scan_view = ui.View(frame=(0, 0, 400, 400)) | |
self.scan_view = scan_view | |
scan_view.name = 'Scannez le Barcode' | |
session = AVCaptureSession.alloc().init() | |
device = AVCaptureDevice.defaultDeviceWithMediaType_('vide') | |
_input = AVCaptureDeviceInput.deviceInputWithDevice_error_(device, None) | |
if _input: | |
session.addInput_(_input) | |
else: | |
print('Failed to create input') | |
return | |
output = AVCaptureMetadataOutput.alloc().init() | |
queue = ObjCInstance(dispatch_get_current_queue()) | |
output.setMetadataObjectsDelegate_queue_(delegate, queue) | |
session.addOutput_(output) | |
output.setMetadataObjectTypes_(output.availableMetadataObjectTypes()) | |
prev_layer = AVCaptureVideoPreviewLayer.layerWithSession_(session) | |
prev_layer.frame = ObjCInstance(scan_view).bounds() | |
prev_layer.setVideoGravity_('AVLayerVideoGravityResizeAspectFill') | |
ObjCInstance(scan_view).layer().addSublayer_(prev_layer) | |
session.startRunning() | |
scan_view.present('sheet') | |
scan_view.wait_modal() | |
session.stopRunning() | |
delegate.release() | |
session.release() | |
output.release() | |
def will_close(self): | |
self.sql_connect.close() | |
self.copy_to_icloud() | |
@on_main_thread | |
def copy_to_icloud(self): | |
shutil.copy(self.full_path, '/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/WeightWatchers.db') | |
def main(): | |
global MainView | |
#----- Main process ----- | |
# Initializations | |
# Hide script | |
w, h = ui.get_screen_size() | |
MainView = MyView(w, h) | |
MainView.background_color='white' | |
MainView.name = 'Weight Watchers' | |
MainView.barcode = '' | |
# present main view | |
MainView.present('fullscreen', hide_title_bar=False) #, title_bar_color='yellow') | |
# Protect against import | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment