Files
overtime-guard/b4a_orologio_marcatempo.b4a

1397 lines
44 KiB
Plaintext

Build1=Default,b4a.example
File1=Main_Layout.bal
FileGroup1=Default Group
Group=Default Group
Library1=core
Library2=xui
Library3=javaobject
ManifestCode='This code will be applied to the manifest file during compilation.~\n~'You do not need to modify it in most cases.~\n~'See this link for for more information: https://www.b4x.com/forum/showthread.php?p=78136~\n~AddManifestText(~\n~<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34"/>~\n~<supports-screens android:largeScreens="true" ~\n~ android:normalScreens="true" ~\n~ android:smallScreens="true" ~\n~ android:anyDensity="true"/>)~\n~SetApplicationAttribute(android:icon, "@drawable/icon")~\n~SetApplicationAttribute(android:label, "$LABEL$")~\n~CreateResourceFromFile(Macro, Themes.LightTheme)~\n~'End of default text.~\n~
Module1=AnalogClock
Module2=Starter
Module3=Localization
NumberOfFiles=1
NumberOfLibraries=3
NumberOfModules=3
Version=13
@EndOfDesignText@
#Region Project Attributes
#ApplicationLabel: Overtime Guard
#VersionCode: 1
#VersionName: 1.0
#CanInstallToExternalStorage: False
#End Region
#Region Activity Attributes
#FullScreen: False
#IncludeTitle: False
#End Region
Sub Process_Globals
Private Timer1 As Timer
Private FirstWorkLimitMs As Long
Private WorkLimitMs As Long
Private OvertimeLimitMs As Long
Private MaxProductiveMs As Long
Private StatsFileName As String
Private StateFileName As String
Private AutoConfigFileName As String
Private SettingsFileName As String
Private MaxStoredDays As Int
End Sub
Sub Globals
Private BtnStart As Button
Private BtnPause As Button
Private BtnReset As Button
Private BtnSync As Button
Private BtnStats As Label
Private BtnStatsClose As Button
Private BtnConfig As Label
Private BtnConfigClose As Button
Private BtnConfigSave As Button
Private BtnConfigReset As Button
Private BtnClearToday As Button
Private BtnHelpOpen As Button
Private BtnHelpClose As Button
Private chkIncludePauses As CheckBox
Private BtnBackground As Label
Private lblAppName As Label
Private lblBrandInfo As Label
Private lblElapsedTime As Label
Private lblAutoStatus As Label
Private pnlClock As Panel
Private pnlStats As Panel
Private pnlConfig As Panel
Private pnlHelp As Panel
Private svStats As ScrollView
Private svHelp As ScrollView
Private txtCfgStart As EditText
Private txtCfgPauseStart As EditText
Private txtCfgPauseEnd As EditText
Private txtCfgEnd As EditText
Private myClock As AnalogClock
Private SessionActive As Boolean
Private PauseActive As Boolean
Private ProductiveElapsedMs As Long
Private CurrentSegmentStart As Long
Private CurrentSegmentColor As Int
Private StatsRows As List
Private WorkMorningColor As Int
Private CurrentWorkDayStart As Long
Private CurrentWorkDayKey As Long
Private AutoModeEnabled As Boolean
Private AutoStartMinutes As Int
Private AutoPauseStartMinutes As Int
Private AutoPauseEndMinutes As Int
Private AutoEndMinutes As Int
Private AutoState As String
Private LastActiveStateSaveAt As Long
Private IncludePauseInTotal As Boolean
End Sub
Sub Activity_Create(FirstTime As Boolean)
Localization.Initialize
Activity.LoadLayout("Main_Layout")
Activity.Title = Localization.T("app_title")
Log("Layout loaded successfully.")
ResizeClockPanel
FirstWorkLimitMs = 4 * DateTime.TicksPerHour
WorkLimitMs = 8 * DateTime.TicksPerHour
OvertimeLimitMs = DateTime.TicksPerHour
MaxProductiveMs = WorkLimitMs + OvertimeLimitMs
WorkMorningColor = Colors.RGB(0, 190, 255)
StatsFileName = "work_segments.txt"
StateFileName = "active_state.txt"
AutoConfigFileName = "auto_config.txt"
SettingsFileName = "app_settings.txt"
MaxStoredDays = 62
IncludePauseInTotal = True
myClock.Initialize(pnlClock, 96)
myClock.SetBaseDuration(12 * DateTime.TicksPerHour)
Timer1.Initialize("Timer1", 1000)
Timer1.Enabled = True
StatsRows.Initialize
LoadStatsRows
LoadSettings
HideLegacyButtons
CreateStatsButton
CreateStatsPage
CreateConfigButton
CreateConfigPage
CreateHelpPage
CreateBackgroundButton
CreateAppNameLabel
CreateBrandInfoLabel
CreateAutoStatusLabel
LayoutMainButtons
LoadAutoConfig
ResetSessionState(False)
If AutoModeEnabled Then
UpdateAutomaticState(DateTime.Now)
Else
RestoreActiveState
If SessionActive = False Then RestoreLastClosedDayState
End If
End Sub
Sub Timer1_Tick
If AutoModeEnabled Then
UpdateAutomaticState(DateTime.Now)
Return
End If
If SessionActive Then
Dim now As Long = DateTime.Now
If PauseActive Then
ProcessActivePauseSegment(now)
If SessionActive = False Then Return
myClock.SetActiveSegment(now - CurrentSegmentStart, Colors.Yellow, True)
Else
ProcessActiveWorkSegment(now)
If SessionActive Then
myClock.SetActiveSegment(now - CurrentSegmentStart, CurrentSegmentColor, False)
End If
End If
If SessionActive And now - LastActiveStateSaveAt >= 30 * DateTime.TicksPerSecond Then SaveActiveState
UpdateElapsedLabel
Else
myClock.DrawClock
End If
End Sub
Sub Activity_Resume
If AutoModeEnabled Then
UpdateAutomaticState(DateTime.Now)
Else
RestoreActiveState
If SessionActive = False Then RestoreLastClosedDayState
End If
End Sub
Private Sub ResizeClockPanel
Dim clockSize As Int = Activity.Width
pnlClock.SetLayout(0, 0, clockSize, clockSize)
End Sub
Private Sub CreateStatsButton
BtnStats.Initialize("BtnStats")
BtnStats.Text = Localization.T("stats")
Activity.AddView(BtnStats, BtnReset.Left, BtnReset.Top, BtnReset.Width, BtnReset.Height)
End Sub
Private Sub CreateStatsPage
pnlStats.Initialize("")
pnlStats.Color = Colors.White
pnlStats.Visible = False
Activity.AddView(pnlStats, 0, 0, Activity.Width, Activity.Height)
Dim title As Label
title.Initialize("")
title.Text = Localization.T("statistics")
title.TextSize = 22
title.TextColor = Colors.Black
title.Gravity = Gravity.CENTER_VERTICAL
pnlStats.AddView(title, 12dip, 8dip, Activity.Width - 110dip, 46dip)
BtnStatsClose.Initialize("BtnStatsClose")
BtnStatsClose.Text = Localization.T("close")
pnlStats.AddView(BtnStatsClose, Activity.Width - 94dip, 8dip, 84dip, 46dip)
svStats.Initialize(0)
pnlStats.AddView(svStats, 0, 62dip, Activity.Width, Activity.Height - 62dip)
End Sub
Private Sub CreateConfigButton
BtnConfig.Initialize("BtnConfig")
BtnConfig.Text = Localization.T("config")
Activity.AddView(BtnConfig, BtnReset.Left, BtnReset.Top, BtnReset.Width, BtnReset.Height)
End Sub
Private Sub CreateBackgroundButton
BtnBackground.Initialize("BtnBackground")
BtnBackground.Text = Localization.T("bg")
Activity.AddView(BtnBackground, BtnPause.Left, BtnPause.Top, BtnPause.Width, BtnPause.Height)
End Sub
Private Sub CreateAppNameLabel
lblAppName.Initialize("")
lblAppName.Text = Localization.T("app_title")
lblAppName.TextColor = Colors.Black
lblAppName.TextSize = 22
lblAppName.Typeface = Typeface.CreateNew(Typeface.SANS_SERIF, Typeface.STYLE_BOLD_ITALIC)
lblAppName.Gravity = Gravity.CENTER
Activity.AddView(lblAppName, 0, 0, 10dip, 10dip)
End Sub
Private Sub CreateBrandInfoLabel
lblBrandInfo.Initialize("")
lblBrandInfo.Text = "AI Team Studio | aiteamstudio.it"
lblBrandInfo.TextColor = Colors.RGB(90, 90, 90)
lblBrandInfo.TextSize = 11
lblBrandInfo.Gravity = Gravity.CENTER
Activity.AddView(lblBrandInfo, 0, 0, 10dip, 10dip)
End Sub
Private Sub LayoutMainButtons
Dim margin As Int = 8dip
Dim gap As Int = 8dip
Dim rowGap As Int = 8dip
Dim buttonHeight As Int = 48dip
Dim labelTop As Int = Activity.Height - 28dip
Dim row2Top As Int = labelTop - gap - buttonHeight
Dim row1Top As Int = row2Top - rowGap - buttonHeight
Dim wideButtonWidth As Int = (Activity.Width - (2 * margin) - gap) / 2
Dim smallButtonWidth As Int = (Activity.Width - (2 * margin) - (2 * gap)) / 3
BtnStart.TextSize = 16
BtnPause.TextSize = 16
BtnBackground.TextSize = 15
BtnStats.TextSize = 15
BtnConfig.TextSize = 15
StyleNeutralButton(BtnPause)
StyleNeutralButton(BtnBackground)
StyleNeutralButton(BtnStats)
StyleNeutralButton(BtnConfig)
BtnStart.SetLayout(margin, row1Top, wideButtonWidth, buttonHeight)
BtnPause.SetLayout(margin + wideButtonWidth + gap, row1Top, wideButtonWidth, buttonHeight)
BtnBackground.SetLayout(margin, row2Top, smallButtonWidth, buttonHeight)
BtnStats.SetLayout(margin + smallButtonWidth + gap, row2Top, smallButtonWidth, buttonHeight)
BtnConfig.SetLayout(margin + (2 * (smallButtonWidth + gap)), row2Top, smallButtonWidth, buttonHeight)
If lblAppName.IsInitialized Then
Dim titleTop As Int = pnlClock.Top + pnlClock.Height + 4dip
Dim titleHeight As Int = Max(28dip, row1Top - titleTop - 4dip)
Dim appNameHeight As Int = Max(24dip, titleHeight * 0.58)
lblAppName.SetLayout(0, titleTop, Activity.Width, appNameHeight)
If lblBrandInfo.IsInitialized Then lblBrandInfo.SetLayout(0, titleTop + appNameHeight - 2dip, Activity.Width, Max(18dip, titleHeight - appNameHeight + 2dip))
End If
If lblElapsedTime.IsInitialized Then lblElapsedTime.SetLayout(0, labelTop, Activity.Width * 0.58, 28dip)
If lblAutoStatus.IsInitialized Then lblAutoStatus.SetLayout(Activity.Width * 0.58, labelTop, Activity.Width * 0.42, 28dip)
End Sub
Private Sub StyleNeutralButton(btn As Label)
If btn.IsInitialized = False Then Return
btn.Color = Colors.RGB(230, 230, 230)
btn.TextColor = Colors.Black
btn.Gravity = Gravity.CENTER
End Sub
Private Sub CreateAutoStatusLabel
lblAutoStatus.Initialize("")
lblAutoStatus.TextColor = Colors.DarkGray
lblAutoStatus.TextSize = 13
lblAutoStatus.Gravity = Bit.Or(Gravity.RIGHT, Gravity.CENTER_VERTICAL)
lblAutoStatus.Text = Localization.T("manual_mode")
Activity.AddView(lblAutoStatus, 0, 0, 10dip, 10dip)
End Sub
Private Sub CreateConfigPage
pnlConfig.Initialize("")
pnlConfig.Color = Colors.White
pnlConfig.Visible = False
Activity.AddView(pnlConfig, 0, 0, Activity.Width, Activity.Height)
Dim title As Label
title.Initialize("")
title.Text = Localization.T("config")
title.TextSize = 22
title.TextColor = Colors.Black
title.Gravity = Gravity.CENTER_VERTICAL
pnlConfig.AddView(title, 12dip, 8dip, Activity.Width - 110dip, 46dip)
BtnConfigClose.Initialize("BtnConfigClose")
BtnConfigClose.Text = Localization.T("close")
pnlConfig.AddView(BtnConfigClose, Activity.Width - 94dip, 8dip, 84dip, 46dip)
Dim y As Int = 78dip
txtCfgStart = AddConfigField(Localization.T("work_start"), "08:30", y)
y = y + 58dip
txtCfgPauseStart = AddConfigField(Localization.T("pause_start"), "12:30", y)
y = y + 58dip
txtCfgPauseEnd = AddConfigField(Localization.T("pause_end"), "13:00", y)
y = y + 58dip
txtCfgEnd = AddConfigField(Localization.T("work_end"), "17:00", y)
y = y + 58dip
chkIncludePauses.Initialize("chkIncludePauses")
chkIncludePauses.Text = Localization.T("include_pause_total")
chkIncludePauses.TextSize = 15
chkIncludePauses.Checked = IncludePauseInTotal
pnlConfig.AddView(chkIncludePauses, 12dip, y, Activity.Width - 24dip, 48dip)
BtnConfigSave.Initialize("BtnConfigSave")
BtnConfigSave.Text = Localization.T("set_config")
pnlConfig.AddView(BtnConfigSave, 12dip, y + 56dip, (Activity.Width - 32dip) / 2, 48dip)
BtnConfigReset.Initialize("BtnConfigReset")
BtnConfigReset.Text = Localization.T("reset_config")
pnlConfig.AddView(BtnConfigReset, 20dip + ((Activity.Width - 32dip) / 2), y + 56dip, (Activity.Width - 32dip) / 2, 48dip)
BtnClearToday.Initialize("BtnClearToday")
BtnClearToday.Text = Localization.T("clear_today")
pnlConfig.AddView(BtnClearToday, 12dip, y + 112dip, Activity.Width - 24dip, 48dip)
BtnHelpOpen.Initialize("BtnHelpOpen")
BtnHelpOpen.Text = Localization.T("help")
pnlConfig.AddView(BtnHelpOpen, 12dip, y + 168dip, Activity.Width - 24dip, 48dip)
End Sub
Private Sub CreateHelpPage
pnlHelp.Initialize("")
pnlHelp.Color = Colors.White
pnlHelp.Visible = False
Activity.AddView(pnlHelp, 0, 0, Activity.Width, Activity.Height)
Dim title As Label
title.Initialize("")
title.Text = Localization.T("help")
title.TextSize = 22
title.TextColor = Colors.Black
title.Gravity = Gravity.CENTER_VERTICAL
pnlHelp.AddView(title, 12dip, 8dip, Activity.Width - 110dip, 46dip)
BtnHelpClose.Initialize("BtnHelpClose")
BtnHelpClose.Text = Localization.T("close")
pnlHelp.AddView(BtnHelpClose, Activity.Width - 94dip, 8dip, 84dip, 46dip)
svHelp.Initialize(0)
pnlHelp.AddView(svHelp, 0, 62dip, Activity.Width, Activity.Height - 62dip)
Dim lblHelp As Label
lblHelp.Initialize("")
lblHelp.Text = BuildHelpText
lblHelp.TextSize = 16
lblHelp.TextColor = Colors.Black
lblHelp.Gravity = Gravity.TOP
svHelp.Panel.AddView(lblHelp, 12dip, 12dip, Activity.Width - 24dip, 1180dip)
svHelp.Panel.Height = 1204dip
End Sub
Private Sub AddConfigField(LabelText As String, ValueText As String, y As Int) As EditText
Dim lbl As Label
lbl.Initialize("")
lbl.Text = LabelText
lbl.TextSize = 16
lbl.TextColor = Colors.Black
lbl.Gravity = Gravity.CENTER_VERTICAL
pnlConfig.AddView(lbl, 12dip, y, 140dip, 46dip)
Dim txt As EditText
txt.Initialize("")
txt.Text = ValueText
txt.TextSize = 18
txt.SingleLine = True
txt.InputType = txt.INPUT_TYPE_NUMBERS
pnlConfig.AddView(txt, 160dip, y, Activity.Width - 172dip, 46dip)
Return txt
End Sub
Sub Activity_Pause (UserClosed As Boolean)
If AutoModeEnabled = False Then SaveActiveState
End Sub
' Formatta il tempo in HH:MM:SS
Sub FormatElapsedTime(ms As Long) As String
Dim seconds As Int = ms / 1000
Dim minutes As Int = seconds / 60
Dim hours As Int = minutes / 60
seconds = seconds Mod 60
minutes = minutes Mod 60
Return NumberFormat(hours, 2, 0) & ":" & NumberFormat(minutes, 2, 0) & ":" & NumberFormat(seconds, 2, 0)
End Sub
' Pulsante Start
Sub BtnStart_Click
If AutoModeEnabled Then Return
If SessionActive = False Then
StartSession
Else
EndSession(DateTime.Now, False)
End If
End Sub
' Pulsante Pause
Sub BtnPause_Click
If AutoModeEnabled Then Return
If SessionActive = False Then Return
Dim now As Long = DateTime.Now
If PauseActive Then
CloseCurrentSegment(now)
PauseActive = False
BtnPause.Text = Localization.T("pause")
CurrentSegmentStart = now
CurrentSegmentColor = GetCurrentWorkColor
myClock.SetActiveSegment(0, CurrentSegmentColor, False)
SaveActiveState
Log("Pause ended.")
Else
ProcessActiveWorkSegment(now)
If SessionActive = False Then Return
CloseCurrentSegment(now)
PauseActive = True
BtnPause.Text = Localization.T("end_pause")
CurrentSegmentStart = now
CurrentSegmentColor = Colors.Yellow
myClock.SetActiveSegment(0, CurrentSegmentColor, True)
SaveActiveState
Log("Pause started.")
End If
End Sub
' Pulsante Reset
Sub BtnReset_Click
ShowStatsPage
End Sub
' Pulsante Sync
Sub BtnSync_Click
ShowConfigPage
End Sub
Sub BtnStats_Click
ShowStatsPage
End Sub
Sub BtnStatsClose_Click
ShowMainPage
End Sub
Sub BtnConfig_Click
ShowConfigPage
End Sub
Sub BtnConfigClose_Click
ShowMainPage
End Sub
Sub BtnConfigSave_Click
SetAutomaticConfig
End Sub
Sub BtnConfigReset_Click
ResetAutomaticConfig
End Sub
Sub chkIncludePauses_CheckedChange(Checked As Boolean)
IncludePauseInTotal = Checked
SaveSettings
ApplyPauseCountingMode
End Sub
Sub BtnClearToday_Click
ClearDisplayedDay
End Sub
Sub BtnHelpOpen_Click
ShowHelpPage
End Sub
Sub BtnHelpClose_Click
ShowConfigPage
End Sub
Sub BtnBackground_Click
SaveActiveState
Dim home As Intent
home.Initialize("android.intent.action.MAIN", "")
home.AddCategory("android.intent.category.HOME")
home.Flags = 268435456
StartActivity(home)
End Sub
' Sincronizza l'orologio
Sub SynchronizeClock
myClock.DrawClock
End Sub
Private Sub StartSession
CurrentWorkDayStart = GetWorkDayStart(DateTime.Now)
CurrentWorkDayKey = CurrentWorkDayStart
ProductiveElapsedMs = GetProductiveTotalForWorkDay(CurrentWorkDayKey)
If ProductiveElapsedMs >= MaxProductiveMs Then
ToastMessageShow(Localization.T("limit_reached"), False)
UpdateElapsedLabel
Log("Start ignored: 9-hour limit already reached.")
Return
End If
SessionActive = True
PauseActive = False
CurrentSegmentStart = DateTime.Now
CurrentSegmentColor = GetCurrentWorkColor
BtnStart.Text = Localization.T("end")
BtnPause.Text = Localization.T("pause")
BtnPause.Enabled = True
UpdateMainControlState
myClock.ClearSegments
LoadWorkDaySegmentsIntoClock(CurrentWorkDayKey)
myClock.SetActiveSegment(0, CurrentSegmentColor, False)
UpdateElapsedLabel
SaveActiveState
Log("Workday started.")
End Sub
Private Sub EndSession(FinalTime As Long, Automatic As Boolean)
If SessionActive Then CloseCurrentSegment(FinalTime)
SessionActive = False
PauseActive = False
BtnStart.Text = Localization.T("start")
BtnPause.Text = Localization.T("pause")
BtnPause.Enabled = False
UpdateMainControlState
myClock.ClearActiveSegment
UpdateElapsedLabel
If File.Exists(File.DirInternal, StateFileName) Then File.Delete(File.DirInternal, StateFileName)
If Automatic Then
Log("Workday stopped automatically.")
Else
Log("Workday closed manually.")
End If
End Sub
Private Sub ResetSessionState(ClearClock As Boolean)
SessionActive = False
PauseActive = False
ProductiveElapsedMs = 0
CurrentSegmentStart = 0
CurrentWorkDayStart = 0
CurrentWorkDayKey = 0
CurrentSegmentColor = WorkMorningColor
LastActiveStateSaveAt = 0
BtnStart.Text = Localization.T("start")
BtnPause.Text = Localization.T("pause")
BtnPause.Enabled = False
UpdateMainControlState
If ClearClock Then myClock.ClearSegments
myClock.ClearActiveSegment
UpdateElapsedLabel
End Sub
Private Sub ProcessActiveWorkSegment(now As Long)
Do While SessionActive And PauseActive = False
Dim midnight As Long = GetNextMidnight(CurrentSegmentStart)
If now >= midnight Then
EndSession(midnight, True)
Log("Workday stopped automatically: midnight reached.")
Return
End If
Dim segmentDuration As Long = now - CurrentSegmentStart
If segmentDuration <= 0 Then Return
Dim nextBoundary As Long = GetNextWorkBoundary
Dim durationToBoundary As Long = nextBoundary - ProductiveElapsedMs
If segmentDuration < durationToBoundary Then
CurrentSegmentColor = GetCurrentWorkColor
Return
End If
RecordSegment(CurrentSegmentStart, durationToBoundary, GetCurrentWorkColor, False)
ProductiveElapsedMs = ProductiveElapsedMs + durationToBoundary
CurrentSegmentStart = CurrentSegmentStart + durationToBoundary
CurrentSegmentColor = GetCurrentWorkColor
If ProductiveElapsedMs >= MaxProductiveMs Then
EndSession(CurrentSegmentStart, True)
Return
End If
Loop
End Sub
Private Sub CloseCurrentSegment(FinalTime As Long)
Dim duration As Long = FinalTime - CurrentSegmentStart
If duration <= 0 Then Return
If PauseActive Then
RecordSegment(CurrentSegmentStart, duration, Colors.Yellow, True)
If IncludePauseInTotal Then ProductiveElapsedMs = ProductiveElapsedMs + duration
Else
RecordSegment(CurrentSegmentStart, duration, GetCurrentWorkColor, False)
ProductiveElapsedMs = ProductiveElapsedMs + duration
End If
End Sub
Private Sub RecordSegment(SegmentStart As Long, Duration As Long, SegmentColor As Int, IsPauseSegment As Boolean)
myClock.AddSegment(Duration, SegmentColor, IsPauseSegment)
AddStatsSegment(CurrentWorkDayStart, CurrentWorkDayKey, SegmentStart, Duration, GetSegmentCategory(SegmentColor, IsPauseSegment), "manual")
End Sub
Private Sub AddStatsSegment(DayStart As Long, DayKey As Long, SegmentStart As Long, Duration As Long, Category As String, Source As String)
Dim row As Map
row.Initialize
row.Put("dayStart", DayStart)
row.Put("dayKey", DayKey)
row.Put("segmentStart", SegmentStart)
row.Put("date", DateTime.Date(DayStart))
row.Put("duration", Duration)
row.Put("category", Category)
row.Put("source", Source)
StatsRows.Add(row)
TrimStatsRows
SaveStatsRows
End Sub
Private Sub ProcessActivePauseSegment(now As Long)
If IncludePauseInTotal Then
Dim remainingToLimit As Long = MaxProductiveMs - ProductiveElapsedMs
If remainingToLimit <= 0 Then
EndSession(CurrentSegmentStart, True)
Log("Workday stopped automatically during pause: 9-hour limit reached.")
Return
End If
Dim limitTime As Long = CurrentSegmentStart + remainingToLimit
If limitTime <= GetNextMidnight(CurrentSegmentStart) And now >= limitTime Then
EndSession(limitTime, True)
Log("Workday stopped automatically during pause: 9-hour limit reached.")
Return
End If
End If
Dim midnight As Long = GetNextMidnight(CurrentSegmentStart)
If now >= midnight Then
EndSession(midnight, True)
Log("Workday stopped automatically during pause: midnight reached.")
End If
End Sub
Private Sub GetSegmentCategory(SegmentColor As Int, IsPauseSegment As Boolean) As String
If IsPauseSegment Then Return "pausa"
If SegmentColor = Colors.Red Then Return "straordinario"
Return "lavoro"
End Sub
Private Sub GetCurrentWorkColor As Int
If ProductiveElapsedMs < FirstWorkLimitMs Then Return WorkMorningColor
If ProductiveElapsedMs < WorkLimitMs Then Return Colors.Green
Return Colors.Red
End Sub
Private Sub GetNextWorkBoundary As Long
If ProductiveElapsedMs < FirstWorkLimitMs Then Return FirstWorkLimitMs
If ProductiveElapsedMs < WorkLimitMs Then Return WorkLimitMs
Return MaxProductiveMs
End Sub
Private Sub UpdateElapsedLabel
If lblElapsedTime.IsInitialized Then
Dim labelPrefix As String = Localization.T("work")
Dim dayStart As Long = GetDisplayedWorkDayStart
If dayStart > 0 Then labelPrefix = FormatDayShort(dayStart) & " " & Localization.T("work")
lblElapsedTime.Text = labelPrefix & ": " & FormatElapsedTime(GetDisplayedProductiveElapsed(DateTime.Now))
End If
End Sub
Private Sub UpdateMainControlState
If BtnStart.IsInitialized = False Then Return
BtnStart.Enabled = AutoModeEnabled = False
If SessionActive Or (AutoModeEnabled And AutoState = "recording") Then
BtnStart.Color = Colors.RGB(0, 150, 70)
BtnStart.TextColor = Colors.White
Else
BtnStart.Color = Colors.RGB(190, 40, 40)
BtnStart.TextColor = Colors.White
End If
If AutoModeEnabled Then
BtnPause.Enabled = False
Else If SessionActive Then
BtnPause.Enabled = True
End If
If lblAutoStatus.IsInitialized Then
If AutoModeEnabled Then
Select AutoState
Case "recording"
lblAutoStatus.Text = Localization.T("auto_recording")
Case "paused"
lblAutoStatus.Text = Localization.T("auto_paused")
Case Else
lblAutoStatus.Text = Localization.T("auto_stopped")
End Select
Else
lblAutoStatus.Text = Localization.T("manual_mode")
End If
End If
End Sub
Private Sub GetDisplayedWorkDayStart As Long
If CurrentWorkDayStart > 0 Then Return CurrentWorkDayStart
Return GetLatestWorkDayStart
End Sub
Private Sub FormatDayShort(ticks As Long) As String
Return NumberFormat(DateTime.GetDayOfMonth(ticks), 2, 0) & "/" & NumberFormat(DateTime.GetMonth(ticks), 2, 0)
End Sub
Private Sub GetDisplayedProductiveElapsed(now As Long) As Long
Dim total As Long = ProductiveElapsedMs
If SessionActive And PauseActive And IncludePauseInTotal Then total = total + Max(0, now - CurrentSegmentStart)
If SessionActive And PauseActive = False Then total = total + Max(0, now - CurrentSegmentStart)
Return total
End Sub
Private Sub GetProductiveTotalForWorkDay(DayKey As Long) As Long
Dim total As Long = 0
For Each row As Map In StatsRows
If row.Get("dayKey") = DayKey Then
Dim category As String = row.Get("category")
If category = "lavoro" Or category = "straordinario" Then total = total + row.Get("duration")
If IncludePauseInTotal And category = "pausa" Then total = total + row.Get("duration")
End If
Next
Return total
End Sub
Private Sub LoadWorkDaySegmentsIntoClock(DayKey As Long)
Dim productiveCursor As Long = 0
For Each row As Map In StatsRows
If row.Get("dayKey") = DayKey Then
Dim category As String = row.Get("category")
Dim duration As Long = row.Get("duration")
If category = "pausa" Then
myClock.AddSegment(duration, Colors.Yellow, True)
If IncludePauseInTotal Then productiveCursor = productiveCursor + duration
Else
AddDisplayProductiveSegment(duration, productiveCursor)
productiveCursor = productiveCursor + duration
End If
End If
Next
End Sub
Private Sub AddDisplayProductiveSegment(Duration As Long, StartProductiveMs As Long)
Dim remaining As Long = Duration
Dim cursor As Long = StartProductiveMs
Do While remaining > 0
Dim segmentColor As Int
Dim boundary As Long
If cursor < FirstWorkLimitMs Then
segmentColor = WorkMorningColor
boundary = FirstWorkLimitMs
Else If cursor < WorkLimitMs Then
segmentColor = Colors.Green
boundary = WorkLimitMs
Else
segmentColor = Colors.Red
boundary = MaxProductiveMs
End If
Dim chunk As Long = remaining
If cursor < boundary Then chunk = Min(chunk, boundary - cursor)
myClock.AddSegment(chunk, segmentColor, False)
remaining = remaining - chunk
cursor = cursor + chunk
Loop
End Sub
Private Sub GetWorkDayStart(now As Long) As Long
Dim latestDayStart As Long = 0
Dim today As String = DateTime.Date(now)
For Each row As Map In StatsRows
Dim dayStart As Long = row.Get("dayStart")
If dayStart > latestDayStart And DateTime.Date(dayStart) = today Then
latestDayStart = dayStart
End If
Next
If latestDayStart > 0 Then Return latestDayStart
Return now
End Sub
Private Sub GetLatestWorkDayStart As Long
Dim latestDayStart As Long = 0
For Each row As Map In StatsRows
Dim dayStart As Long = row.Get("dayStart")
If dayStart > latestDayStart Then latestDayStart = dayStart
Next
Return latestDayStart
End Sub
Private Sub GetNextMidnight(ticks As Long) As Long
Return DateTime.DateParse(DateTime.Date(ticks)) + DateTime.TicksPerDay
End Sub
Private Sub LoadStatsRows
StatsRows.Clear
If File.Exists(File.DirInternal, StatsFileName) = False Then Return
Dim lines As List = File.ReadList(File.DirInternal, StatsFileName)
For Each line As String In lines
If line.Trim.Length > 0 Then
Dim parts() As String = Regex.Split("\|", line)
If parts.Length >= 4 Then
Dim row As Map
row.Initialize
Dim dayStart As Long = parts(0)
Dim segmentStart As Long = parts(1)
Dim duration As Long = parts(2)
row.Put("dayStart", dayStart)
row.Put("dayKey", dayStart)
row.Put("segmentStart", segmentStart)
row.Put("date", DateTime.Date(dayStart))
row.Put("duration", duration)
row.Put("category", parts(3))
If parts.Length >= 5 Then
row.Put("source", parts(4))
Else
row.Put("source", "manual")
End If
StatsRows.Add(row)
End If
End If
Next
TrimStatsRows
SaveStatsRows
End Sub
Private Sub SaveStatsRows
Dim lines As List
lines.Initialize
For Each row As Map In StatsRows
Dim source As String = "manual"
If row.ContainsKey("source") Then source = row.Get("source")
lines.Add(row.Get("dayStart") & "|" & row.Get("segmentStart") & "|" & row.Get("duration") & "|" & row.Get("category") & "|" & source)
Next
File.WriteList(File.DirInternal, StatsFileName, lines)
End Sub
Private Sub TrimStatsRows
Dim retention As Long = MaxStoredDays
retention = retention * DateTime.TicksPerDay
Dim cutoff As Long = DateTime.Now - retention
Dim kept As List
kept.Initialize
For Each row As Map In StatsRows
Dim dayStart As Long = row.Get("dayStart")
If dayStart >= cutoff Then kept.Add(row)
Next
StatsRows.Clear
StatsRows.AddAll(kept)
End Sub
Private Sub SaveActiveState
If SessionActive = False Then
If File.Exists(File.DirInternal, StateFileName) Then File.Delete(File.DirInternal, StateFileName)
LastActiveStateSaveAt = 0
Return
End If
Dim lines As List
lines.Initialize
lines.Add(CurrentWorkDayStart)
lines.Add(CurrentSegmentStart)
lines.Add(ProductiveElapsedMs)
lines.Add(PauseActive)
lines.Add(CurrentSegmentColor)
File.WriteList(File.DirInternal, StateFileName, lines)
LastActiveStateSaveAt = DateTime.Now
End Sub
Private Sub RestoreActiveState
If SessionActive Then Return
If File.Exists(File.DirInternal, StateFileName) = False Then Return
Dim lines As List = File.ReadList(File.DirInternal, StateFileName)
If lines.Size < 5 Then Return
CurrentWorkDayStart = lines.Get(0)
CurrentWorkDayKey = CurrentWorkDayStart
CurrentSegmentStart = lines.Get(1)
ProductiveElapsedMs = GetProductiveTotalForWorkDay(CurrentWorkDayKey)
PauseActive = lines.Get(3)
CurrentSegmentColor = lines.Get(4)
Dim now As Long = DateTime.Now
If now >= GetNextMidnight(CurrentSegmentStart) Then
SessionActive = True
EndSession(GetNextMidnight(CurrentSegmentStart), True)
If File.Exists(File.DirInternal, StateFileName) Then File.Delete(File.DirInternal, StateFileName)
Return
End If
If ProductiveElapsedMs >= MaxProductiveMs Then
If File.Exists(File.DirInternal, StateFileName) Then File.Delete(File.DirInternal, StateFileName)
Return
End If
SessionActive = True
BtnStart.Text = Localization.T("end")
If PauseActive Then
BtnPause.Text = Localization.T("end_pause")
BtnPause.Enabled = True
Else
BtnPause.Text = Localization.T("pause")
BtnPause.Enabled = True
CurrentSegmentColor = GetCurrentWorkColor
End If
UpdateMainControlState
myClock.ClearSegments
LoadWorkDaySegmentsIntoClock(CurrentWorkDayKey)
myClock.SetActiveSegment(Max(0, now - CurrentSegmentStart), CurrentSegmentColor, PauseActive)
UpdateElapsedLabel
End Sub
Private Sub RestoreLastClosedDayState
If SessionActive Then Return
Dim latestDayStart As Long = GetLatestWorkDayStart
If latestDayStart = 0 Then Return
If CurrentWorkDayStart = latestDayStart And ProductiveElapsedMs > 0 Then Return
CurrentWorkDayStart = latestDayStart
CurrentWorkDayKey = latestDayStart
ProductiveElapsedMs = GetProductiveTotalForWorkDay(CurrentWorkDayKey)
CurrentSegmentStart = 0
PauseActive = False
CurrentSegmentColor = GetCurrentWorkColor
myClock.ClearSegments
LoadWorkDaySegmentsIntoClock(CurrentWorkDayKey)
myClock.ClearActiveSegment
UpdateElapsedLabel
End Sub
Private Sub ShowStatsPage
BuildStatsTable
SetMainControlsVisible(False)
pnlStats.Visible = True
pnlConfig.Visible = False
pnlHelp.Visible = False
pnlStats.BringToFront
End Sub
Private Sub SetMainControlsVisible(Visible As Boolean)
For i = 0 To Activity.NumberOfViews - 1
Dim v As View = Activity.GetView(i)
v.Visible = Visible
Next
HideLegacyButtons
pnlStats.Visible = False
pnlConfig.Visible = False
pnlHelp.Visible = False
End Sub
Private Sub HideLegacyButtons
If BtnReset.IsInitialized Then
BtnReset.Enabled = False
BtnReset.Visible = False
BtnReset.SetLayout(-200dip, -200dip, 1dip, 1dip)
End If
If BtnSync.IsInitialized Then
BtnSync.Enabled = False
BtnSync.Visible = False
BtnSync.SetLayout(-200dip, -200dip, 1dip, 1dip)
End If
For i = Activity.NumberOfViews - 1 To 0 Step -1
Dim v As View = Activity.GetView(i)
If v Is Button Then
Dim legacy As Button = v
If legacy.Text = "Sync" Or legacy.Text = "Reset" Then
legacy.RemoveView
End If
End If
Next
End Sub
Private Sub ShowConfigPage
SetMainControlsVisible(False)
pnlStats.Visible = False
pnlConfig.Visible = True
pnlHelp.Visible = False
pnlConfig.BringToFront
End Sub
Private Sub ShowHelpPage
SetMainControlsVisible(False)
pnlStats.Visible = False
pnlConfig.Visible = False
pnlHelp.Visible = True
pnlHelp.BringToFront
End Sub
Private Sub ShowMainPage
SetMainControlsVisible(True)
pnlStats.Visible = False
pnlConfig.Visible = False
pnlHelp.Visible = False
End Sub
Private Sub BuildStatsTable
svStats.Panel.RemoveAllViews
Dim y As Int = 0
AddStatsRow(y, Localization.T("date"), Localization.T("work"), Localization.T("pause_col"), Localization.T("overtime"), True)
y = y + 34dip
Dim totalWork As Long = 0
Dim totalPause As Long = 0
Dim totalOvertime As Long = 0
Dim dailyRows As Map
dailyRows.Initialize
For Each row As Map In StatsRows
AddStatsDuration(dailyRows, row.Get("date"), row.Get("category"), row.Get("duration"))
Next
If SessionActive Then
Dim activeDuration As Long = Max(0, DateTime.Now - CurrentSegmentStart)
If activeDuration > 0 Then
AddStatsDuration(dailyRows, DateTime.Date(CurrentWorkDayStart), GetSegmentCategory(CurrentSegmentColor, PauseActive), activeDuration)
End If
End If
For Each dateText As String In dailyRows.Keys
Dim dailyTotals As Map = dailyRows.Get(dateText)
totalWork = totalWork + dailyTotals.Get("lavoro")
totalPause = totalPause + dailyTotals.Get("pausa")
totalOvertime = totalOvertime + dailyTotals.Get("straordinario")
Next
For Each dateText As String In dailyRows.Keys
Dim dailyTotals As Map = dailyRows.Get(dateText)
AddStatsRow(y, dateText, FormatElapsedTime(dailyTotals.Get("lavoro")), FormatElapsedTime(dailyTotals.Get("pausa")), FormatElapsedTime(dailyTotals.Get("straordinario")), False)
y = y + 30dip
Next
y = y + 8dip
AddStatsRow(y, Localization.T("totals"), FormatElapsedTime(totalWork), FormatElapsedTime(totalPause), FormatElapsedTime(totalOvertime), True)
y = y + 40dip
svStats.Panel.Height = Max(y, svStats.Height + 1dip)
End Sub
Private Sub AddStatsDuration(dailyRows As Map, DateText As String, Category As String, Duration As Long)
Dim dailyTotals As Map
If dailyRows.ContainsKey(DateText) Then
dailyTotals = dailyRows.Get(DateText)
Else
dailyTotals.Initialize
dailyTotals.Put("lavoro", 0)
dailyTotals.Put("pausa", 0)
dailyTotals.Put("straordinario", 0)
dailyRows.Put(DateText, dailyTotals)
End If
dailyTotals.Put(Category, dailyTotals.Get(Category) + Duration)
End Sub
Private Sub SetAutomaticConfig
Dim startMinutes As Int = ParseTimeToMinutes(txtCfgStart.Text)
Dim pauseStartMinutes As Int = ParseTimeToMinutes(txtCfgPauseStart.Text)
Dim pauseEndMinutes As Int = ParseTimeToMinutes(txtCfgPauseEnd.Text)
Dim endMinutes As Int = ParseTimeToMinutes(txtCfgEnd.Text)
If startMinutes < 0 Or pauseStartMinutes < 0 Or pauseEndMinutes < 0 Or endMinutes < 0 Then
ToastMessageShow(Localization.T("use_hhmm"), False)
Return
End If
If startMinutes >= pauseStartMinutes Or pauseStartMinutes >= pauseEndMinutes Or pauseEndMinutes >= endMinutes Then
ToastMessageShow(Localization.T("times_order"), False)
Return
End If
Dim workMinutes As Int = (pauseStartMinutes - startMinutes) + (endMinutes - pauseEndMinutes)
Dim countedMinutes As Int = workMinutes
If IncludePauseInTotal Then countedMinutes = endMinutes - startMinutes
If countedMinutes > 480 Then
ToastMessageShow(Localization.T("work_exceed"), False)
Return
End If
AutoStartMinutes = startMinutes
AutoPauseStartMinutes = pauseStartMinutes
AutoPauseEndMinutes = pauseEndMinutes
AutoEndMinutes = endMinutes
AutoModeEnabled = True
SaveAutoConfig
If SessionActive Then EndSession(DateTime.Now, False)
If File.Exists(File.DirInternal, StateFileName) Then File.Delete(File.DirInternal, StateFileName)
UpdateAutomaticState(DateTime.Now)
ToastMessageShow(Localization.T("auto_enabled"), False)
End Sub
Private Sub ResetAutomaticConfig
AutoModeEnabled = False
AutoState = "stopped"
If File.Exists(File.DirInternal, AutoConfigFileName) Then File.Delete(File.DirInternal, AutoConfigFileName)
ResetSessionState(False)
RestoreLastClosedDayState
UpdateMainControlState
ToastMessageShow(Localization.T("auto_disabled"), False)
End Sub
Private Sub SaveAutoConfig
Dim lines As List
lines.Initialize
lines.Add(AutoStartMinutes)
lines.Add(AutoPauseStartMinutes)
lines.Add(AutoPauseEndMinutes)
lines.Add(AutoEndMinutes)
File.WriteList(File.DirInternal, AutoConfigFileName, lines)
End Sub
Private Sub SaveSettings
Dim lines As List
lines.Initialize
lines.Add(IncludePauseInTotal)
File.WriteList(File.DirInternal, SettingsFileName, lines)
End Sub
Private Sub LoadAutoConfig
AutoModeEnabled = False
AutoState = "stopped"
If File.Exists(File.DirInternal, AutoConfigFileName) = False Then Return
Dim lines As List = File.ReadList(File.DirInternal, AutoConfigFileName)
If lines.Size < 4 Then Return
AutoStartMinutes = lines.Get(0)
AutoPauseStartMinutes = lines.Get(1)
AutoPauseEndMinutes = lines.Get(2)
AutoEndMinutes = lines.Get(3)
AutoModeEnabled = True
txtCfgStart.Text = MinutesToTime(AutoStartMinutes)
txtCfgPauseStart.Text = MinutesToTime(AutoPauseStartMinutes)
txtCfgPauseEnd.Text = MinutesToTime(AutoPauseEndMinutes)
txtCfgEnd.Text = MinutesToTime(AutoEndMinutes)
End Sub
Private Sub LoadSettings
IncludePauseInTotal = True
If File.Exists(File.DirInternal, SettingsFileName) = False Then Return
Dim lines As List = File.ReadList(File.DirInternal, SettingsFileName)
If lines.Size = 0 Then Return
IncludePauseInTotal = ParseBooleanValue(lines.Get(0), True)
End Sub
Private Sub ParseTimeToMinutes(Value As String) As Int
Dim parts() As String = Regex.Split(":", Value.Trim)
If parts.Length <> 2 Then Return -1
If IsNumber(parts(0)) = False Or IsNumber(parts(1)) = False Then Return -1
Dim h As Int = parts(0)
Dim m As Int = parts(1)
If h < 0 Or h > 23 Or m < 0 Or m > 59 Then Return -1
Return h * 60 + m
End Sub
Private Sub MinutesToTime(Minutes As Int) As String
Return NumberFormat(Minutes / 60, 2, 0) & ":" & NumberFormat(Minutes Mod 60, 2, 0)
End Sub
Private Sub UpdateAutomaticState(now As Long)
Dim dayStart As Long = DateTime.DateParse(DateTime.Date(now))
Dim minuteOfDay As Int = DateTime.GetHour(now) * 60 + DateTime.GetMinute(now)
CurrentWorkDayStart = dayStart
CurrentWorkDayKey = dayStart
RegenerateAutoSegmentsForDay(dayStart, minuteOfDay)
ProductiveElapsedMs = GetProductiveTotalForWorkDay(CurrentWorkDayKey)
SessionActive = minuteOfDay >= AutoStartMinutes And minuteOfDay < AutoEndMinutes And ProductiveElapsedMs < WorkLimitMs
PauseActive = minuteOfDay >= AutoPauseStartMinutes And minuteOfDay < AutoPauseEndMinutes
If SessionActive And PauseActive = False Then
AutoState = "recording"
Else If SessionActive And PauseActive Then
AutoState = "paused"
Else
AutoState = "stopped"
End If
BtnStart.Text = IIf(SessionActive, Localization.T("end"), Localization.T("start"))
BtnPause.Text = IIf(PauseActive, Localization.T("end_pause"), Localization.T("pause"))
CurrentSegmentStart = now
CurrentSegmentColor = GetCurrentWorkColor
myClock.ClearSegments
LoadWorkDaySegmentsIntoClock(CurrentWorkDayKey)
myClock.ClearActiveSegment
UpdateElapsedLabel
UpdateMainControlState
End Sub
Private Sub RegenerateAutoSegmentsForDay(DayStart As Long, MinuteOfDay As Int)
RemoveAutoRowsForDay(DayStart)
Dim firstWorkEnd As Int = Min(MinuteOfDay, AutoPauseStartMinutes)
If firstWorkEnd > AutoStartMinutes Then
AddAutoSegmentNoSave(DayStart, AutoStartMinutes, firstWorkEnd, "lavoro")
End If
Dim pauseEnd As Int = Min(MinuteOfDay, AutoPauseEndMinutes)
If pauseEnd > AutoPauseStartMinutes Then
AddAutoSegmentNoSave(DayStart, AutoPauseStartMinutes, pauseEnd, "pausa")
End If
Dim secondWorkEnd As Int = Min(MinuteOfDay, AutoEndMinutes)
If secondWorkEnd > AutoPauseEndMinutes Then
AddAutoSegmentNoSave(DayStart, AutoPauseEndMinutes, secondWorkEnd, "lavoro")
End If
TrimStatsRows
SaveStatsRows
End Sub
Private Sub AddAutoSegmentNoSave(DayStart As Long, FromMinutes As Int, ToMinutes As Int, Category As String)
Dim duration As Long = (ToMinutes - FromMinutes) * DateTime.TicksPerMinute
If duration <= 0 Then Return
Dim row As Map
row.Initialize
row.Put("dayStart", DayStart)
row.Put("dayKey", DayStart)
row.Put("segmentStart", DayStart + FromMinutes * DateTime.TicksPerMinute)
row.Put("date", DateTime.Date(DayStart))
row.Put("duration", duration)
row.Put("category", Category)
row.Put("source", "auto")
StatsRows.Add(row)
End Sub
Private Sub RemoveAutoRowsForDay(DayStart As Long)
Dim kept As List
kept.Initialize
For Each row As Map In StatsRows
Dim source As String = "manual"
If row.ContainsKey("source") Then source = row.Get("source")
If row.Get("dayStart") = DayStart And source = "auto" Then
' skip regenerated automatic segment
Else
kept.Add(row)
End If
Next
StatsRows.Clear
StatsRows.AddAll(kept)
End Sub
Private Sub ClearDisplayedDay
Dim dayStart As Long = GetDisplayedWorkDayStart
If dayStart = 0 Then
ToastMessageShow(Localization.T("no_day_to_clear"), False)
Return
End If
Dim result As Int = Msgbox2(Localization.T("clear_today_confirm") & " " & FormatDayShort(dayStart) & "?", Localization.T("clear_today_title"), Localization.T("clear_button"), Localization.T("cancel"), "", Null)
If result <> DialogResponse.POSITIVE Then Return
Dim kept As List
kept.Initialize
For Each row As Map In StatsRows
If row.Get("dayStart") <> dayStart Then kept.Add(row)
Next
StatsRows.Clear
StatsRows.AddAll(kept)
SaveStatsRows
If File.Exists(File.DirInternal, StateFileName) Then File.Delete(File.DirInternal, StateFileName)
ResetSessionState(True)
If AutoModeEnabled Then
UpdateAutomaticState(DateTime.Now)
Else
RestoreLastClosedDayState
End If
ToastMessageShow(Localization.T("today_cleared"), False)
End Sub
Private Sub ApplyPauseCountingMode
If chkIncludePauses.IsInitialized And chkIncludePauses.Checked <> IncludePauseInTotal Then chkIncludePauses.Checked = IncludePauseInTotal
Dim now As Long = DateTime.Now
If SessionActive Then
ProductiveElapsedMs = GetProductiveTotalForWorkDay(CurrentWorkDayKey)
If PauseActive Then
ProcessActivePauseSegment(now)
If SessionActive = False Then
UpdateElapsedLabel
Return
End If
CurrentSegmentColor = Colors.Yellow
myClock.ClearSegments
LoadWorkDaySegmentsIntoClock(CurrentWorkDayKey)
myClock.SetActiveSegment(Max(0, now - CurrentSegmentStart), Colors.Yellow, True)
Else
ProcessActiveWorkSegment(now)
If SessionActive = False Then
UpdateElapsedLabel
Return
End If
CurrentSegmentColor = GetCurrentWorkColor
myClock.ClearSegments
LoadWorkDaySegmentsIntoClock(CurrentWorkDayKey)
myClock.SetActiveSegment(Max(0, now - CurrentSegmentStart), CurrentSegmentColor, False)
End If
SaveActiveState
Else
If CurrentWorkDayKey > 0 Then
ProductiveElapsedMs = GetProductiveTotalForWorkDay(CurrentWorkDayKey)
CurrentSegmentColor = GetCurrentWorkColor
myClock.ClearSegments
LoadWorkDaySegmentsIntoClock(CurrentWorkDayKey)
myClock.ClearActiveSegment
Else
RestoreLastClosedDayState
End If
End If
UpdateElapsedLabel
UpdateMainControlState
End Sub
Private Sub ParseBooleanValue(Value As Object, DefaultValue As Boolean) As Boolean
If Value = Null Then Return DefaultValue
Dim s As String = Value
s = s.Trim.ToLowerCase
If s = "true" Then Return True
If s = "false" Then Return False
Return DefaultValue
End Sub
Private Sub AddStatsRow(y As Int, DateText As String, WorkText As String, PauseText As String, OvertimeText As String, Header As Boolean)
Dim rowHeight As Int = 30dip
Dim col1 As Int = 104dip
Dim col As Int = (Activity.Width - col1) / 3
AddCell(DateText, 0, y, col1, rowHeight, Header)
AddCell(WorkText, col1, y, col, rowHeight, Header)
AddCell(PauseText, col1 + col, y, col, rowHeight, Header)
AddCell(OvertimeText, col1 + 2 * col, y, col, rowHeight, Header)
End Sub
Private Sub AddCell(Text As String, x As Int, y As Int, w As Int, h As Int, Header As Boolean)
Dim lbl As Label
lbl.Initialize("")
lbl.Text = Text
lbl.TextSize = 12
lbl.TextColor = Colors.Black
lbl.Gravity = Gravity.CENTER
If Header Then
lbl.Color = Colors.RGB(230, 230, 230)
Else
lbl.Color = Colors.White
End If
svStats.Panel.AddView(lbl, x, y, w, h)
End Sub
Private Sub BuildHelpText As String
Return $"Overtime Guard
By AI Team Studio
aiteamstudio.it
AI agents and software systems
Purpose
Overtime Guard is a minimal workday timer. It helps you track work time, breaks, and overtime without projects, tasks, or extra screens.
Main screen
- Start begins manual recording for the current workday.
- End stops manual recording.
- Pause starts a break.
- End pause closes the break and resumes work.
- Bg sends the app to the Android home screen.
- Stats opens the daily totals table.
- Config opens automatic schedule settings.
Clock colors
- Light blue: first 4 hours of productive work.
- Green: productive work from hour 5 to hour 8.
- Yellow: break time.
- Red: overtime after 8 productive hours.
How timing works
- Breaks always stay visible in yellow.
- In Config you can choose whether break time is included in the total 8-hour count.
- Overtime starts only after 8 productive work hours.
- Red overtime can continue for up to 1 extra hour.
- At 9 total productive hours, recording stops automatically.
- If midnight is reached, the current day is closed and the next day starts from zero when you start again.
Daily memory
- The app saves the current day locally.
- If you close or background the app, the state is restored when you open it again.
- During the day, multiple work and break segments are added to the same daily record.
Statistics
- The stats page groups totals by date.
- Each row shows Work, Pause, and Overtime for one day.
- The totals row sums all visible stored days.
- Local history is currently kept for about two months.
Automatic mode
- In Config you can set work start, pause start, pause end, and work end.
- The Include breaks in total option also affects automatic mode.
- Set config enables automatic mode.
- In automatic mode, Start and End on the main screen are disabled.
- Reset config disables automatic mode and returns control to manual mode.
Clear today
- Clear today removes only the currently displayed workday from local history.
- Use it only if you want to restart the day from zero.
Notes
- The app uses the phone local time and time zone.
- The help page is informational only. It does not change your data.
"$
End Sub