Add barcode scanning and meal selection features

Introduces a barcode scanner page using the camera for food item entry, adds routes for selecting meal types and scanning products, and updates the test.html template to allow adding items to specific meals. Also updates branding in base.html, changes the Flask secret key, and enables SSL by default in app.py.
This commit is contained in:
2025-07-08 11:00:30 +02:00
parent 30e5e959cd
commit 5b378cad38
6 changed files with 151 additions and 44 deletions

View File

@@ -1,4 +1,2 @@
# cal_counter
Calorie Counter Webapp
Bello -Iman
Calorie Counter Webapp

10
app.py
View File

@@ -13,7 +13,7 @@ from application.user.routes import user_bp
from typing import Optional
# Config
app.config["SECRET_KEY"] = "Iman"
app.config["SECRET_KEY"] = "Stef123"
login_manager.login_view = "login" # type: ignore
@@ -81,5 +81,9 @@ def scan():
# Run
if __name__ == "__main__":
# app.run(host="0.0.0.0", debug=True, ssl_context=("cert.pem", "key.pem"))
app.run(host="0.0.0.0", debug=True)
app.run(
host="0.0.0.0",
port=443,
debug=True,
ssl_context=("cert.pem", "key.pem"),
)

View File

@@ -13,7 +13,7 @@
<body class="bg-body-secondary">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">Iman was here</a>
<a class="navbar-brand" href="{{ url_for('index') }}">CalCounter</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>

View File

@@ -1,4 +1,13 @@
from flask import Blueprint, redirect, url_for, render_template, flash
from flask import (
Blueprint,
redirect,
url_for,
render_template,
flash,
session,
jsonify,
abort,
)
from flask_login import current_user
from application import db
from forms import FoodItemForm, FoodLogForm
@@ -177,14 +186,44 @@ def overview():
return render_template("overview.html")
@user_bp.route("/select_meal/<int:meal_type>", methods=["GET"])
def select_meal(meal_type: int):
assert type(meal_type) is int
session["meal_type"] = meal_type
return redirect(url_for("user.scan_product"))
@user_bp.route("/get_userid", methods=["GET"])
def scan_product():
return render_template("get_item.html")
@user_bp.route("/add_meal", methods=["GET"])
def add_meal():
meal_type = session["meal_type"]
if meal_type is None:
return redirect("/")
return render_template("scan.html")
@user_bp.route("/", methods=["GET"])
def test():
today = datetime.now(timezone.utc).date()
logs_today = current_user.food_logs.filter_by(date_=today).all()
logs = {0: [], 1: [], 2: [], 3: []}
logs = [[], [], [], []]
for log in logs_today:
logs[log.part_of_day].append(log)
print(logs)
return render_template(
"test.html", date=(today.strftime("%d/%m/%y")), logs=logs
)
@user_bp.route("/foodId_from_barcode/<string:barcode>", methods=["GET"])
def foodId_from_barcode(barcode: str):
if barcode.isdigit():
return jsonify({"food_id": barcode})
else:
return abort(
400, description="Invalid barcode: must contain only digits"
)

View File

@@ -0,0 +1,62 @@
{% extends "base.html" %}
{% block content %}
<div class="container py-5">
<div class="text-center mb-4">
<h1 class="fw-bold">Barcode Scanner</h1>
<p class="text-muted">Use your camera to scan barcodes</p>
</div>
<div class="d-flex justify-content-center mb-4">
<video id="video" class="border rounded shadow-sm" style="width: 100%; max-width: 500px;" autoplay
muted></video>
</div>
<div class="d-flex justify-content-center">
<button id="startButton" class="btn btn-primary px-4">Start Scanning</button>
<button id="stopButton" class="btn btn-danger px-4 ms-3">Stop</button>
</div>
</div>
<script type="module">
import { BrowserMultiFormatReader } from 'https://cdn.jsdelivr.net/npm/@zxing/library@0.21.3/+esm';
// constants
const codeReader = new BrowserMultiFormatReader();
const videoElement = document.getElementById('video');
// Start scanning for barcode
document.getElementById('startButton').addEventListener('click', async () => {
console.log('[DEBUG] Start button clicked')
try {
await navigator.mediaDevices.getUserMedia({ video: true });
} catch (err) {
alert("No camera found or no camera permission");
console.error("Could not access the camera:", err);
return;
}
console.log('[DEBUG] Permission given and at least one device present');
const devices = await codeReader.listVideoInputDevices();
console.log('[DEBUG] Cameras found:', devices);
const rearCamera = devices.find(device => device.label.toLowerCase().includes('back'))
|| devices.find(device => device.label.toLowerCase().includes('rear'))
|| devices[0]; // fallback
const selectedDeviceId = rearCamera?.deviceId;
await codeReader.decodeFromVideoDevice(selectedDeviceId, videoElement, async (result, err) => {
if (result) {
// Result found, this should post the barcode
const codeText = result.getText();
const baseURL = "{{url_for('user.food_item', barcode='0')}}"
window.location.href = baseURL.replace("0", encodeURIComponent(codeText))
}
});
});
document.getElementById('stopButton').addEventListener('click', () => {
codeReader.reset();
});
</script>
{% endblock %}

View File

@@ -23,47 +23,51 @@ Food Nutritional Info
<p>More content here...</p>
</div>
<div class="p-3 mb-2 border rounded">
<h4>Breakfast</h4>
{% for log in logs[0] %}
<p>{{log.food_item.name}}</p>
{% endfor %}
<div class="d-flex justify-content-between align-items-center">
<h4 class="mb-0">Breakfast</h4>
<a href="{{url_for('user.select_meal', meal_type=0)}}" class="btn btn-sm btn-primary">Add</a>
</div>
<div>
{% for log in logs[0] %}
<p class="p-0 mb-0">{{log.food_item.name}}</p>
{% endfor %}
</div>
</div>
<div class="p-3 mb-2 border rounded">
<div class="d-flex justify-content-between align-items-center">
<h4 class="mb-0">Lunch</h4>
<a href="{{url_for('user.select_meal', meal_type=1)}}" class="btn btn-sm btn-primary">Add</a>
</div>
<div>
{% for log in logs[1] %}
<p class="p-0 mb-0">{{log.food_item.name}}</p>
{% endfor %}
</div>
</div>
<div class="p-3 mb-2 border rounded">
<h4>Lunch</h4>
{% for log in logs[1] %}
<p>{{log.food_item.name}}</p>
{% endfor %}
<div class="d-flex justify-content-between align-items-center">
<h4 class="mb-0">Dinner</h4>
<a href="{{url_for('user.select_meal', meal_type=2)}}" class="btn btn-sm btn-primary">Add</a>
</div>
<div>
{% for log in logs[2] %}
<p class="p-0 mb-0">{{log.food_item.name}}</p>
{% endfor %}
</div>
</div>
<div class="p-3 mb-2 border rounded">
<h4>Dinner</h4>
{% for log in logs[2] %}
<p>{{log.food_item.name}}</p>
{% endfor %}
</div>
<div class="p-3 mb-2 border rounded">
<h4>Snacks</h4>
{% for log in logs[3] %}
<p>{{log.food_item.name}}</p>
{% endfor %}
<div class="d-flex justify-content-between align-items-center">
<h4 class="mb-0">Snacks</h4>
<a href="{{url_for('user.select_meal', meal_type=3)}}" class="btn btn-sm btn-primary">Add</a>
</div>
<div>
{% for log in logs[3] %}
<p class="p-0 mb-0">{{log.food_item.name}}</p>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="container-fluid my-4">
<div class="d-flex justify-content-center">
<h1>Overview {{date}}</h1>
</div>
<div class="d-flex justify-content-center">
<div class="mx-2">ENERGY</div>
<div class="mx-2">PROTEIN</div>
<div class="mx-2">CARBS</div>
<div class="mx-2">FATS</div>
</div>
<div class="m-5"></div>
<div class="d-flex justify-content-center">
<div> Breakfast </div>
</div>
</div>
{% endblock%}