A cross-platform time-management application built with Flutter: manage tasks, calendar events, notes, and a Pomodoro focus timer, plus supporting features such as trash, archive, backup/restore, and calendar synchronization.
- Supported platforms: Android, iOS, Windows, Linux, macOS, Web (some features are platform-dependent — see Platform notes).
- Version:
1.5.3+23(seepubspec.yaml). - Storage: local-first with SQLite; optional cloud sync via Firebase.
- Screenshots
- Features
- User guide
- Getting started (for developers)
- Project architecture
- Database schema
- Calendar sync flow
- Platform notes
- Notable dependency constraints
Images are stored in the
res/folder of the repository.
Overview & task types
| Overview | Task | Measurable task | Task with subtasks |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Planning & timeline
| Planned | Set time interval | Plan tracking | Timeline |
|---|---|---|---|
![]() |
![]() |
![]() |
Calendar views
| Daily | Weekly | Monthly |
|---|---|---|
![]() |
![]() |
![]() |
Light / dark mode
- Task management: create/edit tasks that belong to task lists. A single task can be:
- a plain task,
- a measurable task (
isMeasurable: at-least / at-most / about target, unit, progress done), - a task with subtasks — each subtask can itself be measurable and be scheduled independently.
- Time planning (time intervals): attach time ranges (start/end date and time) to a task or subtask; supports notification reminders.
- Calendar: a
CalendarEventmodel compatible with Google Calendar (supports lists, soft-delete, archive, and sync fields). - Notes: block/rich-text editor based on
appflowy_editor. - Focus timer (Pomodoro): a globally running focus timer with a floating (PiP) window, alarm sound, and system notifications.
- Trash: soft-delete with automatic purge after a configurable retention period (default 30 days).
- Archive: hide tasks/lists from the main screens without deleting them.
- Backup & restore: export/import data (JSON/CSV/text) via
backup_service/import_service; every record carries adeviceIdto help resolve conflicts on import. - Calendar sync: with the device calendar and with Google Calendar.
- Authentication (optional): Firebase Auth (email/password, Google Sign-In). The app still runs if Firebase is not configured.
- UI customization: Material 2/3, light/dark/system, color from a seed or from an image, optional bottom bar, and 13 languages (cs, de, en, es, fr, it, ja, ko, no, ru, sv, vi, zh).
This section describes end-user usage. The concrete screen layout is driven by
Home(bottom navigation bar or side rail, depending on settings).
Tasks & lists
- Open the Tasks screen to view lists and tasks (Overview / Timeline).
- Create a new task. To track it quantitatively, enable the measurable target (enter a target and a unit). To break it down, add subtasks.
- On the task edit page, use the "Schedule" tile to set time intervals; you can add notification reminders.
Calendar
- The Calendar screen shows events. An event can belong to a list and can be synced with the device calendar and Google Calendar (once configured / signed in).
Notes
- The Notes screen lets you compose rich-text notes. Notes can be attached to tasks/time intervals.
Focus timer (Pomodoro)
- Pick a task (or none) and start a focus session. Enable the run-in-background option so the timer keeps running when you leave the screen; a floating window shows progress.
Trash & archive
- Deleted items go to Trash and can be restored; the system auto-purges them after the configured retention period.
- Use Archive to hide items from the main screens while keeping their data.
Backup / restore
- Go to Settings to export data (JSON/CSV/text) or import from a file. Records carry a
deviceIdto help merge data imported from another device.
Appearance & language
- In Settings: switch light/dark, Material 2/3, choose colors (seed or image based), task card style, which buttons appear in the AppBar, and the language.
Requirements
- Flutter SDK compatible with Dart
>=3.0.1 <4.0.0. - On Windows desktop: a C++/CMake build toolchain (SQLite runs through FFI; the DLL is bundled by
sqlite3_flutter_libs).
Install & run
flutter pub get
flutter run # choose an available device/platformStatic analysis
flutter analyzeTemporary backup folders are excluded from analysis in
analysis_options.yaml.
Firebase (optional)
- If you have not run
flutterfire configure,firebase_options.dartis a placeholder and the app runs in no-auth mode (sign-in is skipped, local features still work). Once configured, sign-in and Firestore sync become available.
Code generation (if you change models that use json_serializable)
dart run build_runner build --delete-conflicting-outputsThe app follows a local-first model with a domain-driven boundary between the UI and the database:
UI (screens / component_widgets / view)
│ depends only on domain abstractions
State management (BLoC / Cubit / ChangeNotifier)
│
Domain layer (lib/domain)
├─ Repository interfaces (TaskRepository, TaskListRepository,
│ TimeIntervalRepository, CalendarRepository,
│ NoteRepository, TrashRepository,
│ ArchiveRepository, EventRepository)
└─ Domain services (TaskCompletionService, TimeIntervalProgressService,
RecurrenceService — the business rules)
│ implemented by
Data layer (lib/data/repositories)
└─ Sqlite* adapters (thin, 1:1 delegation to DatabaseManager)
│
DatabaseManager (lib/data/database) — schema, CRUD, migration, trash/archive,
│ reminders, export/import table list
Persistence (SQLite via sqflite / sqflite_common_ffi) + Optional cloud (Firebase Auth / Firestore)
- UI / Bloc / Controller never touch
DatabaseManageror SQL directly. They depend on the domain repository interfaces and domain services, which are provided app-wide viaMultiRepositoryProviderinapp/app.dart. The only remaining non-data references toDatabaseManageraremain.dart(setCurrentDeviceId, a startup config call) andFocusRepository(which is a repository). - Business rules live in domain services, not in widgets. Task/interval completion, measurable progress, and recurrence expansion were moved out of the UI so that both the UI and — in the future — an AI agent invoke the exact same rules. See Agent-readiness.
- Cloud is optional: if Firebase is not ready, the sync layers use NoOp implementations so the app runs fully offline.
lib/
├── main.dart # Entry point: init notifications, deviceId, SQLite FFI, purge trash, Firebase
├── firebase_options.dart # Firebase configuration (placeholder until configured)
├── app/
│ ├── app.dart # Root App widget: MaterialApp, theme, locale, AppBloc, global providers
│ └── app_localizations.dart # Translations (13 languages)
├── auth/ # Authentication (Firebase): bloc/ data/ view/, auth_config.dart
├── domain/ # Domain boundary (no Flutter/SQLite imports in interfaces)
│ ├── repositories/ # Repository interfaces (domain capabilities, not tables)
│ └── services/ # Business rules: completion, progress, recurrence + repeat_rule
├── data/
│ ├── database/ # database_manager.dart (schema, CRUD, migration), sample_data.dart
│ ├── repositories/ # Sqlite* adapters implementing the domain interfaces
│ ├── models/ # Data models (see below)
│ ├── backup/ # backup_service.dart (export), import_service.dart (import)
│ ├── sync/ # calendar_sync_manager + device/google calendar sync services
│ ├── device_calendar/ # device_calendar_service.dart (read/write the system calendar)
│ └── device_id_provider.dart# Device identity for export/import
├── home/ # Home shell, data_controller (ChangeNotifier), timetable
├── screen_tasks/ # Task screens (overview, timeline) + component_widgets
├── screen_calendar/ # Calendar screens
├── screen_notes/ # Notes (appflowy_editor)
├── screen_focus_timer/ # Pomodoro: bloc/ data/ view/ + timer_bloc.dart
├── screen_archive/ # Archive
├── screen_trash/ # Trash
├── screen_settings/ # Settings
├── screen_about_us/ # About
├── screen_material_design/ # Material Design showcase
├── ads_mob/ # AdMob integration (currently disabled in main.dart)
├── shared/ # appflowy_description.dart (helper for the description editor)
└── utils/ # constants.dart, utils.dart, widget_coming_soon.dart
packages/
├── time_manager/ # Local plugin (rrule, timezone) — time/recurrence logic, has native code
└── calendar_widgets/ # Local package — customized calendar widgets (table calendar)
Repeated naming convention across features: bloc/ (state), data/ (repository/service), view/ (UI), and component_widgets/ (reusable child widgets).
lib/domain is the contract the rest of the app depends on. It contains no direct SQLite or DatabaseManager references.
- Repository interfaces (
lib/domain/repositories/) model domain capabilities, not database tables. Trash and archive, for example, are exposed as capabilities of the entity repositories (softDeleteTask,archiveTask, …), plus two cross-entity aggregate repositories —TrashRepositoryandArchiveRepository— because the "all trashed / all archived items" view genuinely spans tasks, task lists, and calendar events.EventRepositoryis intentionally minimal (onlygetEventsOfTaskList) since that is the sole caller of the legacyeventstable. - Domain services (
lib/domain/services/) hold business rules that used to be inline in widgets:TaskCompletionService— toggle/set completion, measurable progress for aTask.TimeIntervalProgressService— toggle completion / measurable progress for aTimeInterval.RecurrenceService(+repeat_rule.dart) — expands a repeating interval into independent occurrences and persists them.
DatabaseManager(lib/data/database/database_manager.dart): a singleton that opens SQLite, defines the schema, provides CRUD for every table, and manages version-based migration. It also owns soft-delete/trash, archive, reminders, and the list of tables used for export/import. Its implementation, schema, migrations, triggers, backup format, and sync semantics were left unchanged by the domain refactor.- Sqlite* repositories (
lib/data/repositories/): thin adapters that implement the domain interfaces by delegating 1:1 toDatabaseManager(e.g.SqliteTaskRepository,SqliteCalendarRepository,SqliteTrashRepository). Each takes an optionalDatabaseManager?(defaulting to the singleton) so tests can drive them against an in-memory database. Side effects such as reminder scheduling oncreateTimeIntervalare preserved inside these adapters. - Services:
backup/backup_service.dart— exports data (JSON/CSV/text). CSV usesCsvEncoderfrom thecsv8.x package.backup/import_service.dart— imports data from a file.sync/—calendar_sync_manager.dartorchestrates;device_calendar_sync_service.dartandgoogle_calendar_sync_service.dartare the two sync backends.device_calendar/device_calendar_service.dart— reads/writes the system calendar (mobile only).
device_id_provider.dart— generates/loadsdeviceId; every record written on this machine carries thedeviceIdto help resolve conflicts on import.
In lib/data/models/:
model_task.dart— the unifiedTaskmodel (core). The app previously split tasks into 3 types (Task,MeasurableTask,TaskWithSubtasks); they are now merged into oneTask:isMeasurable = true⇒ the task has a measurement target (targetType/targetAtLeast/targetAtMost/unit/howMuchHasBeenDone).- a non-empty
subtaskslist ⇒ the task has subtasks (hasSubtasksis a convenience getter). Subtaskhasid,title,isCompleted, and can also be measurable.TargetType:atLeast,atMost,about.
model_measurable_task.dart/model_task_with_subtasks.dart— now just re-export files for backward compatibility, exportingTask/Subtask/TargetTypefrommodel_task.dart.model_list.dart—TaskList(a list that holds tasks/events).model_time_interval.dart—TimeInterval(a planned time range), linked to a task viataskIdand to a subtask viasubtaskId.model_calendar_event.dart—CalendarEvent, compatible with Google Calendar.model_note.dart—Note.models.dart— barrel export.
A mix of mechanisms, chosen by scope:
- BLoC / Cubit (
bloc,flutter_bloc):AppBloc(inapp/app.dart) — app-wide UI configuration (theme, color, language, settings), persisted throughSharedPreferences.AuthBloc(auth/bloc) — sign-in state.PomodoroBloc— provided globally (a single instance that lives for the app's whole lifetime) so the timer keeps running across screens.
ChangeNotifier:home/data_controller.dart(DataController) manages the calendar/time-interval display data; it is constructor-injected with aTimeIntervalRepository.- Repository/Service:
FocusRepository(local-first) with a Firestore sync layer enabled when Firebase is ready, otherwise aNoOpFocusSyncService. - Dependency injection: domain repositories and services are provided once, app-wide, by a
MultiRepositoryProviderinapp/app.dart. Blocs and controllers receive them via constructor injection (e.g.AppBloctakes aTimeIntervalRepositoryfor reminder rescheduling), and widgets read them withcontext.read<...>().
Sequence in main.dart:
WidgetsFlutterBinding.ensureInitialized().- Initialize
NotificationService(loads timezone) and request notification permissions. - Initialize
DeviceIdProviderand set thedeviceIdonDatabaseManager. - On desktop:
sqfliteFfiInit()+ setdatabaseFactory = databaseFactoryFfi. - Purge expired trash (
purgeExpiredTrash; retention days read fromSharedPreferences). - Initialize Firebase inside a
try/catch; if it fails or is unconfigured,firebaseReady = false. runApp(App(...)).
App (in app/app.dart) builds MaterialApp with theme/locale from AppBloc, provides FocusRepository + PomodoroBloc globally, and:
- If Firebase is ready ⇒ provides
AuthRepository/AuthBlocand wrapsHomeinAuthGate(redirects based on sign-in state). There is anAuthConfig.bypassAuthflag to go straight toHomeduring testing. - Otherwise ⇒ goes straight to
Home(no-auth mode). - Every screen is wrapped in
PomodoroFloatingOverlay(the floating Pomodoro window).
AdMob (
ads_mob/,google_mobile_ads) is currently disabled inmain.dart; re-enable it following the comment at the end ofmain()once you have a valid App ID.
Located in packages/, referenced by path in pubspec.yaml so the project is self-contained:
time_manager— an internal plugin (declares native code for Android/iOS/Windows/…): handles time and recurrence logic (rrule,timezone).calendar_widgets— a calendar-widget package (a table-calendar variant) used by the calendar screens.
The domain boundary was designed so a future AI agent can drive the app the same way the UI does — through domain services and repositories, never by touching DatabaseManager or SQL. The intended call chain is:
AI Tool -> Domain Service / Repository -> Data Repository (Sqlite* adapter) -> DatabaseManager -> SQLite
Because business rules (completion, measurable progress, recurrence) live in domain services, an agent that calls those services cannot bypass them to write inconsistent state directly to the database.
This is verified by test/domain/agent_readiness_proof_test.dart, which builds a small _AgentToolbox from domain abstractions only and runs a full lifecycle (create project → add task → complete → archive → move to trash → restore) against a real in-memory SQLite database. It also confirms that repositories constructed independently still read/write the same store.
Known platform coupling:
TimeIntervalcreate/update schedules reminders viaNotificationService, which instantiates plugin channels (audio / local notifications). That path needs a Flutter binding and channel mocks, so an agent running headless must either provide those or route interval scheduling through a UI-bound context. The pure recurrence-expansion rule itself is covered headlessly inrecurrence_service_test.dart.
- Domain-service tests (
test/domain/) use in-memory fakes of the repository interfaces — fast and DB-free. - Repository contract tests (
test/data/) run the realSqlite*adapters against an in-memory SQLite database viaDatabaseManager.resetForTesting(inMemory: true)(a@visibleForTestingseam that does not change any production path). They cover task, task-list (including the timestamp-matched cascade restore/unarchive of child tasks), calendar, note, and the cross-entity trash/archive aggregates. - Because
DatabaseManageris a process-wide singleton with a shared handle, the full suite is pinned to serial execution indart_test.yaml(concurrency: 1). Run the whole suite withflutter test; individual DB files also pass in isolation (e.g.flutter test test/data/sqlite_task_repository_test.dart).
- Current schema version: 11 (see
_initDatabase/_onCreate/_onUpgrade). - SQLite runs through FFI on desktop (
sqflite_common_ffi). Foreign keys are enabled (PRAGMA foreign_keys = ON). - Nested objects (subtasks, attendees, reminders, recurrence, etc.) are stored as JSON strings in
TEXTcolumns.
tasklists — task/event lists.
| Column | Type | Notes |
|---|---|---|
| id | TEXT | Primary key |
| title | TEXT | |
| description | TEXT | Plain-text description |
| descriptionDoc | TEXT | appflowy_editor JSON (rich text) |
| color | INTEGER | ARGB value |
| dataFiles | TEXT | JSON list of attachment paths |
| updateTimeStamp | TEXT | |
| isDeleted | INTEGER | Soft-delete, default 0 |
| deletedAt | TEXT | |
| isArchived | INTEGER | Default 0 |
| archivedAt | TEXT | |
| deviceId | TEXT | Source device |
tasks — the unified task model.
| Column | Type | Notes |
|---|---|---|
| id | TEXT | Primary key |
| taskListId | TEXT | FK → tasklists(id) ON DELETE CASCADE |
| isCompleted | INTEGER | NOT NULL |
| isImportant | INTEGER | NOT NULL |
| title | TEXT | |
| description | TEXT | |
| descriptionDoc | TEXT | Rich-text JSON |
| location | TEXT | |
| color | INTEGER | |
| tags | TEXT | JSON list |
| dataFiles | TEXT | JSON list |
| noteIds | TEXT | JSON list of attached note ids |
| isMeasurable | INTEGER | NOT NULL, default 0 |
| targetAtLeast | REAL | |
| targetAtMost | REAL | |
| targetType | INTEGER | TargetType index |
| unit | TEXT | |
| howMuchHasBeenDone | REAL | |
| subtasks | TEXT | JSON list of Subtask |
| updateTimeStamp | TEXT | |
| isDeleted / deletedAt | INTEGER / TEXT | Soft-delete |
| isArchived / archivedAt | INTEGER / TEXT | Archive |
| deviceId | TEXT |
timeintervals — planned time ranges for a task or subtask.
| Column | Type | Notes |
|---|---|---|
| id | TEXT | Primary key |
| isCompleted | INTEGER | NOT NULL |
| isImportant | INTEGER | NOT NULL |
| taskId | TEXT | FK → tasks(id) ON DELETE CASCADE |
| subtaskId | TEXT | Points to a subtask (subtasks live inside tasks.subtasks) |
| location | TEXT | |
| color | INTEGER | |
| title | TEXT | |
| description | TEXT | |
| descriptionDoc | TEXT | Rich-text JSON |
| startDate / endDate | TEXT | |
| startTime / endTime | INTEGER | Minutes-of-day (nullable) |
| isStartDateUndefined | INTEGER | NOT NULL |
| isEndDateUndefined | INTEGER | NOT NULL |
| isStartTimeUndefined | INTEGER | NOT NULL |
| isEndTimeUndefined | INTEGER | NOT NULL |
| targetAtLeast / targetAtMost | REAL | |
| targetType | INTEGER | |
| unit | TEXT | |
| howMuchHasBeenDone | REAL | |
| subtasks | TEXT | JSON list |
| dataFiles | TEXT | JSON list |
| noteIds | TEXT | JSON list |
| updateTimeStamp | TEXT | |
| timeZone | TEXT | |
| deviceId | TEXT | |
| reminders | TEXT | JSON list of minutes-before for notifications |
calendar_events — Google-Calendar-compatible events (added in v11).
| Column | Type | Notes |
|---|---|---|
| id | TEXT | Primary key |
| taskListId | TEXT | FK → tasklists(id) ON DELETE CASCADE |
| title / description / location | TEXT | |
| color | INTEGER | |
| colorId | TEXT | Google color id |
| startDateTime / endDateTime | TEXT | |
| isAllDay | INTEGER | NOT NULL, default 0 |
| recurrence | TEXT | JSON (RRULE lines) |
| recurringEventId | TEXT | |
| originalStartTime | TEXT | |
| attendees | TEXT | JSON list |
| organizerEmail / organizerDisplayName / creatorEmail | TEXT | |
| status / visibility / transparency | TEXT | |
| guestsCanInviteOthers | INTEGER | NOT NULL, default 1 |
| guestsCanModify | INTEGER | NOT NULL, default 0 |
| guestsCanSeeOtherGuests | INTEGER | NOT NULL, default 1 |
| reminders | TEXT | JSON list |
| conferenceUrl | TEXT | |
| dataFiles / noteIds | TEXT | JSON list |
| iCalUID | TEXT | |
| googleEventId | TEXT | Remote event id (set once synced) |
| calendarId | TEXT | |
| etag | TEXT | For optimistic concurrency (If-Match) |
| sequence | INTEGER | NOT NULL, default 0 |
| created / updated | TEXT | |
| syncStatus | TEXT | EventSyncStatus (localOnly / pendingUpdate / pendingDelete / synced) |
| isDeleted / deletedAt | INTEGER / TEXT | Soft-delete |
| isArchived / archivedAt | INTEGER / TEXT | Archive |
| deviceId | TEXT |
events — a simpler legacy event table (kept as-is).
| Column | Type | Notes |
|---|---|---|
| id | TEXT | Primary key |
| taskListId | TEXT | FK → tasklists(id) ON DELETE CASCADE |
| title / description / location | TEXT | |
| color | INTEGER | |
| startTimeStamp / endTimeStamp | INTEGER | |
| tags / dataFiles | TEXT | JSON list |
| updateTimeStamp | TEXT | |
| deviceId | TEXT |
focus_sessions — recorded Pomodoro sessions (no FK: taskId may reference different sources or be null; taskTitle is stored so stats survive task deletion).
| Column | Type | Notes |
|---|---|---|
| id | TEXT | Primary key |
| taskType | INTEGER | NOT NULL |
| taskId | TEXT | |
| taskTitle | TEXT | Snapshot of the task title |
| startTime / endTime | TEXT | NOT NULL |
| focusDurationSeconds | INTEGER | NOT NULL |
| completed | INTEGER | NOT NULL |
| synced | INTEGER | NOT NULL (Firestore sync flag) |
| deviceId | TEXT |
notes — rich-text notes.
| Column | Type | Notes |
|---|---|---|
| id | TEXT | Primary key |
| title | TEXT | |
| contentJson | TEXT | appflowy_editor document JSON |
| createdAt / updatedAt | TEXT | |
| deviceId | TEXT |
Created by _createTaskTriggers (used both on create and on the v9 upgrade):
update_timeintervals_of_task_title_color_isImportant—AFTER UPDATE OF title, color, isImportant ON tasks: propagates title/color/importance changes down to the task's time intervals.delete_timeintervals_of_task—AFTER DELETE ON tasks: removes the task's time intervals.
_onUpgrade applies steps sequentially (if (oldVersion < N)); each step only touches tables that already exist at that version.
| Version | Change |
|---|---|
| v2 | Add focus_sessions table |
| v3 | Add soft-delete columns (isDeleted/deletedAt) |
| v4 | Add deviceId column |
| v5 | Add reminders to timeintervals |
| v6 | Add notes table |
| v7 | Add descriptionDoc column |
| v8 | Add noteIds column |
| v9 | Merge the 3 task types into the unified Task; rebuild tasks/timeintervals and triggers |
| v10 | Add archive columns (isArchived/archivedAt) |
| v11 | Add the calendar_events table |
Tables introduced in a later version (e.g.
calendar_eventsin v11) are not altered by earlier steps — they are created with all their columns already present.
CalendarSyncManager (in data/sync/) is the high-level orchestrator. It connects AuthRepository.calendarAccessToken (a real OAuth access token) to GoogleCalendarSyncService, and stores sync configuration in SharedPreferences: enabled flag, selected calendarId (default 'primary'), a per-calendar syncToken (for incremental sync), and a taskListId to attach pulled events to.
The UI only calls syncNow(). A two-way sync pushes local changes first, then pulls remote changes:
CalendarSyncManager.syncNow()
│
read config from SharedPreferences
(calendarId, taskListId, saved syncToken)
│
GoogleCalendarSyncService.syncTwoWay()
│
┌──────────────────────────┴──────────────────────────┐
│ 1) PUSH (pushPending) │
│ for each local event by syncStatus: │
│ • localOnly → POST create (remote) │
│ • pendingUpdate → PATCH update (If-Match etag) │
│ or POST create if no id │
│ • pendingDelete → DELETE remote │
│ • synced → skip │
└──────────────────────────┬──────────────────────────┘
│
┌──────────────────────────┴──────────────────────────┐
│ 2) PULL (pull) │
│ • incremental if a syncToken exists │
│ • otherwise full sync within a ±1 year window │
│ • upsert events locally (mark syncStatus=synced) │
│ • attach new events to taskListId (if set) │
│ • follow nextPageToken across pages │
└──────────────────────────┬──────────────────────────┘
│
save nextSyncToken for the next incremental sync
│
return CalendarSyncOutcome(upserted, deleted)
Key points:
- Optimistic concurrency: updates send
If-Match: <etag>so the server rejects the write if the event changed remotely. - Sync-token expiry: if Google returns HTTP
410(token expired), the saved token is cleared so the next run performs a full sync. - Error handling: all errors are caught and reported through
CalendarSyncOutcome(success, upserted, deleted, error)— a sync failure never crashes the app. - Device calendar:
device_calendar_sync_service.dartprovides a separate backend for the system calendar (mobile only).
- Desktop (Windows/Linux/macOS): SQLite runs through FFI.
sqlite3_flutter_libsis pinned to 0.5.42 so thesqlite3DLL is bundled via the classic CMake scripts (the0.6.0+eolrelease is an empty stub). - Windows & Google Sign-In: there is no native flow; Google sign-in uses a manual OAuth flow via
http(exchanging the authorization code for a token). - Device calendar: supported only on Android/iOS; on desktop
DeviceCalendarServicereturns empty results. - Notifications/sound: system notifications are mainly for mobile; the alarm sound (
assets/sounds/alarm.mp3) is mainly for desktop (the app still runs if the file is missing).
Documented in detail in pubspec.yaml; the tricky points, summarized:
google_sign_in: ^6.2.1— kept on the 6.x line because 7.x fully rewrote the API;firebase_authdoes not depend ongoogle_sign_in.csv: ^8.0.0— the new API usesCsvEncoder(ListToCsvConverterwas removed).font_awesome_flutter: ^11.0.0— 10.x breaks becauseIconDatais nowfinalin newer Flutter.timezone: ^0.9.0— required by the localtime_managerplugin and for scheduling notifications.flutter_timezone: ^5.0.0— returnsTimezoneInfo; use.identifierto get the IANA name.appflowy_editor— uses the gitmainbranch (to avoid anintlconflict withflutter_localizations).dependency_overrides:file_picker: ^10.3.10,device_info_plus: ^12.3.0(required by theappflowy_editormain branch).










