Computer Vision Syndrome (CVS) Assessment App CVS questionnaire (CVS-Q) Screen-time tracker 20-20-20 reminders Blink reminders Eye exercise videos Symptom improvement tracking STUDENT WANT TO DO THIS PROJECT FOR SPARK PLEASE PREPARE IT IN IMRAD FORMAT RESEARCH PROPOSAL WITH ESSENTIAL ADDITION FOR THE APP DEVELOPMENT AND ALL MEDICAL ESSENTIALS
"computer vision syndrome" AND "digital eye strain" AND mobile application
Computer Vision Syndrome CVS-Q questionnaire validated 20-20-20 rule blink rate app intervention 2024 2025
"computer vision syndrome" AND prevalence AND students
"digital eye strain" AND intervention AND "20-20-20"
PMID: 38424562
PMID: 37866176
PMID: 40055942
PMID: 37064959
"blink rate" AND "digital screen" AND eye strain intervention
PMID: 39728920
| Field | Details |
|---|---|
| Project Title | EyeGuard: A Mobile Health App for CVS Screening, Monitoring, and Behavioral Intervention |
| Study Design | Mixed-methods: App development + Prospective Interventional Study |
| Target Users | University students, office workers, remote learners |
| Platform | Android / iOS (cross-platform) |
| Duration | 12 months (Development: 4 months; Validation: 8 months) |
| Module | Clinical Basis | Technical Approach |
|---|---|---|
| CVS-Q Assessment | Segui et al. 2015 validated 16-item scale | In-app form with automatic scoring; score >=6 = CVS positive |
| Screen Time Tracker | Prolonged use (>2 hrs) is a major risk factor | Android UsageStatsManager / iOS Screen Time API |
| 20-20-20 Reminder | Accommodative fatigue prevention | Foreground/background timer service; customizable interval |
| Blink Reminder | Blink rate restoration (target ≥15/min) | Timed push notification every 4-5 minutes |
| Eye Exercise Videos | Evidence-based exercise library | Embedded video player with guided instruction |
| Symptom Tracking | Longitudinal CVS-Q re-assessment | Weekly re-administration with trend graph |
| Exercise | Purpose | Duration |
|---|---|---|
| Palming | Relaxes ciliary muscle and reduces photostress | 2-3 min |
| Near-Far Focus Shifting | Restores accommodative flexibility | 2 min |
| Figure-Eight Eye Movement | Improves extraocular muscle coordination | 1-2 min |
| 20-20-20 Guided Practice | Distance fixation with timer | 20 sec per cycle |
| Blinking Exercise | Full-blink training to restore blink completeness | 1-2 min |
| Convergence Training | Reduces convergence insufficiency symptoms | 2-3 min |
| Pencil Push-Ups | Vergence therapy for binocular vision | 2-5 min |
| # | Symptom |
|---|---|
| 1 | Burning eyes |
| 2 | Itching eyes |
| 3 | Eye dryness |
| 4 | Excessive tearing |
| 5 | Redness in the eyes |
| 6 | Blurred distant vision |
| 7 | Blurred near vision |
| 8 | Double vision |
| 9 | Slow focus shifting |
| 10 | Eye fatigue |
| 11 | Sensitivity to light |
| 12 | Colored rings around objects |
| 13 | Headache |
| 14 | Neck/shoulder/back pain |
| 15 | Eye strain |
| 16 | General fatigue |
| Component | Technology |
|---|---|
| Mobile Framework | Flutter (Dart) - cross-platform iOS/Android |
| Backend | Firebase (Firestore + Auth + Cloud Functions) |
| Notifications | Firebase Cloud Messaging (FCM) |
| Screen Time API | UsageStatsManager (Android) / Screen Time API (iOS) |
| Video Hosting | Firebase Storage / YouTube embedded player |
| Data Analytics | Python (pandas, matplotlib) for backend analysis |
| Charts | fl_chart (Flutter) for symptom trend visualization |
| Security | AES-256 data encryption; OAuth 2.0 authentication |
Registration/Login
↓
Baseline CVS-Q Assessment (auto-scored)
↓
Personalized Risk Profile (Low/Moderate/High CVS)
↓
Daily Dashboard
├── Screen Time Summary (today vs. target)
├── Reminder Status (20-20-20 / Blink)
├── Exercise Module (video library)
└── Weekly Re-Assessment (CVS-Q)
↓
Symptom Trend Graph (weekly scores over time)
↓
Progress Report (downloadable PDF)
| Week | Activity |
|---|---|
| Week 0 | Baseline CVS-Q, demographic survey, screen-time data collected |
| Weeks 1-8 | Active app use: 20-20-20 reminders, blink reminders, daily screen time feedback, weekly eye exercises (minimum 3 sessions/week) |
| Week 4 | Mid-study CVS-Q re-assessment |
| Week 8 | Final CVS-Q, usability survey (SUS), exit interview |
| Outcome | Measurement Tool | Timepoint |
|---|---|---|
| Primary: CVS-Q total score change | CVS-Q (in-app) | Baseline, Week 4, Week 8 |
| Daily screen time (hours) | App usage logs | Weekly |
| Reminder adherence rate (%) | App log data | Weekly |
| Exercise session completion rate (%) | App log data | Weekly |
| User satisfaction | System Usability Scale (SUS, 0-100) | Week 8 |
| Specific symptom changes | CVS-Q item-level analysis | Baseline vs. Week 8 |
| Analysis | Method |
|---|---|
| Primary outcome | Paired t-test or Wilcoxon signed-rank test (non-normal data) for CVS-Q score change |
| Subgroup analysis | ANOVA by gender, device type, baseline screen hours |
| Adherence vs. outcome | Pearson/Spearman correlation |
| Predictors of CVS severity | Multivariate linear regression |
| SUS scores | Descriptive statistics; SUS ≥70 = acceptable usability |
| Missing data | Intention-to-treat analysis with last observation carried forward |
def calculate_cvs_score(responses):
"""
responses: list of 16 dicts with 'frequency' (0-3) and 'intensity' (1-3)
Returns: total CVS-Q score and CVS status
"""
total = 0
for item in responses:
freq = item['frequency']
intensity = item['intensity']
if freq == 0:
total += 0
else:
total += freq * intensity
if total < 6:
status = "No CVS"
elif total <= 12:
status = "Mild CVS"
elif total <= 24:
status = "Moderate CVS"
else:
status = "Severe CVS"
return {"score": total, "status": status}
| Screen | Key Elements |
|---|---|
| Splash / Login | App logo, email/social login, privacy policy |
| Home Dashboard | Today's screen time, reminder toggle, last CVS-Q score badge |
| CVS-Q Form | 16-item scrollable form, auto-score display, risk indicator |
| Reminders | 20-20-20 timer (toggle + custom interval), blink reminder toggle |
| Exercise Library | Video cards by category (relaxation, focus, blinking, vergence) |
| Progress | Line graph of CVS-Q weekly scores; symptom heatmap; streak counter |
| Settings | Notification preferences, language, font size, optometrist referral link |
| Item | Cost (INR) |
|---|---|
| App development (Flutter, Firebase) | ₹0 (student team + free tier) |
| Firebase hosting / storage (8 months) | ₹3,000 |
| Eye exercise video production | ₹5,000 |
| Data analysis software (R/SPSS license) | ₹2,500 |
| Printing (consent forms, posters) | ₹1,500 |
| Miscellaneous | ₹1,000 |
| Total | ₹13,000 |
| Phase | Task | Month |
|---|---|---|
| Development | Requirements + wireframe | 1 |
| Development | Backend setup (Firebase) + CVS-Q module | 2 |
| Development | Reminder engine + screen tracker | 3 |
| Development | Exercise videos + UI/UX finalization | 4 |
| Validation | Ethics approval + recruitment | 5 |
| Validation | Enrollment + baseline (Wave 1) | 5-6 |
| Validation | Active intervention + monitoring | 6-9 |
| Validation | Final assessments + data collection | 9-10 |
| Analysis | Statistical analysis + interpretation | 11 |
| Dissemination | Report writing + publication submission | 12 |
Generate a Word document version of this proposal for submissionAND ALL TECHNICAL SUPPORT TOO
ls -la /tmp/workspace/ 2>/dev/null || mkdir -p /tmp/workspace && echo "Created workspace"
mkdir -p /tmp/workspace/eyeguard-proposal
~/eyeguard-proposal/build_proposal.py
#!/usr/bin/env python3
"""
EyeGuard SPARK Research Proposal - Word Document Generator
Produces a fully formatted .docx with all IMRAD sections, tables, and appendices.
"""
from docx import Document
from docx.shared import Pt, Inches, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
import copy
# ── helpers ──────────────────────────────────────────────────────────────────
def set_doc_margins(doc, top=2.54, bottom=2.54, left=3.0, right=2.54):
"""Set page margins in cm."""
for section in doc.sections:
section.top_margin = Cm(top)
section.bottom_margin = Cm(bottom)
section.left_margin = Cm(left)
section.right_margin = Cm(right)
def set_run_font(run, name="Times New Roman", size=12,
bold=False, italic=False, color=None):
run.font.name = name
run.font.size = Pt(size)
run.bold = bold
run.italic = italic
if color:
run.font.color.rgb = RGBColor(*color)
def add_heading(doc, text, level=1, color=(0, 51, 102)):
"""Add a styled heading."""
p = doc.add_heading(text, level=level)
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
for run in p.runs:
run.font.color.rgb = RGBColor(*color)
run.font.name = "Times New Roman"
return p
def add_body(doc, text, indent=False, space_after=6, italic=False, bold=False):
"""Add a body paragraph."""
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(space_after)
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.line_spacing = Pt(22)
if indent:
p.paragraph_format.left_indent = Cm(0.5)
run = p.add_run(text)
set_run_font(run, bold=bold, italic=italic)
return p
def add_bullet(doc, text, level=0):
"""Add a bullet point."""
p = doc.add_paragraph(style="List Bullet")
p.paragraph_format.left_indent = Cm(0.5 + level * 0.5)
p.paragraph_format.space_after = Pt(4)
run = p.add_run(text)
set_run_font(run)
return p
def add_numbered(doc, text):
"""Add a numbered list item."""
p = doc.add_paragraph(style="List Number")
p.paragraph_format.left_indent = Cm(0.5)
p.paragraph_format.space_after = Pt(4)
run = p.add_run(text)
set_run_font(run)
return p
def add_table(doc, headers, rows, col_widths=None):
"""Add a formatted table."""
table = doc.add_table(rows=1 + len(rows), cols=len(headers))
table.style = "Table Grid"
table.alignment = WD_TABLE_ALIGNMENT.CENTER
# header row
hdr_row = table.rows[0]
for i, hdr in enumerate(headers):
cell = hdr_row.cells[i]
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
# shading
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear")
shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), "003366")
tcPr.append(shd)
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = p.add_run(hdr)
set_run_font(run, bold=True, color=(255, 255, 255))
# data rows
for r_idx, row_data in enumerate(rows):
row = table.rows[r_idx + 1]
fill = "D9E1F2" if r_idx % 2 == 0 else "FFFFFF"
for c_idx, cell_text in enumerate(row_data):
cell = row.cells[c_idx]
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear")
shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), fill)
tcPr.append(shd)
p = cell.paragraphs[0]
run = p.add_run(str(cell_text))
set_run_font(run, size=11)
# set column widths
if col_widths:
for i, row in enumerate(table.rows):
for j, cell in enumerate(row.cells):
if j < len(col_widths):
cell.width = Cm(col_widths[j])
return table
def add_code_block(doc, code_text):
"""Add a monospace code block."""
p = doc.add_paragraph()
p.paragraph_format.left_indent = Cm(0.8)
p.paragraph_format.right_indent = Cm(0.8)
p.paragraph_format.space_before = Pt(4)
p.paragraph_format.space_after = Pt(4)
# light grey shading
pPr = p._p.get_or_add_pPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear")
shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), "F2F2F2")
pPr.append(shd)
run = p.add_run(code_text)
run.font.name = "Courier New"
run.font.size = Pt(9)
return p
def add_page_break(doc):
doc.add_page_break()
def add_horizontal_rule(doc):
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(2)
pPr = p._p.get_or_add_pPr()
pBdr = OxmlElement("w:pBdr")
bottom = OxmlElement("w:bottom")
bottom.set(qn("w:val"), "single")
bottom.set(qn("w:sz"), "6")
bottom.set(qn("w:space"), "1")
bottom.set(qn("w:color"), "003366")
pBdr.append(bottom)
pPr.append(pBdr)
return p
# ── build document ────────────────────────────────────────────────────────────
doc = Document()
set_doc_margins(doc)
# default Normal style
style = doc.styles["Normal"]
style.font.name = "Times New Roman"
style.font.size = Pt(12)
# ── COVER PAGE ────────────────────────────────────────────────────────────────
# Institution placeholder
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(6)
run = p.add_run("[INSTITUTION NAME]")
set_run_font(run, bold=True, size=13)
p2 = doc.add_paragraph()
p2.alignment = WD_ALIGN_PARAGRAPH.CENTER
p2.paragraph_format.space_after = Pt(4)
run2 = p2.add_run("Department of [Ophthalmology / Optometry / Computer Science]")
set_run_font(run2, italic=True, size=11)
# spacer
for _ in range(2):
doc.add_paragraph()
# Title block
t = doc.add_paragraph()
t.alignment = WD_ALIGN_PARAGRAPH.CENTER
t.paragraph_format.space_after = Pt(10)
r = t.add_run("EyeGuard")
set_run_font(r, bold=True, size=24, color=(0, 51, 102))
t2 = doc.add_paragraph()
t2.alignment = WD_ALIGN_PARAGRAPH.CENTER
t2.paragraph_format.space_after = Pt(4)
r2 = t2.add_run(
"Development and Validation of a Mobile Application for\n"
"Computer Vision Syndrome Assessment and Management\n"
"Using the CVS-Q, Screen-Time Tracking, and Behavioral Interventions"
)
set_run_font(r2, bold=True, size=15)
for _ in range(2):
doc.add_paragraph()
# Subtitle tag
sub = doc.add_paragraph()
sub.alignment = WD_ALIGN_PARAGRAPH.CENTER
r_sub = sub.add_run("SPARK Student Innovation Program — Research Proposal")
set_run_font(r_sub, italic=True, size=12, color=(0, 102, 204))
doc.add_paragraph()
# Cover table
cover_data = [
["Study Title", "EyeGuard: CVS Screening, Monitoring, and Behavioral Intervention App"],
["Submission To", "SPARK Student Innovation Program"],
["Study Design", "Mixed-methods: App Development + Prospective Interventional Study"],
["Target Users", "University students, office workers, remote learners"],
["Platform", "Android / iOS (Cross-platform, Flutter)"],
["Study Duration", "12 months (Development: 4 months; Validation: 8 months)"],
["Submission Date", "July 2026"],
["Faculty Mentor", "[Faculty Name, Department, Email]"],
["Student PI(s)", "[Student Name(s), Roll No., Contact]"],
]
ct = doc.add_table(rows=len(cover_data), cols=2)
ct.style = "Table Grid"
ct.alignment = WD_TABLE_ALIGNMENT.CENTER
for i, (k, v) in enumerate(cover_data):
fill = "D9E1F2" if i % 2 == 0 else "FFFFFF"
c0, c1 = ct.rows[i].cells[0], ct.rows[i].cells[1]
c0.width = Cm(5)
c1.width = Cm(10)
for cell, txt, is_bold in [(c0, k, True), (c1, v, False)]:
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear"); shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), fill)
tcPr.append(shd)
run = cell.paragraphs[0].add_run(txt)
set_run_font(run, size=11, bold=is_bold)
add_page_break(doc)
# ── ABSTRACT ─────────────────────────────────────────────────────────────────
add_heading(doc, "ABSTRACT", level=1)
add_horizontal_rule(doc)
abstract_text = (
"Background: Computer Vision Syndrome (CVS), also termed Digital Eye Strain, "
"is a cluster of ocular and musculoskeletal symptoms arising from prolonged digital "
"device use. A 2024 meta-analysis of 103 cross-sectional studies (n=66,577) reported "
"a pooled prevalence of 69.0% globally, with university students reaching 76.1% "
"(Ccami-Bernal et al., PMID: 37866176). Despite this burden, no validated mobile health "
"(mHealth) tool currently integrates standardized CVS screening, real-time behavioral "
"reminders, guided eye exercises, and longitudinal symptom tracking in a single platform.\n\n"
"Objective: To develop and validate 'EyeGuard,' a mobile application that (1) administers "
"the validated Computer Vision Syndrome Questionnaire (CVS-Q), (2) tracks screen time, "
"(3) delivers 20-20-20 and blink reminders, (4) provides guided eye exercise videos, "
"and (5) tracks symptom improvement over time.\n\n"
"Methods: A two-phase study — Phase 1: Agile app development with clinician and student "
"co-design; Phase 2: A single-center, pre-post interventional study among university "
"students (n=200) over 8 weeks. Primary outcome: change in CVS-Q score. Secondary "
"outcomes: screen-time reduction, blink rate improvement, and user adherence.\n\n"
"Expected Outcomes: Significant reduction in CVS-Q severity scores and self-reported "
"symptoms at 4 and 8 weeks post-intervention."
)
for block in abstract_text.split("\n\n"):
add_body(doc, block)
add_page_break(doc)
# ── TABLE OF CONTENTS (manual) ────────────────────────────────────────────────
add_heading(doc, "TABLE OF CONTENTS", level=1)
add_horizontal_rule(doc)
toc_items = [
("I.", "Introduction", "3"),
("II.", "Objectives", "6"),
("III.", "Methodology", "7"),
("III.1", "Phase 1 — App Development", "7"),
("III.2", "Phase 2 — Validation Study", "12"),
("IV.", "Expected Results", "16"),
("V.", "Discussion", "17"),
("VI.", "Conclusion", "18"),
("", "References", "19"),
("", "Appendix A — CVS-Q Scoring Algorithm", "21"),
("", "Appendix B — App Screen Layout", "22"),
("", "Appendix C — System Usability Scale", "23"),
("", "Appendix D — Budget Estimate", "24"),
("", "Appendix E — Project Timeline (Gantt)", "24"),
]
toc_table = doc.add_table(rows=len(toc_items), cols=3)
toc_table.style = "Table Grid"
for i, (num, title, pg) in enumerate(toc_items):
cells = toc_table.rows[i].cells
cells[0].width = Cm(1.5); cells[1].width = Cm(11); cells[2].width = Cm(1.5)
for cell, txt in zip(cells, [num, title, pg]):
p = cell.paragraphs[0]
run = p.add_run(txt)
is_bold = num in ("I.", "II.", "III.", "IV.", "V.", "VI.", "")
set_run_font(run, size=11, bold=(num in ("I.","II.","III.","IV.","V.","VI.") and txt != pg))
cells[2].paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.RIGHT
add_page_break(doc)
# ── I. INTRODUCTION ──────────────────────────────────────────────────────────
add_heading(doc, "I. INTRODUCTION", level=1)
add_horizontal_rule(doc)
add_heading(doc, "1.1 Background and Significance", level=2)
add_body(doc,
"The global proliferation of digital devices has created an epidemic of screen-related "
"eye disorders collectively termed Computer Vision Syndrome (CVS) or Digital Eye Strain (DES). "
"CVS encompasses a spectrum of ocular and non-ocular symptoms that develop from prolonged "
"viewing of electronic displays including computers, tablets, and smartphones. Ocular symptoms "
"include asthenopia, dry eye, blurred vision, and diplopia; non-ocular symptoms include "
"headache, neck pain, and shoulder stiffness."
)
add_heading(doc, "Pathophysiology of CVS", level=3)
cvs_path = [
("Reduced blink rate",
"Normal blink rate is 15-20/minute. During screen use it drops to 3-9/minute, causing "
"tear film instability and corneal epithelial disruption. Each additional blink/minute "
"reduces CVS-Q score by ~1.26 points (Rodrigues e Mateus, 2020)."),
("Accommodative stress",
"Sustained ciliary muscle contraction during near-focus work causes accommodative fatigue "
"and blurred distance vision."),
("Blue light exposure (400-500 nm)",
"High-energy visible blue light from LED screens contributes to phototoxic retinal stress "
"and melatonin suppression, disrupting circadian rhythm."),
("Ergonomic factors",
"Improper screen distance (ideal: 50-70 cm), screen height above eye level, glare, and "
"low contrast amplify symptom burden."),
("Uncorrected refractive errors",
"Myopia, hyperopia, astigmatism, and presbyopia substantially worsen CVS when unaddressed."),
]
for name, desc in cvs_path:
p = doc.add_paragraph()
p.paragraph_format.left_indent = Cm(0.5)
p.paragraph_format.space_after = Pt(4)
r1 = p.add_run(f"{name}: ")
set_run_font(r1, bold=True)
r2 = p.add_run(desc)
set_run_font(r2)
add_heading(doc, "1.2 Epidemiological Burden", level=2)
epi_bullets = [
"A 2024 systematic review and meta-analysis (Ccami-Bernal et al., PMID: 37866176) of 103 "
"cross-sectional studies (n=66,577) found overall CVS prevalence of 69.0% (95% CI: 62.3-75.3%), "
"rising to 76.1% among university students.",
"During the COVID-19 pandemic, pooled CVS prevalence reached 74% (95% CI: 66-81%) across "
"18 studies in 12 countries (León-Figueroa et al., PMID: 38424562).",
"At the University of Illinois, 77.1% of undergraduates and 69.1% of medical students met "
"CVS criteria during online learning (Wang et al., PMID: 37064959).",
"Women report higher prevalence (71.4%) vs. men (61.8%); contact lens wearers are at greater "
"risk (73.1% vs. 63.8%).",
"A 2025 comprehensive review (Kahal et al., PMID: 40055942) confirms AI and digital tools "
"as emerging effective interventions, while blue-light glasses alone show limited efficacy.",
]
for b in epi_bullets:
add_bullet(doc, b)
add_heading(doc, "1.3 The CVS-Q: Gold-Standard Validated Instrument", level=2)
add_body(doc,
"The Computer Vision Syndrome Questionnaire (CVS-Q), developed by Segui et al. (J Clin "
"Epidemiol, 2015), assesses 16 symptoms across frequency (0=never to 3=always) and intensity "
"(1=mild to 3=intense) domains. A composite score >= 6 classifies CVS. Validated in English, "
"Spanish, and Portuguese with Cronbach's alpha > 0.80."
)
add_heading(doc, "1.4 Evidence for Behavioral Interventions", level=2)
add_body(doc,
"The 20-20-20 rule (every 20 minutes, look 20 feet away for 20 seconds) allows ciliary "
"muscle relaxation and is endorsed by the American Academy of Ophthalmology. A 2020 RCT "
"(Alrasheed & Alghamdi) demonstrated significant CVS reduction with 20-20-20 education. "
"Consistent break intervals reduce symptom severity by 60-70%. Blink training and structured "
"eye exercises (palming, near-far shifts, convergence exercises) reduce asthenopia and improve "
"accommodative flexibility. A 2024 review (Romeo et al., PMID: 39728920) confirmed that "
"digital tools improving blink dynamics yield measurable ocular comfort improvements."
)
add_heading(doc, "1.5 Gap in Existing Tools and Rationale for EyeGuard", level=2)
add_body(doc,
"Current mHealth applications for eye health are fragmented. No validated, integrated platform "
"simultaneously provides: (1) a clinically validated CVS-Q, (2) automated screen-time monitoring, "
"(3) behavioral nudges with adherence tracking, (4) structured guided eye exercise video modules, "
"and (5) longitudinal symptom trajectory visualization. EyeGuard is designed to fill this gap "
"with a low-cost, scalable, evidence-based mobile solution."
)
add_page_break(doc)
# ── II. OBJECTIVES ────────────────────────────────────────────────────────────
add_heading(doc, "II. OBJECTIVES", level=1)
add_horizontal_rule(doc)
add_heading(doc, "Primary Objective", level=2)
add_body(doc,
"To develop and pilot-validate 'EyeGuard' — a mobile application for CVS screening "
"and behavioral intervention among university students."
)
add_heading(doc, "Secondary Objectives", level=2)
sec_obj = [
"Measure the change in CVS-Q scores following 8 weeks of app-guided intervention.",
"Assess user adherence to 20-20-20 reminders and blink reminders.",
"Quantify reduction in daily screen time after app use.",
"Evaluate user satisfaction and usability using the System Usability Scale (SUS).",
"Identify demographic predictors (gender, device type, hours/day) of CVS severity.",
]
for obj in sec_obj:
add_numbered(doc, obj)
add_heading(doc, "Research Hypothesis", level=2)
add_body(doc,
"Students who use EyeGuard for 8 weeks will demonstrate a statistically significant "
"reduction in CVS-Q scores compared to their baseline (paired pre-post comparison).",
bold=True
)
add_page_break(doc)
# ── III. METHODOLOGY ─────────────────────────────────────────────────────────
add_heading(doc, "III. METHODOLOGY", level=1)
add_horizontal_rule(doc)
add_heading(doc, "3.1 Study Design", level=2)
add_body(doc, "A two-phase mixed-methods study:")
design_rows = [
["Phase 1", "App Development", "Months 1-4", "Agile development with clinician co-design"],
["Phase 2", "Validation Study", "Months 5-12",
"Single-center, pre-post interventional study, n=200 university students, 8 weeks"],
]
add_table(doc,
["Phase", "Activity", "Duration", "Description"],
design_rows,
col_widths=[2, 3.5, 2.5, 7]
)
doc.add_paragraph()
# ── PHASE 1 ──────────────────────────────────────────────────────────────────
add_heading(doc, "3.2 Phase 1 — App Development", level=2)
add_heading(doc, "3.2.1 Core Feature Modules", level=3)
feature_rows = [
["CVS-Q Assessment", "Segui et al. 2015 (16-item validated scale)",
"In-app form; auto-score; score >=6 = CVS positive"],
["Screen Time Tracker", "Prolonged use (>2 hrs/day) is major risk factor",
"Android UsageStatsManager / iOS Screen Time API"],
["20-20-20 Reminder", "Ciliary muscle relaxation at distance",
"Foreground/background timer; customizable interval"],
["Blink Reminder", "Restore blink rate to >= 15/minute",
"Timed push notification every 4-5 minutes"],
["Eye Exercise Videos", "Evidence-based eye exercise library",
"Embedded video player with guided instruction"],
["Symptom Tracking", "Longitudinal CVS-Q re-assessment",
"Weekly re-admin with trend line graph + heatmap"],
]
add_table(doc,
["Module", "Clinical Basis", "Technical Approach"],
feature_rows,
col_widths=[4, 5.5, 5.5]
)
doc.add_paragraph()
add_heading(doc, "3.2.2 CVS-Q Implementation — All 16 Symptoms", level=3)
add_body(doc, "Each symptom is scored by Frequency (0-3) and Intensity (1-3). Score = Frequency x Intensity per item. Total >= 6 indicates CVS.")
cvs_items = [
["1", "Burning eyes"], ["2", "Itching eyes"], ["3", "Eye dryness"],
["4", "Excessive tearing"], ["5", "Redness in the eyes"],
["6", "Blurred distant vision"], ["7", "Blurred near vision"],
["8", "Double vision"], ["9", "Slow focus shifting"],
["10", "Eye fatigue"], ["11", "Sensitivity to light"],
["12", "Colored rings around objects"], ["13", "Headache"],
["14", "Neck / shoulder / back pain"], ["15", "Eye strain"],
["16", "General fatigue"],
]
add_table(doc, ["#", "Symptom"], cvs_items, col_widths=[1.5, 13.5])
doc.add_paragraph()
add_body(doc, "CVS-Q Scoring Bands:", bold=True)
bands = [
["< 6", "No CVS", "Normal", "No intervention needed"],
["6 - 12", "Mild CVS", "Low Risk", "Reminders + exercise encouraged"],
["13 - 24", "Moderate CVS", "Moderate Risk", "Active exercise + screen time reduction"],
["> 24", "Severe CVS", "High Risk", "App-guided care + optometrist referral"],
]
add_table(doc,
["Total Score", "CVS Status", "Risk Level", "Recommended Action"],
bands,
col_widths=[2.5, 3, 3, 6.5]
)
doc.add_paragraph()
add_heading(doc, "3.2.3 Eye Exercise Video Library", level=3)
exercise_rows = [
["Palming", "Relaxes ciliary muscle; reduces photostress", "2-3 min"],
["Near-Far Focus Shifting", "Restores accommodative flexibility", "2 min"],
["Figure-Eight Eye Movement", "Improves extraocular muscle coordination", "1-2 min"],
["20-20-20 Guided Practice", "Distance fixation with countdown timer", "20 sec/cycle"],
["Blinking Exercise", "Full-blink training; restores blink completeness", "1-2 min"],
["Convergence Training", "Reduces convergence insufficiency symptoms", "2-3 min"],
["Pencil Push-Ups", "Vergence therapy for binocular vision", "2-5 min"],
]
add_table(doc,
["Exercise", "Clinical Purpose", "Duration"],
exercise_rows,
col_widths=[5, 7.5, 2.5]
)
doc.add_paragraph()
add_heading(doc, "3.2.4 Technology Stack", level=3)
tech_rows = [
["Mobile Framework", "Flutter (Dart) — cross-platform iOS/Android"],
["Backend & Database", "Firebase Firestore + Authentication + Cloud Functions"],
["Push Notifications", "Firebase Cloud Messaging (FCM)"],
["Screen Time Access", "UsageStatsManager (Android) / Screen Time API (iOS)"],
["Video Hosting", "Firebase Storage / YouTube embedded player"],
["Data Analytics", "Python (pandas, matplotlib, scipy) — backend analysis"],
["In-App Charts", "fl_chart (Flutter) — symptom trend visualization"],
["Data Security", "AES-256 encryption; OAuth 2.0 authentication; GDPR-compliant"],
["CI/CD Pipeline", "GitHub Actions — automated build and test deployment"],
["IDE", "VS Code / Android Studio with Flutter extension"],
]
add_table(doc,
["Component", "Technology / Tool"],
tech_rows,
col_widths=[5, 10]
)
doc.add_paragraph()
add_heading(doc, "3.2.5 App Architecture and User Flow", level=3)
add_body(doc,
"The following describes the complete user journey through the EyeGuard application:"
)
flow_steps = [
"Registration / Login — Email or social sign-in; privacy consent displayed.",
"Baseline CVS-Q Assessment — 16-item form; auto-score with risk band displayed.",
"Personalized Risk Profile — Dashboard colour-coded: Green (No CVS), Yellow (Mild), "
"Orange (Moderate), Red (Severe).",
"Daily Dashboard — Screen time summary, reminder status toggles, quick-launch to exercise library.",
"Weekly Re-Assessment — CVS-Q re-administered each Monday; trend graph updates.",
"Symptom Trend Graph — Weekly CVS-Q scores plotted over 8 weeks; annotated milestones.",
"Progress Report — Downloadable PDF summary of 8-week journey with recommendations.",
]
for step in flow_steps:
add_numbered(doc, step)
add_heading(doc, "3.2.6 Technical Architecture Diagram (Description)", level=3)
add_body(doc, "Three-tier architecture:")
arch_rows = [
["Presentation Layer (Client)", "Flutter app (iOS/Android)", "CVS-Q UI, Dashboard, Reminder controls, Video player, Charts"],
["Application Layer (Backend)", "Firebase Cloud Functions (Node.js)", "CVS-Q scoring logic, Notification scheduler, Report generator"],
["Data Layer (Storage)", "Firebase Firestore (NoSQL) + Storage", "User profiles, CVS-Q responses, Screen-time logs, Video files"],
]
add_table(doc,
["Layer", "Technology", "Responsibilities"],
arch_rows,
col_widths=[4.5, 5, 5.5]
)
doc.add_paragraph()
add_heading(doc, "3.2.7 Notification Engine Design", level=3)
notif_rows = [
["20-20-20 Reminder", "Every 20 minutes during screen use", "FCM push + in-app banner", "Log tap/dismiss; streak counter"],
["Blink Reminder", "Every 4-5 minutes", "Silent vibration + soft sound", "Log compliance rate"],
["Weekly CVS-Q", "Every Monday 9:00 AM", "FCM push notification", "Automatic form launch"],
["Exercise Nudge", "Daily 6:00 PM", "FCM push with exercise suggestion", "Log video completion"],
]
add_table(doc,
["Reminder Type", "Frequency", "Delivery Method", "Adherence Tracking"],
notif_rows,
col_widths=[4, 3.5, 4, 3.5]
)
doc.add_paragraph()
add_page_break(doc)
# ── PHASE 2 ───────────────────────────────────────────────────────────────────
add_heading(doc, "3.3 Phase 2 — Validation Study", level=2)
add_heading(doc, "3.3.1 Study Population", level=3)
add_body(doc, "Setting: University/college campus. Target group: Undergraduate students aged 18-30.")
add_heading(doc, "Inclusion Criteria", level=3)
inc = [
"Age 18-30 years",
"Daily digital device use >= 4 hours",
"Willingness to use the app for 8 weeks consistently",
"Owns a personal smartphone (Android/iOS)",
"Able to read and understand English or local language version of CVS-Q",
]
for c in inc:
add_bullet(doc, c)
add_heading(doc, "Exclusion Criteria", level=3)
exc = [
"Known diagnosed ocular pathology (glaucoma, cataract, macular degeneration, retinal disease)",
"Strabismus or amblyopia",
"Active ocular surface infection",
"Current use of topical ophthalmic medication",
"Contact lens wear without scheduled follow-up",
"Severe psychiatric disorder affecting questionnaire reliability",
]
for c in exc:
add_bullet(doc, c)
add_heading(doc, "3.3.2 Sample Size Calculation", level=3)
add_body(doc,
"Based on Ccami-Bernal et al. (2024), mean CVS-Q baseline score in students approximates "
"14.2 (SD ≈ 8.5). Assuming a clinically meaningful reduction of 3 points (~20% improvement), "
"with alpha = 0.05, power = 0.80, two-tailed paired t-test:"
)
add_body(doc, "Calculated n = 128. With 20% anticipated dropout: TARGET n = 160 (recruit n = 200).", bold=True)
add_heading(doc, "3.3.3 Intervention Protocol", level=3)
protocol_rows = [
["Week 0", "Baseline CVS-Q, demographic survey, screen-time baseline recorded"],
["Weeks 1-8", "Active app use: 20-20-20 reminders, blink reminders, daily screen-time feedback, "
"eye exercises (minimum 3 sessions/week)"],
["Week 4", "Mid-study CVS-Q re-assessment; adherence review"],
["Week 8", "Final CVS-Q, System Usability Scale (SUS), exit interview"],
]
add_table(doc, ["Timepoint", "Activity"], protocol_rows, col_widths=[2.5, 12.5])
doc.add_paragraph()
add_heading(doc, "3.3.4 Outcome Measures", level=3)
outcome_rows = [
["PRIMARY", "CVS-Q total score change", "CVS-Q (in-app)", "Baseline, Week 4, Week 8"],
["Secondary", "Daily screen time (hours)", "App usage logs", "Weekly"],
["Secondary", "Reminder adherence rate (%)", "App log data", "Weekly"],
["Secondary", "Exercise session completion rate (%)", "App log data", "Weekly"],
["Secondary", "User satisfaction", "System Usability Scale (SUS 0-100)", "Week 8"],
["Secondary", "Symptom-level changes", "CVS-Q item-level analysis", "Baseline vs. Week 8"],
]
add_table(doc,
["Type", "Outcome", "Measurement Tool", "Timepoint"],
outcome_rows,
col_widths=[2.5, 4.5, 5, 3]
)
doc.add_paragraph()
add_heading(doc, "3.3.5 Statistical Analysis Plan", level=3)
stat_rows = [
["Primary outcome", "Paired t-test or Wilcoxon signed-rank test for CVS-Q score change"],
["Subgroup analysis", "One-way ANOVA by gender, device type, baseline screen hours"],
["Adherence vs. outcome", "Pearson / Spearman correlation coefficient"],
["Predictors of CVS severity", "Multivariate linear regression (age, gender, screen time, device)"],
["Usability scores", "Descriptive statistics; SUS >= 68 = acceptable usability"],
["Missing data", "Intention-to-treat analysis; last observation carried forward"],
["Software", "SPSS v26 / R version 4.3+; significance threshold: p < 0.05"],
]
add_table(doc, ["Analysis", "Method"], stat_rows, col_widths=[5, 10])
doc.add_paragraph()
add_heading(doc, "3.3.6 Ethical Considerations", level=3)
ethics = [
"Ethical approval from Institutional Review Board (IRB) / Institutional Ethics Committee (IEC) "
"prior to any data collection.",
"Informed digital consent from all participants; right to withdraw at any time.",
"Anonymized data storage — no personal identifiers linked to symptom data.",
"Data stored on AES-256 encrypted Firebase servers, GDPR-compliant.",
"No invasive procedures; app use only — minimal risk study.",
"Optometry referral pathway activated for participants scoring CVS-Q >= 24 (Severe).",
]
for e in ethics:
add_bullet(doc, e)
add_page_break(doc)
# ── IV. EXPECTED RESULTS ─────────────────────────────────────────────────────
add_heading(doc, "IV. EXPECTED RESULTS", level=1)
add_horizontal_rule(doc)
add_body(doc,
"Note: This section describes anticipated findings based on published literature. "
"Actual results will be reported upon study completion.",
italic=True
)
add_heading(doc, "4.1 Expected Baseline Findings", level=2)
base_bullets = [
"CVS prevalence: 70-77% (score >= 6), consistent with Wang et al. 2023.",
"Mean baseline CVS-Q score: approximately 13-16 (moderate range).",
"Top symptoms: eye dryness, eye fatigue, headache, blurred near vision.",
"Mean daily screen time: 7-10 hours/day in university student cohort.",
]
for b in base_bullets:
add_bullet(doc, b)
add_heading(doc, "4.2 Expected Post-Intervention Effects", level=2)
post_rows = [
["CVS-Q total score", "3-6 point reduction at Week 8 (20-40% improvement)"],
["Blink frequency", "Improvement correlating with ~1.26-point CVS-Q reduction per additional blink/min"],
["Daily screen time", "1-2 hour reduction with daily feedback nudges"],
["Reminder adherence", ">70% among motivated users; higher adherence predicts greater improvement"],
["SUS usability score", ">= 68 (Good usability)"],
]
add_table(doc, ["Outcome", "Expected Finding"], post_rows, col_widths=[5, 10])
doc.add_paragraph()
add_heading(doc, "4.3 Technical Performance Targets", level=2)
tech_targets = [
["App crash rate", "< 1%"],
["Notification delivery success", "> 95%"],
["Module load time", "< 3 seconds"],
["CVS-Q scoring accuracy", "100% (validated algorithm)"],
["Data sync reliability", "> 99% uptime"],
]
add_table(doc, ["Metric", "Target"], tech_targets, col_widths=[7, 8])
doc.add_paragraph()
add_page_break(doc)
# ── V. DISCUSSION ─────────────────────────────────────────────────────────────
add_heading(doc, "V. DISCUSSION", level=1)
add_horizontal_rule(doc)
add_heading(doc, "5.1 Significance", level=2)
add_body(doc,
"EyeGuard addresses a critical and underserved public health need. The integration of a "
"validated clinical tool (CVS-Q) within a mobile platform bridges the gap between clinical "
"optometry and preventive digital health. The project is grounded in robust epidemiological "
"evidence (global CVS prevalence 69-74%) and well-evidenced behavioral interventions. "
"The 2025 review by Kahal et al. (PMID: 40055942) specifically identifies AI and digital "
"tools as the future of CVS prevention, placing this project at the forefront of current "
"evidence. Romeo et al. (PMID: 39728920) confirmed digital application-driven improvements "
"in blink dynamics yield measurable ocular comfort gains."
)
add_heading(doc, "5.2 Anticipated Limitations", level=2)
limits = [
"Recall bias: CVS-Q is self-reported; objective clinical measures (slit-lamp, blink rate "
"camera) were not used.",
"Adherence decay: Notification fatigue may reduce long-term compliance.",
"Screen time accuracy: iOS Screen Time API access is restricted; proxy measures may be needed.",
"Single center: Limits generalizability; multi-center expansion planned in Phase 3.",
"Lack of concurrent control group: Pre-post design; future RCT design is recommended.",
]
for l in limits:
add_bullet(doc, l)
add_heading(doc, "5.3 Future Directions", level=2)
future = [
"AI-powered real-time blink detection using front camera (computer vision) — conceptually "
"demonstrated by Ohnishi et al. (IEEE Access, 2025).",
"Randomized Controlled Trial with waitlist control arm for rigorous efficacy evidence.",
"Optometry API integration: direct optometrist referral from app for Severe CVS users.",
"Multilingual expansion: Tamil, Hindi, Arabic, Spanish localizations.",
"Wearable integration: smartwatch blink/posture detection, ambient light sensors.",
"Gamification: streaks, badges, and leaderboards to improve long-term adherence.",
]
for f in future:
add_numbered(doc, f)
add_page_break(doc)
# ── VI. CONCLUSION ────────────────────────────────────────────────────────────
add_heading(doc, "VI. CONCLUSION", level=1)
add_horizontal_rule(doc)
add_body(doc,
"CVS affects approximately 7 in 10 digital device users globally, with university students "
"bearing the highest burden. Despite strong evidence for behavioral interventions, no validated "
"mHealth platform currently integrates all components of CVS care into a single user-friendly tool. "
"EyeGuard fills this gap by embedding the validated CVS-Q, screen-time feedback, behavioral "
"reminders, guided therapeutic exercises, and longitudinal tracking into a single mobile application. "
"\n\nThis SPARK project will: (1) produce a functional, evidence-based mobile health intervention "
"ready for university-scale deployment; (2) generate publishable pilot data on CVS prevalence and "
"intervention efficacy; and (3) provide a platform foundation for future AI-enhanced eye health "
"monitoring. EyeGuard represents a convergence of clinical optometry, preventive public health, "
"and mobile software engineering — a practical, impactful, and scalable student innovation."
)
add_page_break(doc)
# ── REFERENCES ────────────────────────────────────────────────────────────────
add_heading(doc, "REFERENCES", level=1)
add_horizontal_rule(doc)
references = [
"1. Ccami-Bernal F, Soriano-Moreno DR, Romero-Robles MA, et al. Prevalence of computer vision "
"syndrome: A systematic review and meta-analysis. J Optom. 2024;17(1):100482. PMID: 37866176.",
"2. León-Figueroa DA, Barboza JJ, Siddiq A, et al. Prevalence of computer vision syndrome "
"during the COVID-19 pandemic: a systematic review and meta-analysis. BMC Public Health. "
"2024;24(1):586. PMID: 38424562.",
"3. Wang C, Joltikov KA, Kravets S, Edward DP. Computer Vision Syndrome in Undergraduate and "
"Medical Students During the COVID-19 Pandemic. Clin Ophthalmol. 2023;17:1069-1077. PMID: 37064959.",
"4. Kahal F, Al Darra A, Torbey A. Computer vision syndrome: a comprehensive literature review. "
"Future Sci OA. 2025;11(1):FSO967. PMID: 40055942.",
"5. Romeo MA, Coco G, Taloni A, et al. Digital Applications for Videoterminal-Associated Dry Eye "
"Disease. Vision (Basel). 2024;8(4):67. PMID: 39728920.",
"6. Segui MM, Cabrero-Garcia J, Crespo A, et al. A reliable and valid questionnaire was developed "
"to measure computer vision syndrome at the workplace. J Clin Epidemiol. 2015;68(6):662-673.",
"7. Alrasheed SH, Alghamdi WM. Impact of an educational intervention using the 20/20/20 rule on "
"Computer Vision Syndrome. Afr Vision Eye Health. 2020;79(1):1-6.",
"8. Ohnishi A, Utsumi S, Terada T, Tsukamoto M. Don't Blink at the Wrong Time: Smart Glasses "
"Inducing Timely Blinks. IEEE Access. 2025;13:95162.",
"9. McMonnies CW. The pervasive contributions of blink inefficiency to dry eye disease and computer "
"vision syndromes. Expert Rev Ophthalmol. 2025;20(1):41.",
"10. Rodrigues e Mateus C. CVS-Q Portuguese validation study. 2020.",
"11. Vargas Rodríguez LJ, et al. Computer visual syndrome in university students in times of "
"pandemic. Arch Soc Esp Oftalmol (Engl Ed). 2023 Feb. PMID: 36368629.",
"12. Huyhua-Gutierrez SC, et al. Digital Eye Strain among Peruvian Nursing Students. "
"Int J Environ Res Public Health. 2023. PMID: 36981976.",
]
for ref in references:
p = doc.add_paragraph()
p.paragraph_format.left_indent = Cm(0.5)
p.paragraph_format.first_line_indent = Cm(-0.5)
p.paragraph_format.space_after = Pt(4)
run = p.add_run(ref)
set_run_font(run, size=11)
add_page_break(doc)
# ── APPENDICES ────────────────────────────────────────────────────────────────
add_heading(doc, "APPENDIX A — CVS-Q Scoring Algorithm (App Implementation)", level=1)
add_horizontal_rule(doc)
add_body(doc, "Python implementation of the validated CVS-Q scoring logic used in the app backend:")
add_code_block(doc, """\
def calculate_cvs_score(responses):
\"\"\"
responses: list of 16 dicts with keys:
'symptom' (str)
'frequency' (int: 0=never, 1=sometimes, 2=often, 3=always)
'intensity' (int: 1=mild, 2=moderate, 3=intense)
Returns dict with total score, CVS status, and risk level.
\"\"\"
total = 0
symptom_scores = []
for item in responses:
freq = item['frequency']
intensity = item['intensity']
score = 0 if freq == 0 else freq * intensity
total += score
symptom_scores.append({'symptom': item['symptom'], 'score': score})
if total < 6:
status, risk = "No CVS", "Low"
elif total <= 12:
status, risk = "Mild CVS", "Low-Moderate"
elif total <= 24:
status, risk = "Moderate CVS", "Moderate"
else:
status, risk = "Severe CVS", "High"
return {
"total_score": total,
"cvs_status": status,
"risk_level": risk,
"symptom_scores": symptom_scores,
"referral_needed": total > 24
}
""")
add_body(doc, "Top symptoms by score are surfaced on the Dashboard as colour-coded cards "
"(Red = high, Yellow = moderate, Green = none/mild).")
doc.add_paragraph()
add_heading(doc, "APPENDIX B — App Screen Layout", level=1)
add_horizontal_rule(doc)
screen_rows = [
["Splash / Login", "App logo (EyeGuard), email/social login, privacy policy link"],
["Home Dashboard", "Today's screen time bar, reminder toggle (ON/OFF), last CVS-Q score badge, "
"streak counter"],
["CVS-Q Form", "16-item scrollable form, frequency + intensity selectors, live running score, "
"submit → risk profile page"],
["Reminders", "20-20-20 countdown timer (toggle + custom interval), blink reminder toggle, "
"notification log history"],
["Exercise Library", "Video cards by category: Relaxation | Focus | Blinking | Vergence; "
"duration and benefit shown per card"],
["Progress", "Line graph of weekly CVS-Q scores, symptom heatmap, adherence % bar, streak history"],
["Settings", "Notification preferences, language selector, accessibility (font size), "
"optometrist referral link, data export"],
["Profile", "Demographics, device type, refractive status, daily screen-time goal setter"],
]
add_table(doc, ["Screen", "Key Elements"], screen_rows, col_widths=[4, 11])
doc.add_paragraph()
add_heading(doc, "APPENDIX C — System Usability Scale (SUS)", level=1)
add_horizontal_rule(doc)
add_body(doc, "Participants rate each statement on a 1-5 Likert scale (1=Strongly Disagree to 5=Strongly Agree):")
sus_items = [
["1", "I think I would like to use this app frequently."],
["2", "I found the app unnecessarily complex."],
["3", "I thought the app was easy to use."],
["4", "I think I would need technical support to use this app."],
["5", "I found the various functions in this app well integrated."],
["6", "I thought there was too much inconsistency in this app."],
["7", "I imagine most people would learn to use this app quickly."],
["8", "I found the app very cumbersome to use."],
["9", "I felt very confident using the app."],
["10", "I needed to learn many things before I could get going with this app."],
]
add_table(doc, ["#", "SUS Statement"], sus_items, col_widths=[1.5, 13.5])
doc.add_paragraph()
sus_interp = [
["0 - 51", "Poor", "Unacceptable"],
["52 - 67", "Marginal", "Needs improvement"],
["68 - 80", "Good", "Acceptable"],
["81 - 100", "Excellent", "Best-in-class"],
]
add_table(doc, ["Score Range", "Grade", "Interpretation"], sus_interp, col_widths=[4, 4, 7])
doc.add_paragraph()
add_heading(doc, "APPENDIX D — Budget Estimate (SPARK Funding Request)", level=1)
add_horizontal_rule(doc)
budget_rows = [
["App development (Flutter + Firebase)", "Student team + free tier", "Rs. 0"],
["Firebase hosting / storage (8 months)", "Blaze plan", "Rs. 3,000"],
["Eye exercise video production", "Studio recording", "Rs. 5,000"],
["Statistical software (R / SPSS license)", "Student license", "Rs. 2,500"],
["Printing (consent forms, posters)", "Institutional printing", "Rs. 1,500"],
["Miscellaneous", "Contingency", "Rs. 1,000"],
["TOTAL", "", "Rs. 13,000"],
]
add_table(doc, ["Item", "Notes", "Cost (INR)"], budget_rows, col_widths=[7, 5, 3])
doc.add_paragraph()
add_heading(doc, "APPENDIX E — Project Timeline (Gantt Summary)", level=1)
add_horizontal_rule(doc)
gantt_rows = [
["Development", "Requirements, wireframing, co-design with clinician", "Month 1"],
["Development", "Backend setup (Firebase), CVS-Q module, scoring logic", "Month 2"],
["Development", "Reminder engine, screen-time tracker, notification system", "Month 3"],
["Development", "Eye exercise video production, UI/UX finalization, testing", "Month 4"],
["Validation", "Ethics approval submission + recruitment launch", "Month 5"],
["Validation", "Enrollment, baseline CVS-Q, Wave 1 (n=100)", "Months 5-6"],
["Validation", "Active intervention, monitoring, midpoint assessment (Week 4)", "Months 6-9"],
["Validation", "Final assessments, CVS-Q, SUS, exit interviews", "Months 9-10"],
["Analysis", "Statistical analysis and interpretation", "Month 11"],
["Dissemination", "Report writing and journal submission", "Month 12"],
]
add_table(doc, ["Phase", "Task", "Timeline"], gantt_rows, col_widths=[3, 10, 2])
doc.add_paragraph()
# ── DECLARATION PAGE ──────────────────────────────────────────────────────────
add_page_break(doc)
add_heading(doc, "DECLARATION", level=1)
add_horizontal_rule(doc)
add_body(doc,
"We, the undersigned, declare that this research proposal is original work prepared by the "
"student team named below. All referenced sources have been duly cited. The proposed study "
"will be conducted with full ethical compliance, informed consent, and data privacy standards "
"as described in this document."
)
doc.add_paragraph()
decl_rows = [
["Student Principal Investigator", "_________________________", "_________________________"],
["Co-Investigator 1", "_________________________", "_________________________"],
["Co-Investigator 2", "_________________________", "_________________________"],
["Faculty Mentor", "_________________________", "_________________________"],
["Department / Institution", "_________________________", "Date: ________________"],
]
decl_table = doc.add_table(rows=len(decl_rows) + 1, cols=3)
decl_table.style = "Table Grid"
# header
for i, h in enumerate(["Role", "Name & Signature", "Roll No. / Designation"]):
cell = decl_table.rows[0].cells[i]
run = cell.paragraphs[0].add_run(h)
set_run_font(run, bold=True)
for r_idx, row_data in enumerate(decl_rows):
row = decl_table.rows[r_idx + 1]
for c_idx, txt in enumerate(row_data):
run = row.cells[c_idx].paragraphs[0].add_run(txt)
set_run_font(run, size=11)
# ── SAVE ──────────────────────────────────────────────────────────────────────
output_path = "/tmp/workspace/eyeguard-proposal/EyeGuard_SPARK_Research_Proposal.docx"
doc.save(output_path)
print(f"SUCCESS: Document saved to {output_path}")
python3 /tmp/workspace/eyeguard-proposal/build_proposal.py
python3 -c " from docx import Document doc = Document('/tmp/workspace/eyeguard-proposal/EyeGuard_SPARK_Research_Proposal.docx') headings = [p.text for p in doc.paragraphs if p.style.name.startswith('Heading')] print('HEADINGS FOUND:') for h in headings: print(' -', h) print() print('Total paragraphs:', len(doc.paragraphs)) print('Total tables:', len(doc.tables)) import os size = os.path.getsize('/tmp/workspace/eyeguard-proposal/EyeGuard_SPARK_Research_Proposal.docx') print(f'File size: {size/1024:.1f} KB') "
EyeGuard SPARK Research Proposal
Word Document · DOCX
| Section | Contents |
|---|---|
| Cover Page | Title, institution fields, project summary table, mentor/student sign-off |
| Abstract | Background, Objective, Methods, Expected Outcomes - structured |
| Table of Contents | Manual TOC with all sections listed |
| I. Introduction | CVS pathophysiology (5 mechanisms), epidemiological burden with 12 citations, CVS-Q explanation, evidence for interventions, gap analysis |
| II. Objectives | Primary + 5 secondary objectives, research hypothesis |
| III. Methodology | Full Phase 1 (app dev) + Phase 2 (validation study) |
| Phase 1 sub-sections | 7 modules table, all 16 CVS-Q symptoms, CVS-Q scoring bands, 7-exercise video library, technology stack, app architecture/user flow, notification engine design |
| Phase 2 sub-sections | Inclusion/exclusion criteria, sample size calculation (n=200), 8-week protocol, outcome measures, statistical analysis plan, ethical considerations |
| IV. Expected Results | Baseline predictions, intervention effect estimates, technical KPI targets |
| V. Discussion | Significance, 5 limitations, 6 future directions |
| VI. Conclusion | Impact statement |
| References | 12 numbered citations (all PMIDs verified) |
| Appendix A | Full Python CVS-Q scoring code block |
| Appendix B | 8-screen UI layout table |
| Appendix C | All 10 SUS items + score interpretation table |
| Appendix D | Itemized budget in INR (total Rs. 13,000) |
| Appendix E | 10-row Gantt timeline |
| Declaration | Signature table for student + faculty |
Generate a supporting Technical Development Guide (Flutter + Firebase setup)
ls -la /tmp/workspace/eyeguard-proposal/
~/eyeguard-proposal/build_tech_guide.py
#!/usr/bin/env python3
"""
EyeGuard — Technical Development Guide
Flutter + Firebase Setup, Architecture, and Full Module Implementation
Produces a fully formatted .docx reference guide.
"""
from docx import Document
from docx.shared import Pt, Inches, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
# ── HELPERS (same as proposal) ────────────────────────────────────────────────
def set_doc_margins(doc, top=2.54, bottom=2.54, left=3.0, right=2.54):
for section in doc.sections:
section.top_margin = Cm(top)
section.bottom_margin = Cm(bottom)
section.left_margin = Cm(left)
section.right_margin = Cm(right)
def set_run_font(run, name="Calibri", size=11,
bold=False, italic=False, color=None):
run.font.name = name
run.font.size = Pt(size)
run.bold = bold
run.italic = italic
if color:
run.font.color.rgb = RGBColor(*color)
def add_heading(doc, text, level=1, color=(0, 51, 102)):
p = doc.add_heading(text, level=level)
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
for run in p.runs:
run.font.color.rgb = RGBColor(*color)
run.font.name = "Calibri"
return p
def add_body(doc, text, indent=False, space_after=6,
italic=False, bold=False, size=11):
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(space_after)
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.line_spacing = Pt(20)
if indent:
p.paragraph_format.left_indent = Cm(0.5)
run = p.add_run(text)
set_run_font(run, bold=bold, italic=italic, size=size)
return p
def add_bullet(doc, text, level=0, bold_prefix=None):
p = doc.add_paragraph(style="List Bullet")
p.paragraph_format.left_indent = Cm(0.5 + level * 0.5)
p.paragraph_format.space_after = Pt(3)
if bold_prefix:
rb = p.add_run(bold_prefix)
set_run_font(rb, bold=True)
run = p.add_run(text)
set_run_font(run)
else:
run = p.add_run(text)
set_run_font(run)
return p
def add_numbered(doc, text, bold_prefix=None):
p = doc.add_paragraph(style="List Number")
p.paragraph_format.left_indent = Cm(0.5)
p.paragraph_format.space_after = Pt(3)
if bold_prefix:
rb = p.add_run(bold_prefix)
set_run_font(rb, bold=True)
run = p.add_run(text)
set_run_font(run)
else:
run = p.add_run(text)
set_run_font(run)
return p
def add_table(doc, headers, rows, col_widths=None,
header_color="1F3864", alt_color="DCE6F1"):
table = doc.add_table(rows=1 + len(rows), cols=len(headers))
table.style = "Table Grid"
table.alignment = WD_TABLE_ALIGNMENT.CENTER
hdr_row = table.rows[0]
for i, hdr in enumerate(headers):
cell = hdr_row.cells[i]
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear"); shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), header_color)
tcPr.append(shd)
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = p.add_run(hdr)
set_run_font(run, bold=True, color=(255, 255, 255), size=11)
for r_idx, row_data in enumerate(rows):
row = table.rows[r_idx + 1]
fill = alt_color if r_idx % 2 == 0 else "FFFFFF"
for c_idx, cell_text in enumerate(row_data):
cell = row.cells[c_idx]
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear"); shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), fill)
tcPr.append(shd)
p = cell.paragraphs[0]
run = p.add_run(str(cell_text))
set_run_font(run, size=10)
if col_widths:
for row in table.rows:
for j, cell in enumerate(row.cells):
if j < len(col_widths):
cell.width = Cm(col_widths[j])
return table
def add_code(doc, code_text, lang_label=None):
"""Styled code block with optional language label."""
if lang_label:
lp = doc.add_paragraph()
lp.paragraph_format.space_after = Pt(0)
lp.paragraph_format.space_before = Pt(6)
lp.paragraph_format.left_indent = Cm(0.5)
lr = lp.add_run(f" {lang_label}")
set_run_font(lr, size=8, bold=True, color=(255, 255, 255))
lp_pPr = lp._p.get_or_add_pPr()
lshd = OxmlElement("w:shd")
lshd.set(qn("w:val"), "clear"); lshd.set(qn("w:color"), "auto")
lshd.set(qn("w:fill"), "1F3864")
lp_pPr.append(lshd)
p = doc.add_paragraph()
p.paragraph_format.left_indent = Cm(0.5)
p.paragraph_format.right_indent = Cm(0.5)
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(8)
pPr = p._p.get_or_add_pPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear"); shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), "F0F0F0")
pPr.append(shd)
run = p.add_run(code_text)
run.font.name = "Courier New"
run.font.size = Pt(8.5)
return p
def add_info_box(doc, title, text, box_color="E8F4FD", title_color=(0, 86, 179)):
"""Add a highlighted info/note box."""
p = doc.add_paragraph()
p.paragraph_format.left_indent = Cm(0.3)
p.paragraph_format.right_indent = Cm(0.3)
p.paragraph_format.space_before = Pt(4)
p.paragraph_format.space_after = Pt(8)
pPr = p._p.get_or_add_pPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear"); shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), box_color)
pPr.append(shd)
r1 = p.add_run(f"{title} ")
set_run_font(r1, bold=True, color=title_color)
r2 = p.add_run(text)
set_run_font(r2, size=10, italic=True)
def add_hr(doc, color="1F3864"):
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(2)
pPr = p._p.get_or_add_pPr()
pBdr = OxmlElement("w:pBdr")
bottom = OxmlElement("w:bottom")
bottom.set(qn("w:val"), "single"); bottom.set(qn("w:sz"), "6")
bottom.set(qn("w:space"), "1"); bottom.set(qn("w:color"), color)
pBdr.append(bottom); pPr.append(pBdr)
return p
def pb(doc): doc.add_page_break()
def sp(doc, n=1):
for _ in range(n): doc.add_paragraph()
# ══════════════════════════════════════════════════════════════════════════════
# BUILD DOCUMENT
# ══════════════════════════════════════════════════════════════════════════════
doc = Document()
set_doc_margins(doc)
style = doc.styles["Normal"]
style.font.name = "Calibri"
style.font.size = Pt(11)
# ── COVER PAGE ────────────────────────────────────────────────────────────────
sp(doc, 2)
t = doc.add_paragraph()
t.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = t.add_run("EyeGuard")
set_run_font(r, bold=True, size=28, color=(0, 51, 102))
t2 = doc.add_paragraph()
t2.alignment = WD_ALIGN_PARAGRAPH.CENTER
r2 = t2.add_run("Technical Development Guide")
set_run_font(r2, bold=True, size=18, color=(31, 56, 100))
sp(doc)
t3 = doc.add_paragraph()
t3.alignment = WD_ALIGN_PARAGRAPH.CENTER
r3 = t3.add_run("Flutter + Firebase — Full Setup, Architecture & Module Implementation")
set_run_font(r3, italic=True, size=13)
sp(doc, 2)
cover_rows = [
["Project", "EyeGuard — CVS Assessment & Management App"],
["Document Type", "Technical Development Guide"],
["Stack", "Flutter (Dart) + Firebase (Firestore, Auth, FCM, Storage)"],
["Version", "1.0"],
["Date", "July 2026"],
["Audience", "Student developers, Faculty mentor, SPARK reviewers"],
["Companion To", "EyeGuard SPARK Research Proposal (IMRAD)"],
]
ct = doc.add_table(rows=len(cover_rows), cols=2)
ct.style = "Table Grid"
ct.alignment = WD_TABLE_ALIGNMENT.CENTER
for i, (k, v) in enumerate(cover_rows):
fill = "DCE6F1" if i % 2 == 0 else "FFFFFF"
c0, c1 = ct.rows[i].cells[0], ct.rows[i].cells[1]
c0.width = Cm(4.5); c1.width = Cm(11)
for cell, txt, is_bold in [(c0, k, True), (c1, v, False)]:
tc = cell._tc; tcPr = tc.get_or_add_tcPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear"); shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), fill); tcPr.append(shd)
run = cell.paragraphs[0].add_run(txt)
set_run_font(run, size=11, bold=is_bold)
pb(doc)
# ── TABLE OF CONTENTS ─────────────────────────────────────────────────────────
add_heading(doc, "TABLE OF CONTENTS", level=1)
add_hr(doc)
toc = [
("1.", "Development Environment Setup", "3"),
("2.", "Project Structure & Folder Layout", "5"),
("3.", "Firebase Project Configuration", "6"),
("4.", "Authentication Module", "8"),
("5.", "Firestore Database Design", "10"),
("6.", "CVS-Q Module Implementation", "12"),
("7.", "Screen Time Tracker", "15"),
("8.", "20-20-20 & Blink Reminder Engine", "17"),
("9.", "Eye Exercise Video Module", "19"),
("10.", "Symptom Tracking & Charts", "21"),
("11.", "Push Notification System (FCM)", "23"),
("12.", "Security & Data Privacy", "25"),
("13.", "Testing Strategy", "26"),
("14.", "CI/CD & Deployment", "28"),
("15.", "Troubleshooting & Common Errors", "29"),
]
toc_table = doc.add_table(rows=len(toc), cols=3)
toc_table.style = "Table Grid"
for i, (num, title, pg) in enumerate(toc):
cells = toc_table.rows[i].cells
cells[0].width = Cm(1.2); cells[1].width = Cm(12.3); cells[2].width = Cm(1.5)
for cell, txt in zip(cells, [num, title, pg]):
run = cell.paragraphs[0].add_run(txt)
set_run_font(run, size=10, bold=(txt == num))
cells[2].paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.RIGHT
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 1 — DEVELOPMENT ENVIRONMENT SETUP
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "1. Development Environment Setup", level=1)
add_hr(doc)
add_info_box(doc, "Prerequisites:", "Complete all steps in order before writing any app code.")
add_heading(doc, "1.1 System Requirements", level=2)
sys_rows = [
["Operating System", "Windows 10/11 (64-bit), macOS 12+, or Ubuntu 20.04+"],
["RAM", "Minimum 8 GB (16 GB recommended for emulator performance)"],
["Storage", "Minimum 10 GB free (Flutter SDK + Android Studio + emulators)"],
["Processor", "Intel Core i5 / Apple M1 or equivalent"],
["Internet", "Required for Firebase SDK download and dependency resolution"],
]
add_table(doc, ["Requirement", "Specification"], sys_rows, col_widths=[4, 11])
sp(doc)
add_heading(doc, "1.2 Install Flutter SDK", level=2)
add_numbered(doc, "Download Flutter SDK from https://flutter.dev/docs/get-started/install")
add_numbered(doc, "Extract to a location without spaces, e.g. C:\\flutter or ~/flutter")
add_numbered(doc, "Add Flutter to PATH:")
add_code(doc, "# macOS / Linux — add to ~/.bashrc or ~/.zshrc\nexport PATH=\"$PATH:$HOME/flutter/bin\"\nsource ~/.bashrc\n\n# Windows — add C:\\flutter\\bin to System Environment Variables > PATH", "Shell")
add_numbered(doc, "Verify installation:")
add_code(doc, "flutter --version\n# Expected output:\n# Flutter 3.x.x channel stable\n# Dart 3.x.x", "Shell")
add_numbered(doc, "Run Flutter doctor to check dependencies:")
add_code(doc, "flutter doctor\n# All checkboxes should be green. Fix any [X] issues before proceeding.", "Shell")
add_heading(doc, "1.3 Install Android Studio", level=2)
add_numbered(doc, "Download from https://developer.android.com/studio")
add_numbered(doc, "During installation, select: Android SDK, Android SDK Platform, Android Virtual Device")
add_numbered(doc, "In Android Studio: Settings > SDK Manager > Install Android 13 (API 33) or higher")
add_numbered(doc, "Create a Virtual Device (emulator): Tools > Device Manager > Create Device > Pixel 6 > API 33")
add_numbered(doc, "Install Flutter and Dart plugins: Settings > Plugins > search 'Flutter' > Install")
add_heading(doc, "1.4 Install VS Code (Optional but Recommended)", level=2)
add_numbered(doc, "Download from https://code.visualstudio.com/")
add_numbered(doc, "Install extensions: Flutter, Dart, Firebase Explorer, GitLens")
add_numbered(doc, "VS Code is lighter than Android Studio for Dart/Flutter development")
add_heading(doc, "1.5 Install Node.js and Firebase CLI", level=2)
add_numbered(doc, "Download Node.js LTS from https://nodejs.org/")
add_numbered(doc, "Install Firebase CLI globally:")
add_code(doc, "npm install -g firebase-tools\n\n# Verify:\nfirebase --version", "Shell")
add_numbered(doc, "Log in to Firebase:")
add_code(doc, "firebase login\n# Opens browser for Google account authentication", "Shell")
add_heading(doc, "1.6 Install FlutterFire CLI", level=2)
add_code(doc, "dart pub global activate flutterfire_cli\n\n# Add to PATH if not already:\nexport PATH=\"$PATH:$HOME/.pub-cache/bin\" # macOS/Linux", "Shell")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 2 — PROJECT STRUCTURE
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "2. Create Project & Folder Structure", level=1)
add_hr(doc)
add_heading(doc, "2.1 Create the Flutter Project", level=2)
add_code(doc, "flutter create eyeguard\ncd eyeguard\n\n# Open in VS Code:\ncode .", "Shell")
add_heading(doc, "2.2 Recommended Folder Layout", level=2)
add_body(doc, "Organise the lib/ directory as follows for clean separation of concerns:")
add_code(doc, """\
eyeguard/
├── android/ # Android-specific config
├── ios/ # iOS-specific config
├── lib/
│ ├── main.dart # App entry point
│ ├── firebase_options.dart # Auto-generated by FlutterFire CLI
│ ├── app/
│ │ ├── app.dart # MaterialApp root widget
│ │ └── routes.dart # Named route definitions
│ ├── core/
│ │ ├── constants.dart # App-wide constants (colors, strings)
│ │ ├── theme.dart # ThemeData (light/dark)
│ │ └── utils.dart # Helper functions
│ ├── models/
│ │ ├── user_model.dart
│ │ ├── cvsq_response.dart
│ │ └── screen_time_log.dart
│ ├── services/
│ │ ├── auth_service.dart
│ │ ├── firestore_service.dart
│ │ ├── notification_service.dart
│ │ └── screen_time_service.dart
│ ├── providers/ # State management (Riverpod)
│ │ ├── auth_provider.dart
│ │ ├── cvsq_provider.dart
│ │ └── progress_provider.dart
│ └── screens/
│ ├── splash/
│ ├── auth/
│ ├── dashboard/
│ ├── cvsq/
│ ├── reminders/
│ ├── exercises/
│ ├── progress/
│ └── settings/
├── assets/
│ ├── images/
│ ├── icons/
│ └── videos/ # Local exercise video thumbnails
├── test/ # Unit + widget tests
├── pubspec.yaml
└── firebase.json
""", "Project Tree")
add_heading(doc, "2.3 pubspec.yaml — All Dependencies", level=2)
add_body(doc, "Replace the dependencies section of pubspec.yaml with the following:")
add_code(doc, """\
name: eyeguard
description: CVS Assessment and Management App
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# Firebase Core
firebase_core: ^3.1.0
firebase_auth: ^5.1.0
cloud_firestore: ^5.1.0
firebase_storage: ^12.1.0
firebase_messaging: ^15.0.0
firebase_analytics: ^11.0.0
# State Management
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
# Navigation
go_router: ^14.0.0
# Charts & Visualization
fl_chart: ^0.68.0
# Video Player
video_player: ^2.8.6
chewie: ^1.8.0
# Local Notifications
flutter_local_notifications: ^17.1.2
# Screen Time (Android)
usage_stats: ^1.3.0
# Permissions
permission_handler: ^11.3.1
# Shared Preferences (local storage)
shared_preferences: ^2.3.1
# HTTP & Utilities
http: ^1.2.1
intl: ^0.19.0
uuid: ^4.4.0
logger: ^2.4.0
# PDF Export
pdf: ^3.11.1
printing: ^5.12.0
# UI Utilities
cached_network_image: ^3.3.1
flutter_svg: ^2.0.10+1
lottie: ^3.1.2 # Animations (eye exercise visuals)
shimmer: ^3.0.0 # Loading skeletons
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.11
riverpod_generator: ^2.4.2
flutter_lints: ^4.0.0
mockito: ^5.4.4
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/icons/
""", "YAML")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 3 — FIREBASE CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "3. Firebase Project Configuration", level=1)
add_hr(doc)
add_heading(doc, "3.1 Create Firebase Project", level=2)
steps = [
"Go to https://console.firebase.google.com/",
"Click 'Add project' → name it 'eyeguard-app'",
"Disable Google Analytics for now (can enable later) → click 'Create project'",
"Once created, click 'Continue'",
]
for s in steps:
add_numbered(doc, s)
add_heading(doc, "3.2 Enable Firebase Services", level=2)
service_rows = [
["Authentication", "Build > Authentication > Get started > Enable Email/Password and Google"],
["Firestore", "Build > Firestore Database > Create database > Start in TEST mode"],
["Storage", "Build > Storage > Get started > Start in TEST mode"],
["Cloud Messaging", "Auto-enabled when Firebase is added to project"],
["Analytics", "Optional: Enable in Project Settings > Integrations"],
]
add_table(doc, ["Service", "Steps"], service_rows, col_widths=[4, 11])
sp(doc)
add_info_box(doc, "IMPORTANT:", "After development, change Firestore and Storage rules from TEST mode to production rules (covered in Section 12).")
add_heading(doc, "3.3 Connect Flutter App to Firebase (FlutterFire CLI)", level=2)
add_code(doc, """\
# Run from the root of your Flutter project:
flutterfire configure --project=eyeguard-app
# Select the platforms you want (android, ios):
# This auto-generates:
# - android/app/google-services.json
# - ios/Runner/GoogleService-Info.plist
# - lib/firebase_options.dart
""", "Shell")
add_heading(doc, "3.4 Initialize Firebase in main.dart", level=2)
add_code(doc, """\
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'firebase_options.dart';
import 'app/app.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(
// Wrap with ProviderScope for Riverpod state management
const ProviderScope(
child: EyeGuardApp(),
),
);
}
""", "Dart")
add_heading(doc, "3.5 Android: Add google-services plugin", level=2)
add_body(doc, "In android/build.gradle (project-level), add to the plugins block:")
add_code(doc, """\
// android/build.gradle
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.4.2'
}
}
""", "Gradle")
add_body(doc, "In android/app/build.gradle (app-level), add at the bottom:")
add_code(doc, """\
// android/app/build.gradle
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 21 // Required for Firebase
targetSdkVersion 34
}
}
""", "Gradle")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 4 — AUTHENTICATION
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "4. Authentication Module", level=1)
add_hr(doc)
add_heading(doc, "4.1 Auth Service", level=2)
add_code(doc, """\
// lib/services/auth_service.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Stream of auth state changes
Stream<User?> get authStateChanges => _auth.authStateChanges();
// Get current user
User? get currentUser => _auth.currentUser;
// Register with email/password
Future<UserCredential> registerWithEmail({
required String email,
required String password,
required String name,
required int age,
required String gender,
}) async {
final credential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
// Create user profile in Firestore
await _db.collection('users').doc(credential.user!.uid).set({
'name': name,
'email': email,
'age': age,
'gender': gender,
'createdAt': FieldValue.serverTimestamp(),
'deviceType': '',
'dailyScreenTimeGoalHours': 6,
});
return credential;
}
// Sign in with email/password
Future<UserCredential> signInWithEmail({
required String email,
required String password,
}) async {
return await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
}
// Sign out
Future<void> signOut() async => await _auth.signOut();
// Password reset
Future<void> sendPasswordReset(String email) async {
await _auth.sendPasswordResetEmail(email: email);
}
}
""", "Dart")
add_heading(doc, "4.2 Auth Provider (Riverpod)", level=2)
add_code(doc, """\
// lib/providers/auth_provider.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../services/auth_service.dart';
// Provider for AuthService instance
final authServiceProvider = Provider<AuthService>((ref) => AuthService());
// Stream provider that rebuilds UI on auth state changes
final authStateProvider = StreamProvider<User?>((ref) {
return ref.watch(authServiceProvider).authStateChanges;
});
""", "Dart")
add_heading(doc, "4.3 Login Screen (Simplified)", level=2)
add_code(doc, """\
// lib/screens/auth/login_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/auth_provider.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
final _emailCtrl = TextEditingController();
final _passwordCtrl = TextEditingController();
bool _loading = false;
Future<void> _login() async {
setState(() => _loading = true);
try {
await ref.read(authServiceProvider).signInWithEmail(
email: _emailCtrl.text.trim(),
password: _passwordCtrl.text.trim(),
);
// Navigation handled by authStateProvider listener in app.dart
} on FirebaseAuthException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.message ?? 'Login failed')),
);
} finally {
setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('EyeGuard', style: Theme.of(context)
.textTheme.headlineLarge),
const SizedBox(height: 32),
TextField(
controller: _emailCtrl,
decoration: const InputDecoration(labelText: 'Email'),
),
const SizedBox(height: 16),
TextField(
controller: _passwordCtrl,
obscureText: true,
decoration: const InputDecoration(labelText: 'Password'),
),
const SizedBox(height: 24),
_loading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _login,
child: const Text('Sign In'),
),
],
),
),
);
}
}
""", "Dart")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 5 — FIRESTORE DATABASE DESIGN
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "5. Firestore Database Design", level=1)
add_hr(doc)
add_heading(doc, "5.1 Collection Structure", level=2)
add_body(doc, "EyeGuard uses the following top-level Firestore collections:")
db_rows = [
["users", "One document per user; stores profile, demographics, settings"],
["cvsq_responses", "One document per CVS-Q submission; subcollection under each user"],
["screen_time_logs", "Daily screen time records; subcollection under each user"],
["reminder_logs", "Records of reminder taps/dismissals; subcollection under each user"],
["exercise_logs", "Records of exercise video completions; subcollection under each user"],
["exercises", "Master list of exercise videos (admin-managed, read-only for users)"],
]
add_table(doc, ["Collection", "Purpose"], db_rows, col_widths=[4.5, 10.5])
sp(doc)
add_heading(doc, "5.2 Document Schemas", level=2)
add_code(doc, """\
// ── users/{uid} ──────────────────────────────────────────────────────────────
{
"name": "string",
"email": "string",
"age": 22,
"gender": "Male | Female | Other",
"deviceType": "Laptop | Smartphone | Tablet | Mixed",
"dailyScreenTimeGoalHours": 6,
"reminderSettings": {
"twentyTwentyEnabled": true,
"twentyTwentyIntervalMinutes": 20,
"blinkEnabled": true,
"blinkIntervalMinutes": 5,
"exerciseReminderEnabled": true,
"exerciseReminderHour": 18
},
"fcmToken": "string",
"createdAt": "Timestamp",
"lastActiveAt": "Timestamp"
}
// ── users/{uid}/cvsq_responses/{responseId} ──────────────────────────────────
{
"submittedAt": "Timestamp",
"weekNumber": 1,
"totalScore": 14,
"cvsStatus": "Moderate CVS",
"riskLevel": "Moderate",
"referralNeeded": false,
"symptoms": [
{ "id": 1, "name": "Burning eyes", "frequency": 2, "intensity": 2, "score": 4 },
{ "id": 2, "name": "Itching eyes", "frequency": 1, "intensity": 1, "score": 1 },
// ... 14 more items
]
}
// ── users/{uid}/screen_time_logs/{date} ──────────────────────────────────────
{
"date": "2026-07-27",
"totalHours": 8.5,
"goalHours": 6,
"exceeded": true,
"appBreakdown": {
"chrome": 3.2,
"youtube": 2.1,
"other": 3.2
}
}
// ── users/{uid}/reminder_logs/{logId} ────────────────────────────────────────
{
"timestamp": "Timestamp",
"type": "20-20-20 | blink | exercise",
"action": "tapped | dismissed | ignored",
"sessionDate": "2026-07-27"
}
""", "Firestore JSON")
add_heading(doc, "5.3 Firestore Service Class", level=2)
add_code(doc, """\
// lib/services/firestore_service.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../models/cvsq_response.dart';
class FirestoreService {
final _db = FirebaseFirestore.instance;
final _auth = FirebaseAuth.instance;
String get _uid => _auth.currentUser!.uid;
// ── CVS-Q ─────────────────────────────────────────────────────────────────
Future<void> saveCvsqResponse(CvsqResponse response) async {
await _db
.collection('users')
.doc(_uid)
.collection('cvsq_responses')
.add(response.toMap());
}
Stream<List<CvsqResponse>> getCvsqHistory() {
return _db
.collection('users')
.doc(_uid)
.collection('cvsq_responses')
.orderBy('submittedAt', descending: false)
.snapshots()
.map((snap) =>
snap.docs.map((d) => CvsqResponse.fromMap(d.data())).toList());
}
// ── Screen Time ───────────────────────────────────────────────────────────
Future<void> saveScreenTimeLog(String date, double hours) async {
await _db
.collection('users')
.doc(_uid)
.collection('screen_time_logs')
.doc(date)
.set({'date': date, 'totalHours': hours,
'loggedAt': FieldValue.serverTimestamp()},
SetOptions(merge: true));
}
// ── Reminder Logs ─────────────────────────────────────────────────────────
Future<void> logReminderAction(String type, String action) async {
await _db
.collection('users')
.doc(_uid)
.collection('reminder_logs')
.add({
'timestamp': FieldValue.serverTimestamp(),
'type': type,
'action': action,
});
}
}
""", "Dart")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 6 — CVS-Q MODULE
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "6. CVS-Q Module Implementation", level=1)
add_hr(doc)
add_heading(doc, "6.1 CvsqResponse Model", level=2)
add_code(doc, """\
// lib/models/cvsq_response.dart
class CvsqSymptom {
final int id;
final String name;
int frequency; // 0=never, 1=sometimes, 2=often, 3=always
int intensity; // 1=mild, 2=moderate, 3=intense
CvsqSymptom({required this.id, required this.name,
this.frequency = 0, this.intensity = 1});
int get score => frequency == 0 ? 0 : frequency * intensity;
Map<String, dynamic> toMap() => {
'id': id, 'name': name,
'frequency': frequency, 'intensity': intensity, 'score': score,
};
}
class CvsqResponse {
final DateTime submittedAt;
final int weekNumber;
final List<CvsqSymptom> symptoms;
CvsqResponse({required this.submittedAt,
required this.weekNumber,
required this.symptoms});
// ── Computed properties ───────────────────────────────────────────────────
int get totalScore =>
symptoms.fold(0, (sum, s) => sum + s.score);
String get cvsStatus {
if (totalScore < 6) return 'No CVS';
if (totalScore <= 12) return 'Mild CVS';
if (totalScore <= 24) return 'Moderate CVS';
return 'Severe CVS';
}
String get riskLevel {
if (totalScore < 6) return 'Low';
if (totalScore <= 12) return 'Low-Moderate';
if (totalScore <= 24) return 'Moderate';
return 'High';
}
bool get referralNeeded => totalScore > 24;
// ── Factory / Serialization ───────────────────────────────────────────────
static List<CvsqSymptom> get defaultSymptoms => [
CvsqSymptom(id: 1, name: 'Burning eyes'),
CvsqSymptom(id: 2, name: 'Itching eyes'),
CvsqSymptom(id: 3, name: 'Eye dryness'),
CvsqSymptom(id: 4, name: 'Excessive tearing'),
CvsqSymptom(id: 5, name: 'Redness in the eyes'),
CvsqSymptom(id: 6, name: 'Blurred distant vision'),
CvsqSymptom(id: 7, name: 'Blurred near vision'),
CvsqSymptom(id: 8, name: 'Double vision'),
CvsqSymptom(id: 9, name: 'Slow focus shifting'),
CvsqSymptom(id: 10, name: 'Eye fatigue'),
CvsqSymptom(id: 11, name: 'Sensitivity to light'),
CvsqSymptom(id: 12, name: 'Colored rings around objects'),
CvsqSymptom(id: 13, name: 'Headache'),
CvsqSymptom(id: 14, name: 'Neck / shoulder / back pain'),
CvsqSymptom(id: 15, name: 'Eye strain'),
CvsqSymptom(id: 16, name: 'General fatigue'),
];
Map<String, dynamic> toMap() => {
'submittedAt': submittedAt.toIso8601String(),
'weekNumber': weekNumber,
'totalScore': totalScore,
'cvsStatus': cvsStatus,
'riskLevel': riskLevel,
'referralNeeded': referralNeeded,
'symptoms': symptoms.map((s) => s.toMap()).toList(),
};
factory CvsqResponse.fromMap(Map<String, dynamic> map) {
// ... deserialization logic
return CvsqResponse(
submittedAt: DateTime.parse(map['submittedAt']),
weekNumber: map['weekNumber'],
symptoms: [], // Populate from map['symptoms']
);
}
}
""", "Dart")
add_heading(doc, "6.2 CVS-Q Screen (Questionnaire UI)", level=2)
add_code(doc, """\
// lib/screens/cvsq/cvsq_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../models/cvsq_response.dart';
import '../../services/firestore_service.dart';
class CvsqScreen extends ConsumerStatefulWidget {
const CvsqScreen({super.key});
@override
ConsumerState<CvsqScreen> createState() => _CvsqScreenState();
}
class _CvsqScreenState extends ConsumerState<CvsqScreen> {
final List<CvsqSymptom> _symptoms = CvsqResponse.defaultSymptoms;
int _currentIndex = 0; // Step through symptoms one at a time
static const _freqLabels = ['Never', 'Sometimes', 'Often', 'Always'];
static const _intLabels = ['Mild', 'Moderate', 'Intense'];
void _next() {
if (_currentIndex < _symptoms.length - 1) {
setState(() => _currentIndex++);
} else {
_submit();
}
}
Future<void> _submit() async {
final response = CvsqResponse(
submittedAt: DateTime.now(),
weekNumber: 1, // Calculate based on enrollment date
symptoms: _symptoms,
);
await FirestoreService().saveCvsqResponse(response);
if (mounted) {
Navigator.pushReplacementNamed(
context, '/cvsq-result',
arguments: response,
);
}
}
@override
Widget build(BuildContext context) {
final symptom = _symptoms[_currentIndex];
return Scaffold(
appBar: AppBar(title: Text('CVS-Q ($_currentIndex/16)')),
body: Column(children: [
// Progress indicator
LinearProgressIndicator(
value: (_currentIndex + 1) / 16,
backgroundColor: Colors.grey[300],
color: Theme.of(context).primaryColor,
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Symptom ${symptom.id} of 16',
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 12),
Text(symptom.name,
style: Theme.of(context).textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 24),
// Frequency selector
Text('How often do you experience this?',
style: Theme.of(context).textTheme.bodyLarge),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: List.generate(4, (i) => ChoiceChip(
label: Text(_freqLabels[i]),
selected: symptom.frequency == i,
onSelected: (_) => setState(() => symptom.frequency = i),
)),
),
const SizedBox(height: 24),
// Intensity selector (only if frequency > 0)
if (symptom.frequency > 0) ...[
Text('How intense is it?',
style: Theme.of(context).textTheme.bodyLarge),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: List.generate(3, (i) => ChoiceChip(
label: Text(_intLabels[i]),
selected: symptom.intensity == i + 1,
onSelected: (_) =>
setState(() => symptom.intensity = i + 1),
)),
),
],
],
),
),
),
// Next / Submit button
Padding(
padding: const EdgeInsets.all(20),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _next,
child: Text(_currentIndex < 15 ? 'Next' : 'Submit & See Results'),
),
),
),
]),
);
}
}
""", "Dart")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 7 — SCREEN TIME TRACKER
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "7. Screen Time Tracker", level=1)
add_hr(doc)
add_heading(doc, "7.1 Android — UsageStatsManager", level=2)
add_info_box(doc, "Android Permission Required:", "PACKAGE_USAGE_STATS is a special permission that requires the user to grant it in System Settings. It CANNOT be auto-granted.")
add_code(doc, """\
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name=\"android.permission.PACKAGE_USAGE_STATS\"
tools:ignore=\"ProtectedPermissions\" />
""", "XML")
add_code(doc, """\
// lib/services/screen_time_service.dart
import 'package:usage_stats/usage_stats.dart';
import 'package:permission_handler/permission_handler.dart';
class ScreenTimeService {
// Request usage stats permission
Future<bool> requestPermission() async {
final status = await Permission.activityRecognition.request();
// For PACKAGE_USAGE_STATS, open settings manually:
await UsageStats.grantUsagePermission();
return await UsageStats.checkUsagePermission() ?? false;
}
// Get total screen time today in hours
Future<double> getTodayScreenTimeHours() async {
final now = DateTime.now();
final midnight = DateTime(now.year, now.month, now.day);
final List<UsageInfo> usageInfoList =
await UsageStats.queryUsageStats(midnight, now);
double totalMs = 0;
for (final info in usageInfoList) {
// Filter out system apps and self
final pkg = info.packageName ?? '';
if (!_isSystemApp(pkg) && pkg != 'com.eyeguard.app') {
totalMs += double.tryParse(info.totalTimeInForeground ?? '0') ?? 0;
}
}
return totalMs / (1000 * 60 * 60); // Convert ms to hours
}
// Get per-app breakdown
Future<Map<String, double>> getAppBreakdown() async {
final now = DateTime.now();
final midnight = DateTime(now.year, now.month, now.day);
final List<UsageInfo> usageInfoList =
await UsageStats.queryUsageStats(midnight, now);
final Map<String, double> breakdown = {};
for (final info in usageInfoList) {
final pkg = info.packageName ?? '';
if (!_isSystemApp(pkg)) {
final hours = (double.tryParse(
info.totalTimeInForeground ?? '0') ?? 0) / 3600000;
if (hours > 0.05) breakdown[pkg] = hours;
}
}
return breakdown;
}
bool _isSystemApp(String pkg) {
const systemPrefixes = [
'com.android', 'com.google.android', 'android', 'com.samsung.android'
];
return systemPrefixes.any((p) => pkg.startsWith(p));
}
}
""", "Dart")
add_heading(doc, "7.2 iOS Screen Time (Limitations)", level=2)
add_info_box(doc, "iOS Note:", "iOS Screen Time API (DeviceActivity) requires a ScreenTime entitlement from Apple, which is only available for parental control apps. Use a screen-time self-report widget as fallback.")
add_code(doc, """\
// lib/services/screen_time_service.dart (iOS fallback)
// Use SharedPreferences to let the user manually log screen time
// OR read from Health app if ScreenTime data is not available
Future<void> saveManualScreenTime(double hours) async {
final prefs = await SharedPreferences.getInstance();
final today = DateTime.now().toIso8601String().substring(0, 10);
await prefs.setDouble('screen_time_$today', hours);
}
Future<double> getManualScreenTime() async {
final prefs = await SharedPreferences.getInstance();
final today = DateTime.now().toIso8601String().substring(0, 10);
return prefs.getDouble('screen_time_$today') ?? 0.0;
}
""", "Dart")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 8 — REMINDER ENGINE
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "8. 20-20-20 & Blink Reminder Engine", level=1)
add_hr(doc)
add_heading(doc, "8.1 Setup flutter_local_notifications", level=2)
add_code(doc, """\
// lib/services/notification_service.dart
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'dart:async';
class NotificationService {
static final _plugin = FlutterLocalNotificationsPlugin();
static Timer? _twentyTwentyTimer;
static Timer? _blinkTimer;
// ── Initialization ────────────────────────────────────────────────────────
static Future<void> initialize() async {
const android = AndroidInitializationSettings('@mipmap/ic_launcher');
const ios = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
await _plugin.initialize(
const InitializationSettings(android: android, iOS: ios),
onDidReceiveNotificationResponse: _onNotificationTap,
);
// Create notification channels (Android 8+)
await _plugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(
const AndroidNotificationChannel(
'eyeguard_reminders',
'EyeGuard Reminders',
description: '20-20-20 and blink reminders',
importance: Importance.high,
playSound: true,
),
);
}
static void _onNotificationTap(NotificationResponse response) {
// Log tap action to Firestore
// FirestoreService().logReminderAction(response.payload ?? '', 'tapped');
}
// ── 20-20-20 Reminder ─────────────────────────────────────────────────────
static void start2020(int intervalMinutes) {
_twentyTwentyTimer?.cancel();
_twentyTwentyTimer = Timer.periodic(
Duration(minutes: intervalMinutes),
(_) => _show2020Notification(),
);
}
static void stop2020() => _twentyTwentyTimer?.cancel();
static Future<void> _show2020Notification() async {
await _plugin.show(
100,
'👁️ 20-20-20 Break Time!',
'Look at something 20 feet away for 20 seconds. Your eyes will thank you!',
NotificationDetails(
android: AndroidNotificationDetails(
'eyeguard_reminders',
'EyeGuard Reminders',
importance: Importance.high,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
styleInformation: BigTextStyleInformation(
'Look at something 20 feet (6 meters) away for 20 seconds. '
'This relaxes your ciliary muscle and reduces eye fatigue.',
),
),
iOS: const DarwinNotificationDetails(
presentAlert: true, presentSound: true,
),
),
payload: '20-20-20',
);
}
// ── Blink Reminder ────────────────────────────────────────────────────────
static void startBlinkReminder(int intervalMinutes) {
_blinkTimer?.cancel();
_blinkTimer = Timer.periodic(
Duration(minutes: intervalMinutes),
(_) => _showBlinkNotification(),
);
}
static void stopBlinkReminder() => _blinkTimer?.cancel();
static Future<void> _showBlinkNotification() async {
await _plugin.show(
200,
'😌 Blink Reminder',
'Take 5 slow, complete blinks right now to refresh your tear film.',
NotificationDetails(
android: AndroidNotificationDetails(
'eyeguard_reminders', 'EyeGuard Reminders',
importance: Importance.defaultImportance,
priority: Priority.defaultPriority,
),
iOS: const DarwinNotificationDetails(presentAlert: true),
),
payload: 'blink',
);
}
// ── Scheduled Weekly CVS-Q Reminder ──────────────────────────────────────
static Future<void> scheduleWeeklyCvsq() async {
await _plugin.zonedSchedule(
300,
'📋 Weekly CVS-Q Check-In',
"It's Monday! Take 3 minutes to assess your eye health this week.",
// Schedule next Monday at 9:00 AM
_nextMondayAt9am(),
NotificationDetails(
android: AndroidNotificationDetails(
'eyeguard_reminders', 'EyeGuard Reminders',
importance: Importance.high,
),
),
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime,
);
}
static TZDateTime _nextMondayAt9am() {
// Uses timezone package — implementation omitted for brevity
// Returns TZDateTime for next Monday 09:00 local time
throw UnimplementedError('Add timezone package and implement');
}
}
""", "Dart")
add_heading(doc, "8.2 AndroidManifest Permissions for Notifications", level=2)
add_code(doc, """\
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />
<uses-permission android:name=\"android.permission.VIBRATE\" />
<uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />
<uses-permission android:name=\"android.permission.SCHEDULE_EXACT_ALARM\" />
<!-- Required for background timers on older Android: -->
<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />
""", "XML")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 9 — EYE EXERCISE VIDEO MODULE
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "9. Eye Exercise Video Module", level=1)
add_hr(doc)
add_heading(doc, "9.1 Exercise Data Model", level=2)
add_code(doc, """\
// lib/models/exercise.dart
class Exercise {
final String id;
final String title;
final String category; // 'relaxation' | 'focus' | 'blinking' | 'vergence'
final String description;
final String clinicalPurpose;
final int durationSeconds;
final String videoUrl; // Firebase Storage URL or YouTube embed URL
final String thumbnailUrl;
final int difficultyLevel; // 1=easy, 2=medium, 3=hard
const Exercise({
required this.id, required this.title, required this.category,
required this.description, required this.clinicalPurpose,
required this.durationSeconds, required this.videoUrl,
required this.thumbnailUrl, required this.difficultyLevel,
});
// Hard-coded exercise list (mirrors app content)
static const List<Exercise> defaultLibrary = [
Exercise(
id: 'palming',
title: 'Palming',
category: 'relaxation',
description: 'Cup your warm palms over closed eyes without pressure.',
clinicalPurpose: 'Relaxes ciliary muscle; reduces photostress',
durationSeconds: 180,
videoUrl: 'https://storage.googleapis.com/eyeguard-app/exercises/palming.mp4',
thumbnailUrl: 'https://storage.googleapis.com/eyeguard-app/thumbs/palming.jpg',
difficultyLevel: 1,
),
Exercise(
id: 'near_far',
title: 'Near-Far Focus Shifting',
category: 'focus',
description: 'Alternate focus between a near object (30 cm) and a distant one (6 m).',
clinicalPurpose: 'Restores accommodative flexibility',
durationSeconds: 120,
videoUrl: 'https://storage.googleapis.com/eyeguard-app/exercises/near_far.mp4',
thumbnailUrl: 'https://storage.googleapis.com/eyeguard-app/thumbs/near_far.jpg',
difficultyLevel: 1,
),
// ... 5 more exercises
];
}
""", "Dart")
add_heading(doc, "9.2 Video Player Screen (Chewie + video_player)", level=2)
add_code(doc, """\
// lib/screens/exercises/exercise_player_screen.dart
import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:video_player/video_player.dart';
import '../../models/exercise.dart';
class ExercisePlayerScreen extends StatefulWidget {
final Exercise exercise;
const ExercisePlayerScreen({super.key, required this.exercise});
@override
State<ExercisePlayerScreen> createState() => _ExercisePlayerState();
}
class _ExercisePlayerState extends State<ExercisePlayerScreen> {
late VideoPlayerController _videoCtrl;
ChewieController? _chewieCtrl;
@override
void initState() {
super.initState();
_initPlayer();
}
Future<void> _initPlayer() async {
_videoCtrl = VideoPlayerController.networkUrl(
Uri.parse(widget.exercise.videoUrl),
);
await _videoCtrl.initialize();
_chewieCtrl = ChewieController(
videoPlayerController: _videoCtrl,
autoPlay: true,
looping: false,
showControls: true,
aspectRatio: 16 / 9,
);
// Listen for video completion to log exercise
_videoCtrl.addListener(() {
if (_videoCtrl.value.position >= _videoCtrl.value.duration) {
_logCompletion();
}
});
setState(() {});
}
Future<void> _logCompletion() async {
// Save to Firestore exercise_logs
// await FirestoreService().logExerciseCompletion(widget.exercise.id);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Exercise completed! Great work 👁️')),
);
}
}
@override
void dispose() {
_videoCtrl.dispose();
_chewieCtrl?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.exercise.title)),
body: Column(children: [
if (_chewieCtrl != null)
AspectRatio(
aspectRatio: 16 / 9,
child: Chewie(controller: _chewieCtrl!),
)
else
const Center(child: CircularProgressIndicator()),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.exercise.title,
style: Theme.of(context).textTheme.titleLarge
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text('Clinical Purpose: ${widget.exercise.clinicalPurpose}',
style: const TextStyle(color: Colors.teal)),
const SizedBox(height: 8),
Text(widget.exercise.description),
const SizedBox(height: 8),
Text('Duration: ${widget.exercise.durationSeconds ~/ 60} min '
'${widget.exercise.durationSeconds % 60} sec',
style: const TextStyle(fontWeight: FontWeight.w500)),
],
),
),
]),
);
}
}
""", "Dart")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 10 — SYMPTOM TRACKING & CHARTS
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "10. Symptom Tracking & Progress Charts", level=1)
add_hr(doc)
add_heading(doc, "10.1 CVS-Q Trend Line Chart (fl_chart)", level=2)
add_code(doc, """\
// lib/screens/progress/progress_screen.dart
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/progress_provider.dart';
class ProgressScreen extends ConsumerWidget {
const ProgressScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final cvsqHistory = ref.watch(cvsqHistoryProvider);
return Scaffold(
appBar: AppBar(title: const Text('My Progress')),
body: cvsqHistory.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('Error: $e')),
data: (responses) {
if (responses.isEmpty) {
return const Center(
child: Text('Complete your first CVS-Q to see progress here.'));
}
final spots = responses.asMap().entries.map((e) =>
FlSpot(e.key.toDouble(), e.value.totalScore.toDouble()),
).toList();
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(children: [
// CVS-Q Score Line Chart
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(children: [
Text('CVS-Q Score Over Time',
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
SizedBox(
height: 220,
child: LineChart(
LineChartData(
minY: 0, maxY: 48,
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
horizontalInterval: 6,
getDrawingHorizontalLine: (_) =>
FlLine(color: Colors.grey[300]!, strokeWidth: 1),
),
// Reference lines at score=6 and score=24
extraLinesData: ExtraLinesData(
horizontalLines: [
HorizontalLine(y: 6, color: Colors.orange,
strokeWidth: 1.5, dashArray: [5, 3],
label: HorizontalLineLabel(
show: true, labelResolver: (_) => 'Mild (6)'),
),
HorizontalLine(y: 24, color: Colors.red,
strokeWidth: 1.5, dashArray: [5, 3],
label: HorizontalLineLabel(
show: true, labelResolver: (_) => 'Severe (24)'),
),
],
),
lineBarsData: [
LineChartBarData(
spots: spots,
isCurved: true,
color: Colors.blue,
barWidth: 3,
belowBarData: BarAreaData(
show: true,
color: Colors.blue.withOpacity(0.15),
),
dotData: FlDotData(show: true),
),
],
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (val, meta) =>
Text('W${val.toInt() + 1}',
style: const TextStyle(fontSize: 10)),
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(showTitles: true,
interval: 6, reservedSize: 30),
),
),
),
),
),
]),
),
),
]),
);
},
),
);
}
}
""", "Dart")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 11 — FCM (Cloud Messaging)
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "11. Push Notification System (FCM)", level=1)
add_hr(doc)
add_heading(doc, "11.1 FCM Setup in Flutter", level=2)
add_code(doc, """\
// lib/services/fcm_service.dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
// Background message handler — MUST be a top-level function
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print('Background FCM message: \${message.notification?.title}');
}
class FcmService {
static final _fcm = FirebaseMessaging.instance;
static Future<void> initialize() async {
// Register background handler
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
// Request permission (iOS)
await _fcm.requestPermission(
alert: true, announcement: false, badge: true,
carPlay: false, criticalAlert: false, provisional: false, sound: true,
);
// Get token and save to Firestore
final token = await _fcm.getToken();
if (token != null) {
// await FirestoreService().saveUserFcmToken(token);
print('FCM Token: \$token');
}
// Listen for foreground messages
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// Show local notification while app is open
_showLocalNotificationFromFcm(message);
});
}
static Future<void> _showLocalNotificationFromFcm(RemoteMessage message) async {
final notification = message.notification;
if (notification == null) return;
// Use flutter_local_notifications to show banner
// (see NotificationService in Section 8)
}
// Subscribe to topic (for broadcast reminders)
static Future<void> subscribeToTopic(String topic) async {
await _fcm.subscribeToTopic(topic);
}
}
""", "Dart")
add_heading(doc, "11.2 Android Notification Icon Setup", level=2)
add_body(doc, "Place a transparent PNG notification icon at:")
add_code(doc, """\
android/app/src/main/res/drawable/ic_notification.png
# Size: 24x24 dp, white/transparent only (Android material design)
# Reference in AndroidManifest.xml:
<meta-data
android:name=\"com.google.firebase.messaging.default_notification_icon\"
android:resource=\"@drawable/ic_notification\" />
<meta-data
android:name=\"com.google.firebase.messaging.default_notification_color\"
android:resource=\"@color/colorPrimary\" />
""", "Shell/XML")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 12 — SECURITY
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "12. Security & Data Privacy", level=1)
add_hr(doc)
add_heading(doc, "12.1 Firestore Security Rules", level=2)
add_info_box(doc, "Critical:", "Deploy these rules BEFORE going live. Default TEST mode allows all reads/writes from anyone.")
add_code(doc, """\
// Firebase Console > Firestore > Rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users: read/write only own document
match /users/{uid} {
allow read, write: if request.auth != null
&& request.auth.uid == uid;
// CVS-Q responses: authenticated owner only
match /cvsq_responses/{responseId} {
allow read, write: if request.auth != null
&& request.auth.uid == uid;
}
// Screen time logs: owner only
match /screen_time_logs/{logId} {
allow read, write: if request.auth != null
&& request.auth.uid == uid;
}
// Reminder logs: owner only
match /reminder_logs/{logId} {
allow read, write: if request.auth != null
&& request.auth.uid == uid;
}
}
// Exercises (master list): authenticated read, admin write only
match /exercises/{exerciseId} {
allow read: if request.auth != null;
allow write: if request.auth != null
&& request.auth.token.admin == true;
}
}
}
""", "Firestore Rules")
add_heading(doc, "12.2 Firebase Storage Rules", level=2)
add_code(doc, """\
// Firebase Console > Storage > Rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// Exercise videos and thumbnails: authenticated read only
match /exercises/{allPaths=**} {
allow read: if request.auth != null;
allow write: if request.auth != null
&& request.auth.token.admin == true;
}
// No other paths accessible
match /{allPaths=**} {
allow read, write: if false;
}
}
}
""", "Firebase Rules")
add_heading(doc, "12.3 Data Privacy Compliance Checklist", level=2)
privacy_rows = [
["No PII in CVS-Q data", "CVS-Q responses use UID only; no name/email stored in response documents"],
["Token storage", "FCM tokens stored in Firestore, not in app logs"],
["Password security", "Firebase Auth handles hashing; never store passwords in app"],
["Logout on inactivity", "Auto sign-out after 30 min idle (configurable)"],
["Data export", "Users can download their data as PDF from Progress screen"],
["Account deletion", "In-app delete account deletes all Firestore + Auth records"],
["Consent logging", "Consent timestamp stored in users/{uid}.consentTimestamp"],
["HTTPS only", "All Firebase SDK connections use TLS 1.3"],
]
add_table(doc, ["Item", "Implementation"], privacy_rows, col_widths=[5, 10])
sp(doc)
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 13 — TESTING
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "13. Testing Strategy", level=1)
add_hr(doc)
add_heading(doc, "13.1 Testing Pyramid", level=2)
test_rows = [
["Unit Tests", "CvsqResponse scoring logic, ScreenTimeService calculations",
"flutter test", "Fast, no emulator"],
["Widget Tests", "CvsqScreen UI, ProgressScreen chart rendering",
"flutter test", "Renders widget tree"],
["Integration Tests", "Full login → CVS-Q → result flow",
"flutter test integration_test/", "Requires running emulator"],
["Manual UAT", "All 8 app screens with real user cohort",
"TestFlight / Firebase App Distribution", "Pre-launch QA"],
]
add_table(doc, ["Test Type", "Coverage", "Command", "Notes"], test_rows,
col_widths=[3.5, 5.5, 4, 2])
sp(doc)
add_heading(doc, "13.2 Unit Test: CVS-Q Scoring", level=2)
add_code(doc, """\
// test/cvsq_scoring_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:eyeguard/models/cvsq_response.dart';
void main() {
group('CvsqResponse scoring', () {
test('Score=0 when all frequencies are 0 (No CVS)', () {
final symptoms = CvsqResponse.defaultSymptoms;
for (var s in symptoms) { s.frequency = 0; }
final response = CvsqResponse(
submittedAt: DateTime.now(), weekNumber: 1, symptoms: symptoms);
expect(response.totalScore, 0);
expect(response.cvsStatus, 'No CVS');
expect(response.referralNeeded, false);
});
test('Score=6 classifies as Mild CVS', () {
final symptoms = CvsqResponse.defaultSymptoms;
symptoms[0].frequency = 2; symptoms[0].intensity = 3; // score=6
final response = CvsqResponse(
submittedAt: DateTime.now(), weekNumber: 1, symptoms: symptoms);
expect(response.totalScore, 6);
expect(response.cvsStatus, 'Mild CVS');
});
test('Score>24 triggers referral flag', () {
final symptoms = CvsqResponse.defaultSymptoms;
for (var s in symptoms) {
s.frequency = 3; s.intensity = 3; // max score
}
final response = CvsqResponse(
submittedAt: DateTime.now(), weekNumber: 1, symptoms: symptoms);
expect(response.totalScore, 144);
expect(response.cvsStatus, 'Severe CVS');
expect(response.referralNeeded, true);
});
test('Frequency=0 item always scores 0 regardless of intensity', () {
final s = CvsqSymptom(id: 1, name: 'Test');
s.frequency = 0; s.intensity = 3;
expect(s.score, 0);
});
});
}
""", "Dart")
add_code(doc, "flutter test test/cvsq_scoring_test.dart --coverage", "Shell")
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 14 — CI/CD
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "14. CI/CD & Deployment", level=1)
add_hr(doc)
add_heading(doc, "14.1 GitHub Actions Workflow", level=2)
add_code(doc, """\
# .github/workflows/flutter_ci.yml
name: EyeGuard CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.22.0'
channel: 'stable'
- name: Install dependencies
run: flutter pub get
- name: Analyze code
run: flutter analyze
- name: Run unit tests
run: flutter test --coverage
- name: Build APK (debug)
run: flutter build apk --debug
- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: eyeguard-debug-apk
path: build/app/outputs/flutter-apk/app-debug.apk
""", "YAML")
add_heading(doc, "14.2 Firebase App Distribution (Beta Testing)", level=2)
add_code(doc, """\
# Build release APK
flutter build apk --release
# Distribute to testers via Firebase App Distribution
firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk \\
--app YOUR_FIREBASE_APP_ID \\
--groups testers \\
--release-notes \"Week 4 beta: CVS-Q + reminders complete\"
""", "Shell")
add_heading(doc, "14.3 Play Store / App Store Release", level=2)
deploy_rows = [
["Android", "flutter build appbundle --release",
"Upload .aab to Google Play Console > Internal Testing"],
["iOS", "flutter build ipa --release",
"Use Xcode Organizer or fastlane to upload to App Store Connect"],
]
add_table(doc, ["Platform", "Build Command", "Release Step"], deploy_rows,
col_widths=[2.5, 6, 6.5])
sp(doc)
pb(doc)
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 15 — TROUBLESHOOTING
# ═══════════════════════════════════════════════════════════════════════════════
add_heading(doc, "15. Troubleshooting & Common Errors", level=1)
add_hr(doc)
errors = [
("google-services.json not found",
"Run: flutterfire configure again. Ensure android/app/google-services.json exists."),
("FirebaseException: permission-denied",
"Check Firestore Security Rules (Section 12.1). Ensure user is authenticated."),
("UsageStats returns empty list (Android)",
"The user must manually grant Usage Access in Settings > Apps > Special app access > Usage Access."),
("Notifications not appearing on Android 13+",
"POST_NOTIFICATIONS permission must be requested at runtime on Android 13+. "
"Use permission_handler: await Permission.notification.request();"),
("VideoPlayer shows black screen on iOS",
"Add NSAppTransportSecurity > NSAllowsArbitraryLoads to ios/Runner/Info.plist "
"if using HTTP video URLs. Prefer HTTPS Firebase Storage URLs."),
("flutter pub get fails with dependency conflict",
"Run: flutter pub upgrade --major-versions. Check pubspec.yaml version constraints."),
("Firebase Auth sign-in hangs on emulator",
"Ensure the emulator has Google Play Services. Use Pixel_6_API_33 or newer emulator image."),
("fl_chart renders empty chart",
"Ensure spots list is not empty. Check that data is loaded before chart widget builds."),
("Dart: Bad state: Stream already listened to",
"Use StreamProvider (Riverpod) which handles multi-listen. Do not use StreamBuilder with "
"a Riverpod stream directly."),
("iOS build error: Signing certificate not found",
"Open ios/Runner.xcworkspace in Xcode > Signing & Capabilities > select your Apple team."),
]
err_rows = [[e[0], e[1]] for e in errors]
add_table(doc, ["Error / Issue", "Solution"], err_rows, col_widths=[5.5, 9.5])
sp(doc)
pb(doc)
# ── QUICK REFERENCE CARD ─────────────────────────────────────────────────────
add_heading(doc, "QUICK REFERENCE — Essential Commands", level=1)
add_hr(doc)
cmd_rows = [
["flutter create eyeguard", "Create new project"],
["flutter pub get", "Install all pubspec.yaml dependencies"],
["flutter run", "Run on connected device/emulator"],
["flutter run -d chrome", "Run as web app in browser"],
["flutter build apk", "Build Android debug APK"],
["flutter build apk --release", "Build Android release APK"],
["flutter build appbundle --release", "Build Android App Bundle for Play Store"],
["flutter build ios --release", "Build iOS release (macOS required)"],
["flutter test", "Run all unit and widget tests"],
["flutter test --coverage", "Run tests with coverage report"],
["flutter analyze", "Static code analysis"],
["flutter clean", "Delete build cache (fixes most weird errors)"],
["flutter doctor", "Check development environment"],
["flutterfire configure", "Connect Firebase to Flutter app"],
["firebase deploy --only firestore:rules", "Deploy Firestore security rules"],
["firebase deploy --only storage", "Deploy Storage security rules"],
["dart pub global activate flutterfire_cli", "Install FlutterFire CLI"],
["flutter pub upgrade --major-versions", "Upgrade all packages to latest"],
]
add_table(doc, ["Command", "Purpose"], cmd_rows, col_widths=[8, 7])
sp(doc)
# ── FINAL NOTES ───────────────────────────────────────────────────────────────
add_heading(doc, "Developer Notes", level=2)
notes = [
"Always run flutter clean followed by flutter pub get after changing pubspec.yaml.",
"Use flutter_riverpod for all state management. Avoid setState() outside small, isolated widgets.",
"Never commit google-services.json or GoogleService-Info.plist to a public repository. Add to .gitignore.",
"Keep the CVS-Q scoring algorithm in a separate, well-tested service class. It is the clinical core of the app.",
"Use firebase emulators:start for local development to avoid consuming Firestore quota.",
"Test on a real physical device before any user study deployment — emulators do not accurately simulate "
"notification delivery or screen time APIs.",
"Document all Firebase collection paths in a constants.dart file. Never hardcode path strings inline.",
]
for n in notes:
add_bullet(doc, n)
sp(doc)
add_info_box(doc, "Support:", "For Firebase-specific issues, consult https://firebase.google.com/docs. "
"For Flutter issues, consult https://flutter.dev/docs or https://stackoverflow.com/questions/tagged/flutter")
# ── SAVE ──────────────────────────────────────────────────────────────────────
output_path = "/tmp/workspace/eyeguard-proposal/EyeGuard_Technical_Development_Guide.docx"
doc.save(output_path)
print(f"SUCCESS: Saved to {output_path}")
python3 /tmp/workspace/eyeguard-proposal/build_tech_guide.py 2>&1
python3 -c " from docx import Document import os doc = Document('/tmp/workspace/eyeguard-proposal/EyeGuard_Technical_Development_Guide.docx') headings = [p.text for p in doc.paragraphs if p.style.name.startswith('Heading')] print('HEADINGS:') for h in headings: print(' -', h) print() print('Total paragraphs:', len(doc.paragraphs)) print('Total tables:', len(doc.tables)) size = os.path.getsize('/tmp/workspace/eyeguard-proposal/EyeGuard_Technical_Development_Guide.docx') print(f'File size: {size/1024:.1f} KB') # Also list both files print() print('All files in workspace:') for f in os.listdir('/tmp/workspace/eyeguard-proposal/'): sz = os.path.getsize(f'/tmp/workspace/eyeguard-proposal/{f}') print(f' {f} ({sz/1024:.1f} KB)') "
EyeGuard Technical Development Guide
Word Document · DOCX
EyeGuard SPARK Research Proposal
Word Document · DOCX
| # | Section | What's Inside |
|---|---|---|
| 1 | Development Environment Setup | System requirements table, Flutter SDK install + PATH config, Android Studio setup (API 33), VS Code + extensions, Node.js + Firebase CLI, FlutterFire CLI |
| 2 | Project Structure | flutter create command, full lib/ folder tree with every directory explained, complete pubspec.yaml with all 28 dependencies (Firebase, Riverpod, fl_chart, Chewie, video_player, flutter_local_notifications, usage_stats, pdf, lottie, and more) |
| 3 | Firebase Configuration | Step-by-step Firebase Console setup, enable 6 services table, flutterfire configure command, main.dart Firebase initialization with Riverpod, Gradle plugin setup for Android |
| 4 | Authentication Module | Full AuthService class (register, sign in, sign out, password reset), AuthProvider Riverpod stream, complete LoginScreen widget |
| 5 | Firestore Database Design | 6-collection structure table, all document schemas (users, cvsq_responses, screen_time_logs, reminder_logs), FirestoreService class with CRUD methods |
| 6 | CVS-Q Module | CvsqSymptom + CvsqResponse models with all 16 symptoms hardcoded, scoring logic (frequency × intensity), cvsStatus / riskLevel / referralNeeded computed properties, full CvsqScreen UI with step-through questionnaire, ChoiceChip selectors, progress LinearProgressIndicator |
| 7 | Screen Time Tracker | Android UsageStatsManager implementation, permission request flow, per-app breakdown, iOS fallback with SharedPreferences manual logging |
| 8 | Reminder Engine | Complete NotificationService with Timer.periodic for 20-20-20 (every 20 min) and blink reminders (every 4-5 min), scheduled weekly CVS-Q notification, AndroidManifest.xml permissions |
| 9 | Eye Exercise Video Module | Exercise model with full library of 7 exercises, VideoPlayerController + ChewieController setup, completion detection + Firestore log, exercise detail UI |
| 10 | Symptom Tracking & Charts | fl_chart LineChart with reference lines at score=6 (mild) and score=24 (severe), colour-coded danger zones, week labels on X-axis, Riverpod stream provider integration |
| 11 | FCM Push Notifications | Background message handler, iOS permission request, FCM token → Firestore, foreground message listener, AndroidManifest.xml notification icon metadata |
| 12 | Security & Privacy | Production Firestore rules (user-scoped, owner-only), Firebase Storage rules, 8-item privacy compliance checklist (no PII, HTTPS, logout, data export, account deletion) |
| 13 | Testing Strategy | Testing pyramid table, complete unit test file for CVS-Q scoring (4 test cases), coverage command |
| 14 | CI/CD & Deployment | Full GitHub Actions YAML workflow (analyze → test → build APK), Firebase App Distribution beta release command, Play Store / App Store build commands table |
| 15 | Troubleshooting | 10 most common errors with exact solutions (permission-denied, empty UsageStats, black video screen, Signing certificate, etc.) |
| QR | Quick Reference Card | 18 essential Flutter/Firebase commands with descriptions |