File size: 11,820 Bytes
cd7bed1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
"""
FocusTrack - Page 4: Settings
Configure tracker behavior, categories, and data management.
"""

import streamlit as st
import json
import shutil
from datetime import datetime
from pathlib import Path

from ui_utils import privacy_badge, page_header, CATEGORY_COLORS, get_db

DB_PATH = Path(__file__).parent.parent / "data" / "activity.db"


def render():
    db = get_db()
    privacy_badge()
    page_header("Settings", "Configure FocusTrack to your workflow")

    settings = db.get_all_settings()

    tab1, tab2, tab3, tab4 = st.tabs(["βš™οΈ Tracker", "🏷️ Categories", "πŸ—„οΈ Data & Backup", "🎨 Appearance"])

    # ─── Tab 1: Tracker Settings ──────────────────────────────────────────────
    with tab1:
        st.markdown("#### Tracking Behavior")

        col1, col2 = st.columns(2)
        with col1:
            idle_threshold = st.number_input(
                "Idle threshold (seconds)",
                min_value=30, max_value=3600,
                value=int(settings.get("idle_threshold_seconds", 300)),
                step=30,
                help="User is considered idle after this many seconds without mouse/keyboard activity."
            )

            heartbeat = st.number_input(
                "Heartbeat interval (seconds)",
                min_value=1, max_value=60,
                value=int(settings.get("heartbeat_interval", 5)),
                step=1,
                help="How often activity is logged. Lower = more granular, higher = less CPU."
            )

        with col2:
            ignored_raw = settings.get("ignored_apps", '[]')
            try:
                ignored_list = json.loads(ignored_raw)
            except Exception:
                ignored_list = []

            ignored_text = st.text_area(
                "Ignored apps (one per line)",
                value="\n".join(ignored_list),
                height=120,
                help="Apps listed here will not be tracked."
            )

            auto_start = st.toggle(
                "Auto-start tracker on launch",
                value=settings.get("auto_start", "false") == "true"
            )

        if st.button("πŸ’Ύ Save Tracker Settings", use_container_width=False):
            db.set_setting("idle_threshold_seconds", str(idle_threshold))
            db.set_setting("heartbeat_interval", str(heartbeat))
            db.set_setting("auto_start", "true" if auto_start else "false")
            new_ignored = [a.strip() for a in ignored_text.strip().split("\n") if a.strip()]
            db.set_setting("ignored_apps", json.dumps(new_ignored))
            st.toast("βœ… Tracker settings saved!", icon="βœ…")
            st.rerun()

        st.markdown("---")
        st.markdown("#### Tracker Status")

        status = db.get_setting("tracker_running", "true")
        status_map = {"true": ("🟒 Running", "#10b981"), "paused": ("🟑 Paused", "#f59e0b"), "false": ("πŸ”΄ Stopped", "#f43f5e")}
        label, color = status_map.get(status, ("Unknown", "#6366f1"))
        st.markdown(
            f"<span style='color:{color}; font-size:1rem; font-weight:600;'>{label}</span>",
            unsafe_allow_html=True
        )

        col_s1, col_s2, col_s3 = st.columns([1, 1, 1])
        with col_s1:
            if st.button("β–Ά Start", use_container_width=True):
                db.set_setting("tracker_running", "true")
                st.toast("Tracker started")
                st.rerun()
        with col_s2:
            if st.button("⏸ Pause", use_container_width=True):
                db.set_setting("tracker_running", "paused")
                st.toast("Tracker paused")
                st.rerun()
        with col_s3:
            if st.button("⏹ Stop", use_container_width=True):
                db.set_setting("tracker_running", "false")
                st.toast("Tracker stopped")
                st.rerun()

    # ─── Tab 2: Categories ────────────────────────────────────────────────────
    with tab2:
        st.markdown("#### Category Rules")
        st.markdown("<p style='color:#9090b0; font-size:0.85rem;'>Define which apps and keywords map to which category. The tracker uses these rules to auto-categorize activity.</p>", unsafe_allow_html=True)

        cats = db.get_categories()

        for cat in cats:
            with st.expander(f"{'●'} {cat['name'].title()}", expanded=False):
                col_n, col_c = st.columns([3, 1])
                with col_n:
                    cat_name = st.text_input("Name", value=cat["name"], key=f"cname_{cat['id']}")
                with col_c:
                    cat_color = st.color_picker("Color", value=cat["color"], key=f"ccolor_{cat['id']}")

                try:
                    kw_list = json.loads(cat["keywords"] or "[]")
                    app_list = json.loads(cat["apps"] or "[]")
                except Exception:
                    kw_list, app_list = [], []

                col_k, col_a = st.columns(2)
                with col_k:
                    kw_text = st.text_area(
                        "Keywords (one per line)",
                        value="\n".join(kw_list),
                        height=100,
                        key=f"kw_{cat['id']}",
                        help="These words are matched against app name and window title."
                    )
                with col_a:
                    app_text = st.text_area(
                        "App names (one per line)",
                        value="\n".join(app_list),
                        height=100,
                        key=f"apps_{cat['id']}",
                        help="Partial matches against the process/app name."
                    )

                col_save, col_del = st.columns([1, 1])
                with col_save:
                    if st.button("πŸ’Ύ Save", key=f"save_cat_{cat['id']}", use_container_width=True):
                        new_kw  = [k.strip() for k in kw_text.strip().split("\n") if k.strip()]
                        new_app = [a.strip() for a in app_text.strip().split("\n") if a.strip()]
                        db.upsert_category(cat_name, cat_color, new_kw, new_app)
                        st.toast(f"βœ… '{cat_name}' saved")
                        st.rerun()
                with col_del:
                    if cat["name"] not in ("idle", "uncategorized"):
                        if st.button("πŸ—‘ Delete", key=f"del_cat_{cat['id']}", use_container_width=True):
                            db.delete_category(cat["name"])
                            st.toast(f"Deleted '{cat['name']}'")
                            st.rerun()

        st.markdown("---")
        st.markdown("#### Add New Category")
        nc1, nc2, nc3 = st.columns([2, 1, 1])
        with nc1:
            new_cat_name = st.text_input("Category name", placeholder="e.g. learning")
        with nc2:
            new_cat_color = st.color_picker("Color", value="#6366f1")
        with nc3:
            st.markdown("<div style='margin-top:28px'></div>", unsafe_allow_html=True)
            if st.button("βž• Add Category", use_container_width=True):
                if new_cat_name.strip():
                    db.upsert_category(new_cat_name.strip(), new_cat_color, [], [])
                    st.toast(f"βœ… Added '{new_cat_name}'")
                    st.rerun()

    # ─── Tab 3: Data & Backup ─────────────────────────────────────────────────
    with tab3:
        st.markdown("#### Database Info")

        db_size = db.get_db_size_mb()
        from datetime import date
        from datetime import datetime as dt2
        from ui_utils import get_date_range
        start, end = get_date_range("Last 30 days")
        count = db.get_activity_count(start, end)

        col_i1, col_i2, col_i3 = st.columns(3)
        with col_i1:
            st.metric("Database Size", f"{db_size:.2f} MB")
        with col_i2:
            st.metric("Records (30d)", f"{count:,}")
        with col_i3:
            st.metric("DB Location", "Local only βœ…")

        st.code(str(DB_PATH), language=None)

        st.markdown("---")
        st.markdown("#### Backup Database")
        col_b1, col_b2 = st.columns([1, 2])
        with col_b1:
            if st.button("πŸ“¦ Download Backup", use_container_width=True):
                if DB_PATH.exists():
                    with open(DB_PATH, "rb") as f:
                        data = f.read()
                    st.download_button(
                        "πŸ’Ύ Save activity.db",
                        data,
                        file_name=f"focustrack_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.db",
                        mime="application/octet-stream",
                        use_container_width=True,
                    )

        st.markdown("---")
        st.markdown("#### Restore Database")
        uploaded = st.file_uploader("Upload a backup .db file", type=["db"])
        if uploaded:
            if st.button("⚠️ Restore from backup (overwrites current data)"):
                DB_PATH.write_bytes(uploaded.read())
                if "db" in st.session_state:
                    del st.session_state["db"]
                st.toast("βœ… Database restored! Please refresh the page.", icon="βœ…")

        st.markdown("---")
        st.markdown("#### Danger Zone")
        st.markdown(
            "<div style='background:#1a0a0f; border:1px solid #4a1a2a; border-radius:12px; padding:1.25rem; margin-bottom:1rem;'>"
            "<p style='color:#f43f5e; font-weight:600; margin:0 0 8px;'>⚠️ Clear All Data</p>"
            "<p style='color:#9090b0; font-size:0.85rem; margin:0;'>This permanently deletes all tracked activity. This action cannot be undone.</p>"
            "</div>",
            unsafe_allow_html=True,
        )
        confirm = st.text_input("Type DELETE to confirm")
        if st.button("πŸ—‘ Clear All Activity Data", type="secondary"):
            if confirm == "DELETE":
                db.clear_all_data()
                st.toast("All data cleared.")
                st.rerun()
            else:
                st.error("Please type DELETE to confirm.")

    # ─── Tab 4: Appearance ────────────────────────────────────────────────────
    with tab4:
        st.markdown("#### Theme")
        current_theme = db.get_setting("theme", "dark")
        theme = st.radio(
            "Color mode",
            ["dark", "light"],
            index=0 if current_theme == "dark" else 1,
            horizontal=True,
        )
        if theme != current_theme:
            db.set_setting("theme", theme)
            st.toast("Theme updated β€” refresh to apply.")
            st.rerun()

        st.markdown("---")
        st.markdown("#### About FocusTrack")
        st.markdown("""
        <div style="background:#16161f; border:1px solid #2a2a3a; border-radius:12px; padding:1.5rem;">
            <h3 style="font-family:'Syne',sans-serif; margin:0 0 8px; color:#f0f0ff;">FocusTrack v1.0</h3>
            <p style="color:#9090b0; font-size:0.85rem; margin:0 0 4px;">100% local, privacy-first productivity tracker.</p>
            <p style="color:#9090b0; font-size:0.85rem; margin:0 0 4px;">Built with Python 3.11+ Β· Streamlit Β· SQLite Β· Plotly</p>
            <p style="color:#5a5a7a; font-size:0.8rem; margin:0;">No internet β€’ No accounts β€’ No cloud β€’ Your data never leaves your device.</p>
        </div>
        """, unsafe_allow_html=True)