Skip to content

Commit bb3de54

Browse files
authored
docs: update 2025-12 (#131)
* docs: current arch and planned modernaztion stuff * docs: reorg * docs: update root readme
1 parent d48fa46 commit bb3de54

19 files changed

Lines changed: 867 additions & 7 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ dist
138138

139139

140140
#TypeScript stuff
141-
build/
141+
/build/
142142

143143
# tmp dir
144144
tmp/
@@ -162,6 +162,7 @@ out/
162162
# Gradle files
163163
.gradle/
164164
build/
165+
!docs/build/
165166

166167
# Local configuration file (sdk path, etc)
167168
local.properties

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@ When snoozing events, you can snooze them a desired amount of time or quickly re
2222
- **Smart Snooze**: Easily snooze calendar notifications to a time that works for you
2323
- **Notification Persistence**: Never miss an event - notifications persist until you handle them
2424
- **Quick Rescheduling**: Reschedule non-repeating events for the next day or week with just one click
25-
- **Quiet Hours**: Set periods when notifications won't disturb you
2625
- **Missed Event Reminders**: Optional reminders for notifications you might have missed (configurable intervals)
2726
- **Bulk Actions**: "Snooze All" button to manage multiple notifications at once
2827
- **Customization**: Custom LED colors and screen wake options for notifications
2928
- **Reboot Protection**: All your notifications are automatically restored after device restart
3029

31-
### ☁️ NEW! (March 2025)
32-
- **Data Backup**: Unidirectional sync to backup your notification data to the cloud (via Supabase/PowerSync)
30+
### ☁️ Data Backup (March 2025)
31+
- **Cloud Sync**: Unidirectional sync to backup your notification data to the cloud (via Supabase/PowerSync)
3332

3433

3534
<p align="center">
@@ -62,9 +61,10 @@ The app requires the following permissions for core functionality:
6261

6362
If you're interested in contributing or building the app yourself, check out our developer documentation:
6463

65-
- [Build Instructions](docs/BUILD.md)
64+
- [Full Documentation Index](docs/README.md)
65+
- [Build Instructions](docs/build/BUILD.md)
6666
- [Data Sync Guide](docs/DATA_SYNC_README.md)
67-
- [Debugging Guide](docs/DEBUG.md)
67+
- [Debugging Guide](docs/build/DEBUG.md)
6868

6969
### Automated Testing
7070

@@ -77,7 +77,7 @@ We use GitHub Actions for continuous integration with automated test reporting:
7777
- JaCoCo coverage analysis with coverage badges and PR comments
7878
- Coverage thresholds ensure code quality is maintained
7979

80-
For more details, see our [Test Reporting Documentation](docs/TEST_REPORTING.md).
80+
For more details, see our [Test Reporting Documentation](docs/testing/TEST_REPORTING.md).
8181

8282
## 💖 Contributing
8383

docs/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Calendar Notifications Plus Documentation
2+
3+
## Architecture & Core Concepts
4+
5+
- [Architecture Overview](architecture/architecture_overview.md) - GlobalState and ApplicationController pattern
6+
- [Calendar Monitoring](architecture/calendar_monitoring.md) - How event detection works (detailed flows)
7+
- [Clock Implementation](architecture/clock_implementation.md) - CNPlusClockInterface for testable time
8+
9+
## Build & Development
10+
11+
- [Build Instructions](build/BUILD.md) - Setting up and building the app
12+
- [CR-SQLite Build](build/CR_SQLITE_BUILD.md) - Building cr-sqlite native library
13+
- [Debug Guide](build/DEBUG.md) - Debugging tips (Chrome/Hermes limitations)
14+
- [WSL Log Cleaning](build/wsl_test_log_clean.md) - Log cleaning scripts for sharing
15+
16+
## Data & Sync
17+
18+
- [Data Sync Setup](DATA_SYNC_README.md) - Supabase/PowerSync configuration
19+
20+
## Testing Infrastructure
21+
22+
- [Test Development Chronology](testing/test_development_chronology.md) - History and reading order
23+
- [Test Fixture Refactoring](testing/test_fixture_refactoring.md) - Modular test infrastructure
24+
- [Test Reporting](testing/TEST_REPORTING.md) - CI test reporting with JaCoCo
25+
- [Test Sharding](testing/test_sharding.md) - Parallel test execution
26+
- [Async Task Idling Resource](testing/async_task_idling_resource.md) - Espresso synchronization
27+
28+
## Completed RFCs (`dev_completed/`)
29+
30+
Historical decisions and completed work, kept for reference:
31+
32+
- [Constructor Mocking Limitations](dev_completed/constructor-mocking-android.md)*Key Reference*
33+
- [Calendar Backup/Restore Test Isolation](dev_completed/calendar_backup_restore_test_isolation.md)
34+
- [Event Dismissal Testing Notes](dev_completed/event_dismissal_testing_notes.md)
35+
- [Expo Router Migration Decision](dev_completed/expo_router_migration.md) - Evaluated & intentionally deferred
36+
- [Intermittent Build Issue Fix](dev_completed/intermittent_build_issue_resources_npe.md)
37+
- [Mock Calendar Provider Refactor](dev_completed/mock_calendar_provider_refactor.md)
38+
- [SQLite Mocking in Robolectric](dev_completed/sqlite-mocking-robolectric.md)
39+
40+
## TODO/In Progress (`dev_todo/`)
41+
42+
Features and changes under consideration:
43+
44+
- [Deprecated Features Removal](dev_todo/deprecated_features.md) - QuietHours, CalendarEditor
45+
- [Android Modernization](dev_todo/android_modernization.md) - Coroutines, Hilt, Room overview
46+
- [Room Database Migration](dev_todo/room_database_migration.md) - SQLite → Room
47+
- [Raise Min SDK](dev_todo/raise_min_sdk.md) - API 24 → 26+ considerations
48+
- [Event Deletion Issues](dev_todo/event_deletion_issues.md) - Cleanup mechanisms
49+
- [Event Restore Behavior](dev_todo/event_restore_behavior.md) - Restoration logic
50+
- [Reschedule Confirmation Handling](dev_todo/reschedule_confirmation_handling.md) - UI improvements
51+
- [Test Exception Fallback](dev_todo/test_exception_fallback.md) - XML results file behavior on crash
52+
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Architecture Overview: GlobalState and ApplicationController
2+
3+
## Summary
4+
5+
This app uses a **non-standard but sensible** architecture that separates Android lifecycle concerns from business logic. This document explains how the two core classes interact.
6+
7+
## The Two Core Classes
8+
9+
### GlobalState - The Android Application Class
10+
11+
**Location:** `android/app/src/main/java/com/github/quarck/calnotify/GlobalState.kt`
12+
13+
**What it is:** The actual Android `Application` subclass - the entry point that Android creates when the app process starts.
14+
15+
**Responsibilities (minimal by design):**
16+
- Holds only ephemeral/instance-specific state:
17+
- `lastNotificationRePost` - when notifications were last refreshed
18+
- `lastTimerBroadcastReceived` - when last timer broadcast arrived
19+
- Initializes React Native (this is a hybrid RN app)
20+
- Initializes SoLoader for native code
21+
- Applies theme preferences on startup
22+
- Creates notification channels (required for Android 8+)
23+
24+
**Extension property for access:**
25+
```kotlin
26+
val Context.globalState: GlobalState?
27+
get() {
28+
val appCtx = applicationContext
29+
if (appCtx is GlobalState)
30+
return appCtx
31+
return null
32+
}
33+
```
34+
35+
This allows any code with a `Context` to access `context.globalState?.lastNotificationRePost`.
36+
37+
### ApplicationController - The Business Logic Singleton
38+
39+
**Location:** `android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt`
40+
41+
**What it is:** A Kotlin `object` (singleton) - **not** an Android component! It's just a regular Kotlin object that holds all the app's business logic.
42+
43+
**Responsibilities (extensive):**
44+
- Event management (register, dismiss, snooze, restore)
45+
- Notification management
46+
- Alarm scheduling
47+
- Calendar monitoring
48+
- Quiet hours management
49+
- All coordination between subsystems
50+
51+
**Key design pattern:** Receives `Context` as a parameter rather than holding one:
52+
```kotlin
53+
object ApplicationController : ApplicationControllerInterface {
54+
fun onBootComplete(context: Context) { ... }
55+
fun onCalendarChanged(context: Context) { ... }
56+
fun dismissEvent(context: Context, ...) { ... }
57+
}
58+
```
59+
60+
This avoids memory leaks and makes the code more testable.
61+
62+
## How They Interact
63+
64+
```
65+
┌─────────────────────────────────────────────────────────────────┐
66+
│ Android OS │
67+
└────────────────────────────┬────────────────────────────────────┘
68+
│ creates on app start
69+
70+
┌─────────────────────────────────────────────────────────────────┐
71+
│ GlobalState (Application) │
72+
│ - React Native host │
73+
│ - Ephemeral state (lastNotificationRePost, etc.) │
74+
│ - Theme/Notification channel init │
75+
└─────────────────────────────────────────────────────────────────┘
76+
77+
┌─────────────────────┐ ┌─────────────────────┐ ┌───────────────┐
78+
│ BroadcastReceivers │ │ Activities │ │ Services │
79+
│ - Boot │ │ - MainActivity │ │ - Snooze │
80+
│ - Calendar changed │ │ - ViewEventActivity│ │ - Dismiss │
81+
│ - Alarms │ │ - Settings │ │ - etc. │
82+
└─────────┬───────────┘ └──────────┬──────────┘ └───────┬───────┘
83+
│ │ │
84+
│ All call into │ │
85+
└─────────────────────────┼──────────────────────┘
86+
87+
┌─────────────────────────────────────────────────────────────────┐
88+
│ ApplicationController (object/singleton) │
89+
│ - ALL business logic │
90+
│ - Event management │
91+
│ - Notification management │
92+
│ - Calendar monitoring │
93+
│ - Alarm scheduling │
94+
└─────────────────────────────────────────────────────────────────┘
95+
```
96+
97+
## Entry Points
98+
99+
The key insight is that **Android components call ApplicationController**, not the other way around:
100+
101+
### BroadcastReceivers
102+
```kotlin
103+
// BootCompleteBroadcastReceiver.kt
104+
class BootCompleteBroadcastReceiver : BroadcastReceiver() {
105+
override fun onReceive(context: Context?, intent: Intent?) {
106+
if (context != null)
107+
ApplicationController.onBootComplete(context)
108+
}
109+
}
110+
111+
// CalendarChangedBroadcastReceiver.kt
112+
class CalendarChangedBroadcastReceiver : BroadcastReceiver() {
113+
override fun onReceive(context: Context?, intent: Intent?) {
114+
if (context != null)
115+
ApplicationController.onCalendarChanged(context)
116+
}
117+
}
118+
```
119+
120+
### Activities
121+
```kotlin
122+
// MainActivity.kt
123+
override fun onCreate(savedInstanceState: Bundle?) {
124+
super.onCreate(savedInstanceState)
125+
ApplicationController.onMainActivityCreate(this)
126+
}
127+
128+
override fun onResume() {
129+
super.onResume()
130+
ApplicationController.onMainActivityResumed(this, shouldForceRepost, monitorSettingsChanged)
131+
}
132+
```
133+
134+
### Services
135+
```kotlin
136+
// NotificationActionDismissService.kt
137+
ApplicationController.dismissEvent(context, dismissType, event)
138+
```
139+
140+
## Why This Pattern?
141+
142+
### 1. Multiple Entry Points
143+
Calendar notification apps respond to many system events:
144+
- Phone boots → need to restore notifications
145+
- Calendar changes → need to check for new/moved events
146+
- Snooze alarm fires → need to re-show notification
147+
- User taps notification → need to handle action
148+
- Time zone changes → need to recalculate times
149+
- App updates → need to refresh state
150+
151+
All these entry points need **consistent behavior**. Centralizing logic in one place ensures this.
152+
153+
### 2. Separation of Concerns
154+
- **GlobalState:** Android lifecycle only (Application class boilerplate)
155+
- **ApplicationController:** All business logic (pure Kotlin, no Android lifecycle)
156+
157+
### 3. Testability
158+
The ApplicationController pattern with injectable providers makes testing easier:
159+
```kotlin
160+
// In ApplicationController
161+
var eventsStorageProvider: ((Context) -> EventsStorageInterface)? = null
162+
163+
private fun getEventsStorage(ctx: Context): EventsStorageInterface {
164+
return eventsStorageProvider?.invoke(ctx) ?: EventsStorage(ctx)
165+
}
166+
167+
// In tests
168+
ApplicationController.eventsStorageProvider = { mockEventsStorage }
169+
```
170+
171+
### 4. Avoiding Memory Leaks
172+
By receiving `Context` as a parameter rather than storing it, there's no risk of holding onto an Activity context after it's destroyed.
173+
174+
## Comparison to "Standard" Simple Apps
175+
176+
A typical simple Android app might look like:
177+
178+
```kotlin
179+
class MainActivity : AppCompatActivity() {
180+
override fun onCreate(savedInstanceState: Bundle?) {
181+
// UI setup
182+
val button = findViewById<Button>(R.id.myButton)
183+
184+
// Business logic mixed right in
185+
button.setOnClickListener {
186+
val items = loadItemsFromDatabase() // DB logic here
187+
showNotification(items.size) // Notification logic here
188+
updateUI(items) // UI logic here
189+
}
190+
}
191+
}
192+
```
193+
194+
This works for simple apps with one entry point (user opens app). It breaks down when you have 10+ entry points that all need the same behavior.
195+
196+
## Historical Context
197+
198+
This codebase was started in 2016, before Google provided official architecture guidance (Architecture Components came in 2017). The original author created a sensible pattern that predates the official recommendations.
199+
200+
The pattern is actually **ahead of its time** in some ways - the separation of concerns and injectable providers are exactly what modern architecture recommends, just implemented differently.
201+
202+
## Modern Equivalent
203+
204+
Today, a similar architecture would use:
205+
- **ViewModel** instead of singleton object (for lifecycle awareness)
206+
- **Repository pattern** for data access
207+
- **Hilt** for dependency injection (instead of manual providers)
208+
- **Coroutines** for async operations
209+
210+
See `docs/dev_todo/android_modernization.md` for migration considerations.
211+
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)