refactor(apps): 主题系统迁移至 ColorScheme + 扩展架构并支持 Dark Mode

This commit is contained in:
qzl
2026-03-27 19:07:39 +08:00
parent ecc1ec6ce4
commit ae29a8209b
146 changed files with 4301 additions and 3200 deletions
@@ -1,6 +1,5 @@
import 'package:social_app/core/network/i_api_client.dart';
import 'models/schedule_item_model.dart';
import 'package:social_app/data/repositories/models/schedule_item_model.dart';
class CalendarApi {
final IApiClient _client;
@@ -1,306 +0,0 @@
import 'package:flutter/material.dart';
enum ScheduleSourceType { manual, imported, agentGenerated }
enum ScheduleStatus { active, archived }
class ScheduleItemModel {
final String id;
final String ownerId;
final int permission;
final bool isOwner;
final String title;
final String? description;
final DateTime startAt;
final DateTime? endAt;
final String timezone;
final ScheduleMetadata? metadata;
final ScheduleSourceType sourceType;
final ScheduleStatus status;
final DateTime createdAt;
final DateTime updatedAt;
static const int permissionView = 1;
static const int permissionInvite = 2;
static const int permissionEdit = 4;
bool get canEdit => isOwner || (permission & permissionEdit) != 0;
bool get canInvite => isOwner || (permission & permissionInvite) != 0;
bool get canDelete => isOwner;
ScheduleItemModel({
required this.id,
required this.ownerId,
this.permission = 1,
this.isOwner = false,
required this.title,
this.description,
required this.startAt,
this.endAt,
this.timezone = 'Asia/Shanghai',
this.metadata,
this.sourceType = ScheduleSourceType.manual,
this.status = ScheduleStatus.active,
DateTime? createdAt,
DateTime? updatedAt,
}) : createdAt = createdAt ?? DateTime.now(),
updatedAt = updatedAt ?? DateTime.now();
ScheduleItemModel copyWith({
String? id,
String? ownerId,
int? permission,
bool? isOwner,
String? title,
String? description,
DateTime? startAt,
DateTime? endAt,
String? timezone,
ScheduleMetadata? metadata,
ScheduleSourceType? sourceType,
ScheduleStatus? status,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return ScheduleItemModel(
id: id ?? this.id,
ownerId: ownerId ?? this.ownerId,
permission: permission ?? this.permission,
isOwner: isOwner ?? this.isOwner,
title: title ?? this.title,
description: description ?? this.description,
startAt: startAt ?? this.startAt,
endAt: endAt ?? this.endAt,
timezone: timezone ?? this.timezone,
metadata: metadata ?? this.metadata,
sourceType: sourceType ?? this.sourceType,
status: status ?? this.status,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
factory ScheduleItemModel.fromJson(Map<String, dynamic> json) {
return ScheduleItemModel(
id: json['id'] as String,
ownerId: json['owner_id'] as String? ?? '',
permission: json['permission'] as int? ?? 1,
isOwner: json['is_owner'] as bool? ?? false,
title: json['title'] as String,
description: json['description'] as String?,
startAt: DateTime.parse(json['start_at'] as String).toLocal(),
endAt: json['end_at'] != null
? DateTime.parse(json['end_at'] as String).toLocal()
: null,
timezone: (json['timezone'] as String?) ?? 'UTC',
metadata: json['metadata'] is Map<String, dynamic>
? ScheduleMetadata.fromJson(json['metadata'] as Map<String, dynamic>)
: null,
sourceType: _sourceTypeFromApi(json['source_type'] as String?),
status: _statusFromApi(json['status'] as String?),
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'] as String).toLocal()
: DateTime.now(),
updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at'] as String).toLocal()
: DateTime.now(),
);
}
Map<String, dynamic> toCreateJson() {
return {
'title': title,
'description': description,
'start_at': startAt.toUtc().toIso8601String(),
'end_at': endAt?.toUtc().toIso8601String(),
'timezone': timezone,
'metadata': metadata?.toJson(),
};
}
Map<String, dynamic> toUpdateJson() {
return {
'title': title,
'description': description,
'start_at': startAt.toUtc().toIso8601String(),
'end_at': endAt?.toUtc().toIso8601String(),
'timezone': timezone,
'metadata': metadata?.toJson(),
'status': _statusToApi(status),
};
}
}
class ScheduleMetadata {
final String? color;
final String? location;
final String? notes;
final int? reminderMinutes;
final List<Attachment> attachments;
final int version;
final Map<String, dynamic> raw;
ScheduleMetadata({
this.color,
this.location,
this.notes,
this.reminderMinutes,
List<Attachment>? attachments,
this.version = 1,
Map<String, dynamic>? raw,
}) : attachments = attachments ?? const [],
raw = raw ?? const {};
ScheduleMetadata copyWith({
String? color,
String? location,
String? notes,
int? reminderMinutes,
List<Attachment>? attachments,
int? version,
Map<String, dynamic>? raw,
}) {
return ScheduleMetadata(
color: color ?? this.color,
location: location ?? this.location,
notes: notes ?? this.notes,
reminderMinutes: reminderMinutes ?? this.reminderMinutes,
attachments: attachments ?? this.attachments,
version: version ?? this.version,
raw: raw ?? this.raw,
);
}
factory ScheduleMetadata.fromJson(Map<String, dynamic> json) {
final rawAttachments = json['attachments'];
final attachments = rawAttachments is List
? rawAttachments
.whereType<Map<String, dynamic>>()
.map(Attachment.fromJson)
.toList()
: <Attachment>[];
return ScheduleMetadata(
color: json['color'] as String?,
location: json['location'] as String?,
notes: json['notes'] as String?,
reminderMinutes: json['reminder_minutes'] as int?,
attachments: attachments,
version: (json['version'] as int?) ?? 1,
raw: Map<String, dynamic>.from(json),
);
}
Map<String, dynamic> toJson() {
return {
'color': color,
'location': location,
'notes': notes,
'reminder_minutes': reminderMinutes,
'attachments': attachments.map((item) => item.toJson()).toList(),
'version': version,
};
}
}
class Attachment {
final String name;
final List<String> visibleTo;
final String? url;
final String? note;
final String? content;
final String type;
Attachment({
required this.name,
this.visibleTo = const [],
this.url,
this.note,
this.content,
this.type = 'document',
});
Attachment copyWith({
String? name,
List<String>? visibleTo,
String? url,
String? note,
String? content,
String? type,
}) {
return Attachment(
name: name ?? this.name,
visibleTo: visibleTo ?? this.visibleTo,
url: url ?? this.url,
note: note ?? this.note,
content: content ?? this.content,
type: type ?? this.type,
);
}
factory Attachment.fromJson(Map<String, dynamic> json) {
final rawVisibleTo = json['visible_to'];
final visibleTo = rawVisibleTo is List
? rawVisibleTo.map((item) => item.toString()).toList()
: <String>[];
return Attachment(
name: (json['name'] as String?) ?? '',
visibleTo: visibleTo,
url: json['url'] as String?,
note: json['note'] as String?,
content: json['content'] as String?,
type: (json['type'] as String?) ?? 'document',
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'visible_to': visibleTo,
'url': url,
'note': note,
'content': content,
'type': type,
};
}
}
ScheduleSourceType _sourceTypeFromApi(String? raw) {
switch (raw) {
case 'imported':
return ScheduleSourceType.imported;
case 'agent_generated':
return ScheduleSourceType.agentGenerated;
case 'manual':
default:
return ScheduleSourceType.manual;
}
}
ScheduleStatus _statusFromApi(String? raw) {
switch (raw) {
case 'completed':
case 'canceled':
case 'archived':
return ScheduleStatus.archived;
case 'active':
default:
return ScheduleStatus.active;
}
}
String _statusToApi(ScheduleStatus status) {
switch (status) {
case ScheduleStatus.active:
return 'active';
case ScheduleStatus.archived:
return 'archived';
}
}
const defaultColors = [
Color(0xFF3B82F6),
Color(0xFF8B5CF6),
Color(0xFF10B981),
Color(0xFFF59E0B),
Color(0xFFEF4444),
];
@@ -1,144 +0,0 @@
import 'dart:async';
import '../../../../core/cache/cache_entry.dart';
import '../../../../core/cache/cache_policy.dart';
import '../../../../core/cache/hybrid_cache_store.dart';
import '../models/schedule_item_model.dart';
class CalendarRepository {
final HybridCacheStore store;
final CachePolicy policy;
final DateTime Function() now;
final Future<List<ScheduleItemModel>> Function(DateTime date)
loadDayFromRemote;
final Future<List<ScheduleItemModel>> Function(DateTime start, DateTime end)
loadMonthFromRemote;
final Map<String, Future<void>> _refreshInFlight = <String, Future<void>>{};
CalendarRepository({
required this.store,
required this.loadDayFromRemote,
required this.loadMonthFromRemote,
CachePolicy? policy,
DateTime Function()? now,
}) : policy =
policy ??
const CachePolicy(
softTtl: Duration(minutes: 2),
hardTtl: Duration(minutes: 30),
minRefreshInterval: Duration(minutes: 1),
),
now = now ?? DateTime.now;
static String dayKey(DateTime date) {
final day =
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
return 'calendar:day:$day';
}
static String monthKey(DateTime date) {
return 'calendar:month:${date.year}-${date.month.toString().padLeft(2, '0')}';
}
Future<List<ScheduleItemModel>> getDayEvents(
DateTime date, {
bool forceRefresh = false,
}) async {
final key = dayKey(date);
if (forceRefresh) {
return _refreshDayAndRead(date, key);
}
final cached = await store.read<CacheEntry<List<ScheduleItemModel>>>(key);
if (cached == null) {
return _refreshDayAndRead(date, key);
}
final decision = policy.evaluate(now: now(), fetchedAt: cached.fetchedAt);
if (decision.shouldRefreshInBackground) {
_refreshDayInBackground(date, key);
}
if (decision.mustBlockForNetwork || !decision.canUseCached) {
return _refreshDayAndRead(date, key);
}
return cached.value;
}
Future<List<ScheduleItemModel>> getMonthEvents(
DateTime monthStart, {
bool forceRefresh = false,
}) async {
final key = monthKey(monthStart);
if (forceRefresh) {
return _refreshMonthAndRead(monthStart, key);
}
final cached = await store.read<CacheEntry<List<ScheduleItemModel>>>(key);
if (cached == null) {
return _refreshMonthAndRead(monthStart, key);
}
final decision = policy.evaluate(now: now(), fetchedAt: cached.fetchedAt);
if (decision.shouldRefreshInBackground) {
_refreshMonthInBackground(monthStart, key);
}
if (decision.mustBlockForNetwork || !decision.canUseCached) {
return _refreshMonthAndRead(monthStart, key);
}
return cached.value;
}
Future<List<ScheduleItemModel>> _refreshDayAndRead(
DateTime date,
String key,
) async {
await _refreshDay(date, key);
final cached = await store.read<CacheEntry<List<ScheduleItemModel>>>(key);
return cached?.value ?? const <ScheduleItemModel>[];
}
Future<List<ScheduleItemModel>> _refreshMonthAndRead(
DateTime monthStart,
String key,
) async {
await _refreshMonth(monthStart, key);
final cached = await store.read<CacheEntry<List<ScheduleItemModel>>>(key);
return cached?.value ?? const <ScheduleItemModel>[];
}
Future<void> _refreshDay(DateTime date, String key) async {
final remote = await loadDayFromRemote(date);
await store.write<CacheEntry<List<ScheduleItemModel>>>(
key,
CacheEntry<List<ScheduleItemModel>>(value: remote, fetchedAt: now()),
);
}
Future<void> _refreshMonth(DateTime monthStart, String key) async {
final start = DateTime(monthStart.year, monthStart.month, 1);
final end = DateTime(monthStart.year, monthStart.month + 1, 0, 23, 59, 59);
final remote = await loadMonthFromRemote(start, end);
await store.write<CacheEntry<List<ScheduleItemModel>>>(
key,
CacheEntry<List<ScheduleItemModel>>(value: remote, fetchedAt: now()),
);
}
void _refreshDayInBackground(DateTime date, String key) {
_refreshInBackground(key, () => _refreshDay(date, key));
}
void _refreshMonthInBackground(DateTime monthStart, String key) {
_refreshInBackground(key, () => _refreshMonth(monthStart, key));
}
void _refreshInBackground(String key, Future<void> Function() taskFactory) {
if (_refreshInFlight.containsKey(key)) {
return;
}
final task = taskFactory().whenComplete(() {
_refreshInFlight.remove(key);
});
_refreshInFlight[key] = task;
unawaited(task);
}
}
@@ -1,92 +0,0 @@
import 'package:social_app/core/network/i_api_client.dart';
import 'package:social_app/core/cache/cache_invalidator.dart';
import 'package:social_app/app/di/injection.dart';
import '../calendar_api.dart';
import '../models/schedule_item_model.dart';
class CalendarService {
final IApiClient _apiClient;
CalendarApi? _calendarApi;
CalendarService({required IApiClient apiClient}) : _apiClient = apiClient;
CalendarApi get _api {
final api = _calendarApi;
if (api != null) {
return api;
}
final created = CalendarApi(_apiClient);
_calendarApi = created;
return created;
}
Future<List<ScheduleItemModel>> getEventsForDay(DateTime date) async {
final start = DateTime(date.year, date.month, date.day);
final end = DateTime(date.year, date.month, date.day, 23, 59, 59);
return getEventsForRange(start, end);
}
Future<List<ScheduleItemModel>> getEventsForRange(
DateTime start,
DateTime end,
) async {
return _api.listByRange(startAt: start, endAt: end);
}
Future<ScheduleItemModel?> getEventById(String id) async {
return _api.getById(id);
}
Future<ScheduleItemModel> addEvent(ScheduleItemModel event) async {
final created = await _api.create(event);
_invalidateEventCache(created);
return created;
}
Future<ScheduleItemModel> updateEvent(ScheduleItemModel event) async {
final updated = await _api.update(event);
_invalidateEventCache(updated);
return updated;
}
Future<ScheduleItemModel?> archiveEvent(String id) async {
final event = await getEventById(id);
if (event == null) {
return null;
}
final updatedEvent = await updateEvent(
event.copyWith(status: ScheduleStatus.archived),
);
_invalidateEventCache(updatedEvent);
return updatedEvent;
}
void _invalidateEventCache(ScheduleItemModel event) {
try {
final invalidator = sl<CacheInvalidator>();
var current = DateTime(
event.startAt.year,
event.startAt.month,
event.startAt.day,
);
final end = DateTime(
event.endAt?.year ?? event.startAt.year,
event.endAt?.month ?? event.startAt.month,
event.endAt?.day ?? event.startAt.day,
);
while (!current.isAfter(end)) {
invalidator.invalidateCalendarDay(current);
current = current.add(const Duration(days: 1));
}
} catch (_) {}
}
Future<void> deleteEvent(String id) async {
final event = await getEventById(id);
if (event != null) {
_invalidateEventCache(event);
}
await _api.delete(id);
}
}
@@ -1,55 +0,0 @@
import 'package:flutter/material.dart';
enum CalendarViewType { day, month }
class CalendarState {
final CalendarViewType viewType;
final DateTime selectedDate;
CalendarState({required this.viewType, required this.selectedDate});
CalendarState copyWith({CalendarViewType? viewType, DateTime? selectedDate}) {
return CalendarState(
viewType: viewType ?? this.viewType,
selectedDate: selectedDate ?? this.selectedDate,
);
}
}
class CalendarStateManager extends ChangeNotifier {
CalendarState _state;
CalendarStateManager()
: _state = CalendarState(
viewType: CalendarViewType.month,
selectedDate: DateTime.now(),
);
CalendarState get state => _state;
CalendarViewType get viewType => _state.viewType;
DateTime get selectedDate => _state.selectedDate;
void setViewType(CalendarViewType type) {
_state = _state.copyWith(viewType: type);
notifyListeners();
}
void setSelectedDate(DateTime date) {
_state = _state.copyWith(selectedDate: date);
notifyListeners();
}
void resetToToday() {
final now = DateTime.now();
_state = CalendarState(
viewType: CalendarViewType.month,
selectedDate: DateTime(now.year, now.month, now.day),
);
notifyListeners();
}
void refresh() {
notifyListeners();
}
}
@@ -1,4 +1,4 @@
import '../../data/models/schedule_item_model.dart';
import '../../../../data/repositories/models/schedule_item_model.dart';
import 'day_timeline_metrics.dart';
import 'day_view_scale.dart';
@@ -2,21 +2,21 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../../app/router/app_routes.dart';
import '../../../home/presentation/navigation/home_return_policy.dart';
import '../../../../app/router/home_return_policy.dart';
import '../../../../app/di/injection.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../data/repositories/calendar_repository.dart';
import '../../../../shared/widgets/app_pressable.dart';
import '../../data/models/schedule_item_model.dart';
import '../../data/services/calendar_repository.dart';
import '../calendar_state_manager.dart';
import '../../../../shared/widgets/bottom_dock.dart';
import '../../../../shared/state/calendar_state_manager.dart';
import '../../../../data/repositories/models/schedule_item_model.dart';
import '../calendar_time_utils.dart';
import '../utils/event_color_resolver.dart';
import '../dayweek/day_event_layout_engine.dart';
import '../dayweek/day_timeline_metrics.dart';
import '../dayweek/day_view_scale.dart';
import '../widgets/bottom_dock.dart';
class CalendarDayWeekScreen extends StatefulWidget {
final DateTime? initialDate;
@@ -52,6 +52,8 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
late List<DateTime> _monthDates;
List<ScheduleItemModel> _events = const [];
ColorScheme get _colorScheme => Theme.of(context).colorScheme;
@override
void initState() {
super.initState();
@@ -107,7 +109,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.todoBg,
backgroundColor: _colorScheme.surface,
body: PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
@@ -244,18 +246,18 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
height: 36,
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: AppColors.messageBtnWrap,
color: _colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.messageBtnBorder),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icon(
LucideIcons.chevronLeft,
size: 16,
color: AppColors.slate700,
color: _colorScheme.onSurface,
),
const SizedBox(width: 6),
AnimatedSwitcher(
@@ -277,10 +279,10 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
child: Text(
monthLabel,
key: ValueKey(monthLabel),
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
color: _colorScheme.onSurface,
),
),
),
@@ -297,17 +299,17 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
height: 36,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: AppColors.messageBtnWrap,
color: _colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.messageBtnBorder),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Center(
child: Text(
context.l10n.calendarToday,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
color: _colorScheme.onSurface,
),
),
),
@@ -328,13 +330,13 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
width: 36,
height: 36,
decoration: BoxDecoration(
color: AppColors.blue600,
color: _colorScheme.primary,
borderRadius: BorderRadius.circular(AppRadius.full),
),
child: const Icon(
child: Icon(
LucideIcons.plus,
size: 20,
color: AppColors.white,
color: _colorScheme.onPrimary,
),
),
),
@@ -454,7 +456,9 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
_weekdayLabel(date),
style: TextStyle(
fontSize: 11,
color: isWeekend ? AppColors.slate400 : AppColors.slate600,
color: isWeekend
? _colorScheme.onSurfaceVariant
: _colorScheme.onSurface,
),
),
const SizedBox(height: 2),
@@ -464,7 +468,9 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
width: 32,
height: 32,
decoration: BoxDecoration(
color: isSelected ? AppColors.blue100 : Colors.transparent,
color: isSelected
? _colorScheme.primaryContainer
: _colorScheme.surface.withValues(alpha: 0),
borderRadius: BorderRadius.circular(AppRadius.full),
),
child: Center(
@@ -474,8 +480,10 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
fontSize: 17,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
color: isSelected
? AppColors.blue600
: (isWeekend ? AppColors.slate400 : AppColors.slate900),
? _colorScheme.primary
: (isWeekend
? _colorScheme.onSurfaceVariant
: _colorScheme.onSurface),
),
),
),
@@ -572,7 +580,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
top: adjustedY,
left: eventAreaLeft,
right: 0,
child: Container(height: 1, color: AppColors.border),
child: Container(height: 1, color: _colorScheme.outlineVariant),
),
Positioned(
top: labelTop,
@@ -584,7 +592,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: AppColors.slate400,
color: _colorScheme.onSurfaceVariant,
),
),
),
@@ -613,16 +621,16 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
width: DayTimelineMetrics.timeLabelWidth,
height: 18,
decoration: BoxDecoration(
color: AppColors.red500,
color: _colorScheme.error,
borderRadius: BorderRadius.circular(9),
),
child: Center(
child: Text(
formatHm(now),
style: const TextStyle(
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
color: Colors.white,
color: _colorScheme.onError,
),
),
),
@@ -632,7 +640,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
child: Container(
height: 2,
decoration: BoxDecoration(
color: AppColors.red500,
color: _colorScheme.error,
borderRadius: BorderRadius.circular(99),
),
),
@@ -651,7 +659,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
final isArchived = layout.event.status == ScheduleStatus.archived;
Color eventColor;
if (isArchived) {
eventColor = AppColors.slate400;
eventColor = _colorScheme.onSurfaceVariant;
} else {
eventColor = resolveEventColor(
status: layout.event.status,
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import '../../../../core/theme/design_tokens.dart';
import '../widgets/create_event_sheet.dart';
class CalendarEventCreateScreen extends StatelessWidget {
@@ -10,8 +9,9 @@ class CalendarEventCreateScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: AppColors.background,
backgroundColor: colorScheme.surface,
body: SafeArea(
child: CreateEventSheet(initialDate: initialDate, pageMode: true),
),
@@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart';
import 'package:social_app/core/l10n/l10n.dart';
import '../../../../app/di/injection.dart';
import '../../../../app/router/app_routes.dart';
import '../../../../features/notification/data/services/local_notification_service.dart';
import '../../../../data/services/local_notification_service.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../../../../shared/widgets/back_title_page_header.dart';
@@ -12,8 +12,8 @@ import '../../../../shared/widgets/detail_header_action_menu.dart';
import '../../../../shared/widgets/destructive_action_sheet.dart';
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import '../../data/services/calendar_service.dart';
import '../../data/models/schedule_item_model.dart';
import '../../../../data/services/calendar_service.dart';
import '../../../../data/repositories/models/schedule_item_model.dart';
import '../utils/event_color_resolver.dart';
enum _CalendarHeaderAction { edit, delete, share, archive }
@@ -32,6 +32,8 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
ScheduleItemModel? _event;
bool _loading = true;
ColorScheme get _colorScheme => Theme.of(context).colorScheme;
@override
void initState() {
super.initState();
@@ -60,14 +62,17 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
}
if (_event == null) {
return Scaffold(
backgroundColor: AppColors.background,
backgroundColor: _colorScheme.surface,
body: SafeArea(
child: Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [AppColors.homeBackgroundTop, AppColors.background],
colors: [
_colorScheme.surfaceContainerLow,
_colorScheme.surface,
],
),
),
child: Column(
@@ -89,7 +94,7 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
color: _colorScheme.onSurface,
),
),
const SizedBox(height: AppSpacing.sm),
@@ -98,7 +103,7 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
color: AppColors.slate500,
color: _colorScheme.onSurfaceVariant,
),
),
],
@@ -115,14 +120,14 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
final event = _event!;
return Scaffold(
backgroundColor: AppColors.background,
backgroundColor: _colorScheme.surface,
body: SafeArea(
child: Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [AppColors.homeBackgroundTop, AppColors.background],
colors: [_colorScheme.surfaceContainerLow, _colorScheme.surface],
),
),
child: Column(
@@ -255,12 +260,12 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
return Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: AppColors.white,
color: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.borderTertiary),
border: Border.all(color: _colorScheme.outlineVariant),
boxShadow: [
BoxShadow(
color: AppColors.slate200.withValues(alpha: 0.38),
color: _colorScheme.shadow.withValues(alpha: 0.18),
blurRadius: AppRadius.lg,
offset: const Offset(0, AppSpacing.xs),
),
@@ -289,10 +294,10 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
event.title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: AppColors.slate900,
color: _colorScheme.onSurface,
),
),
const SizedBox(height: AppSpacing.xs),
@@ -307,9 +312,9 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceInfoLight,
color: _colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: AppColors.borderQuaternary),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -319,7 +324,7 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColors.slate500,
color: _colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: AppSpacing.xs),
@@ -329,7 +334,7 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: AppColors.slate800,
color: _colorScheme.onSurface,
),
),
],
@@ -356,9 +361,9 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
return Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: AppColors.white,
color: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.borderSecondary),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -368,7 +373,7 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.slate500,
color: _colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: AppSpacing.md),
@@ -386,10 +391,10 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
width: AppSpacing.xxl * 3,
child: Text(
context.l10n.calendarDetailColor,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.slate500,
color: _colorScheme.onSurfaceVariant,
),
),
),
@@ -399,7 +404,7 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(color: AppColors.borderSecondary),
border: Border.all(color: _colorScheme.outlineVariant),
),
),
],
@@ -419,9 +424,9 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
return Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
color: _colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.borderSecondary),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -431,7 +436,7 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.slate500,
color: _colorScheme.onSurfaceVariant,
),
),
if (event.metadata?.location?.trim().isNotEmpty ?? false) ...[
@@ -570,13 +575,11 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
),
decoration: BoxDecoration(
color: isArchived
? AppColors.feedbackWarningSurface
: AppColors.feedbackSuccessSurface,
? _colorScheme.errorContainer
: _colorScheme.tertiaryContainer,
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(
color: isArchived
? AppColors.feedbackWarningBorder
: AppColors.feedbackSuccessBorder,
color: isArchived ? _colorScheme.error : _colorScheme.tertiary,
),
),
child: Text(
@@ -587,8 +590,8 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
fontSize: 12,
fontWeight: FontWeight.w700,
color: isArchived
? AppColors.feedbackWarningText
: AppColors.feedbackSuccessText,
? _colorScheme.onErrorContainer
: _colorScheme.onTertiaryContainer,
),
),
);
@@ -604,10 +607,10 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
width: AppSpacing.xxl * 3,
child: Text(
label,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.slate500,
color: _colorScheme.onSurfaceVariant,
),
),
),
@@ -619,7 +622,7 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
fontSize: 14,
height: multiline ? 1.4 : 1.0,
fontWeight: FontWeight.w600,
color: AppColors.slate800,
color: _colorScheme.onSurface,
),
),
),
@@ -2,11 +2,10 @@ import 'package:flutter/material.dart';
import '../../../../app/di/injection.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../widgets/create_event_sheet.dart';
import '../../data/models/schedule_item_model.dart';
import '../../data/services/calendar_service.dart';
import '../../../../data/repositories/models/schedule_item_model.dart';
import '../../../../data/services/calendar_service.dart';
class CalendarEventEditScreen extends StatefulWidget {
final String eventId;
@@ -61,7 +60,7 @@ class _CalendarEventEditScreenState extends State<CalendarEventEditScreen> {
}
return Scaffold(
backgroundColor: AppColors.background,
backgroundColor: Theme.of(context).colorScheme.surface,
body: SafeArea(
child: CreateEventSheet(editingEvent: _event, pageMode: true),
),
@@ -2,11 +2,10 @@ import 'package:flutter/material.dart';
import '../../../../app/di/injection.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../../../../shared/widgets/back_title_page_header.dart';
import '../../data/models/schedule_item_model.dart';
import '../../data/services/calendar_service.dart';
import '../../../../data/repositories/models/schedule_item_model.dart';
import '../../../../data/services/calendar_service.dart';
import '../widgets/calendar_share_dialog.dart';
class CalendarEventShareScreen extends StatefulWidget {
@@ -63,7 +62,7 @@ class _CalendarEventShareScreenState extends State<CalendarEventShareScreen> {
}
return Scaffold(
backgroundColor: AppColors.background,
backgroundColor: Theme.of(context).colorScheme.surface,
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -6,14 +6,14 @@ import 'package:social_app/core/l10n/l10n.dart';
import '../../../../app/di/injection.dart';
import '../../../../app/router/app_routes.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../data/repositories/calendar_repository.dart';
import '../../../../shared/widgets/app_pressable.dart';
import '../../../home/presentation/navigation/home_return_policy.dart';
import '../calendar_state_manager.dart';
import '../../../../shared/widgets/bottom_dock.dart';
import '../../../../shared/state/calendar_state_manager.dart';
import '../../../../app/router/home_return_policy.dart';
import '../calendar_time_utils.dart';
import '../utils/event_color_resolver.dart';
import '../widgets/bottom_dock.dart';
import '../../data/models/schedule_item_model.dart';
import '../../data/services/calendar_repository.dart';
import '../../../../data/repositories/models/schedule_item_model.dart';
class CalendarMonthScreen extends StatefulWidget {
final bool resetToToday;
@@ -31,6 +31,8 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
late DateTime _selectedDate;
final Map<String, List<ScheduleItemModel>> _eventsByDay = {};
ColorScheme get _colorScheme => Theme.of(context).colorScheme;
@override
void initState() {
super.initState();
@@ -79,7 +81,7 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.todoBg,
backgroundColor: _colorScheme.surface,
body: PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
@@ -136,18 +138,18 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
child: Text(
l10n.calendarMonthHeader(_currentMonth.month),
key: ValueKey(_currentMonth.month),
style: const TextStyle(
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
color: AppColors.slate900,
color: _colorScheme.onSurface,
),
),
),
const SizedBox(width: 6),
const Icon(
Icon(
LucideIcons.chevronDown,
size: 16,
color: AppColors.slate900,
color: _colorScheme.onSurface,
),
],
),
@@ -161,9 +163,11 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
height: 36,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: AppColors.messageBtnWrap,
color: _colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.messageBtnBorder),
border: Border.all(
color: _colorScheme.outlineVariant,
),
),
child: Center(
child: Text(
@@ -171,7 +175,7 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
color: _colorScheme.onSurface,
),
),
),
@@ -192,13 +196,13 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
width: 36,
height: 36,
decoration: BoxDecoration(
color: AppColors.blue600,
color: _colorScheme.primary,
borderRadius: BorderRadius.circular(AppRadius.full),
),
child: const Icon(
child: Icon(
LucideIcons.plus,
size: 20,
color: AppColors.white,
color: _colorScheme.onPrimary,
),
),
),
@@ -229,7 +233,7 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
return Column(
children: [
_buildWeekdayHeader(),
Container(height: 1, color: AppColors.border),
Container(height: 1, color: _colorScheme.outlineVariant),
..._buildWeeks(),
],
);
@@ -259,10 +263,10 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
child: Center(
child: Text(
day,
style: const TextStyle(
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: AppColors.slate400,
color: _colorScheme.onSurfaceVariant,
),
),
),
@@ -294,7 +298,7 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
for (var weekStart = 0; weekStart < totalCells; weekStart += 7) {
weeks.add(_buildWeekRow(weekStart, startWeekday, daysInMonth));
if (weekStart + 7 < totalCells) {
weeks.add(Container(height: 1, color: AppColors.border));
weeks.add(Container(height: 1, color: _colorScheme.outlineVariant));
}
}
@@ -337,7 +341,9 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
width: 36,
height: 36,
decoration: BoxDecoration(
color: isSelected ? AppColors.blue100 : Colors.transparent,
color: isSelected
? _colorScheme.primaryContainer
: _colorScheme.surface.withValues(alpha: 0),
borderRadius: BorderRadius.circular(AppRadius.full),
),
child: Center(
@@ -349,8 +355,8 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
? FontWeight.w600
: FontWeight.normal,
color: isSelected
? AppColors.blue600
: AppColors.slate900,
? _colorScheme.primary
: _colorScheme.onSurface,
),
),
),
@@ -447,9 +453,9 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
},
child: Text(
'+$remainingCount',
style: const TextStyle(
style: TextStyle(
fontSize: 9,
color: AppColors.slate500,
color: _colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
@@ -469,7 +475,7 @@ class _CalendarMonthScreenState extends State<CalendarMonthScreen>
showModalBottomSheet(
context: context,
backgroundColor: AppColors.white,
backgroundColor: _colorScheme.surface,
builder: (context) {
return StatefulBuilder(
builder: (context, setSheetState) {
@@ -1,23 +1,22 @@
import 'package:flutter/material.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../data/models/schedule_item_model.dart';
import '../../../../data/repositories/models/schedule_item_model.dart';
Color resolveEventColor({
required ScheduleStatus status,
required String? colorHex,
}) {
if (status == ScheduleStatus.archived) {
return AppColors.slate400;
return const Color(0xFF64748B);
}
if (colorHex == null || colorHex.isEmpty) {
return AppColors.blue600;
return const Color(0xFF3B82F6);
}
try {
return Color(int.parse(colorHex.replaceFirst('#', '0xFF')));
} catch (_) {
return AppColors.blue600;
return const Color(0xFF3B82F6);
}
}
@@ -1,139 +0,0 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../../core/theme/design_tokens.dart';
enum DockTab { todo, calendar }
class BottomDock extends StatelessWidget {
final DockTab activeTab;
final VoidCallback? onTodoTap;
final VoidCallback? onCalendarTap;
final VoidCallback? onHomeTap;
const BottomDock({
super.key,
required this.activeTab,
this.onTodoTap,
this.onCalendarTap,
this.onHomeTap,
});
@override
Widget build(BuildContext context) {
return Container(
height: 72,
padding: const EdgeInsets.only(
left: AppSpacing.xl,
right: AppSpacing.xl,
top: AppSpacing.md,
bottom: AppSpacing.sm,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [_buildToggle(), _buildHomeBtn()],
),
);
}
Widget _buildToggle() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 4),
decoration: BoxDecoration(
color: AppColors.todoToggleBg,
borderRadius: BorderRadius.circular(AppRadius.xxl),
border: Border.all(color: AppColors.todoToggleBorder),
boxShadow: [
BoxShadow(
color: AppColors.slate200.withValues(alpha: 0.45),
blurRadius: AppRadius.sm,
offset: const Offset(0, AppSpacing.xs / 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildToggleItem(
icon: LucideIcons.listTodo,
isActive: activeTab == DockTab.todo,
onTap: onTodoTap,
),
const SizedBox(width: 4),
_buildToggleItem(
icon: LucideIcons.calendar,
isActive: activeTab == DockTab.calendar,
onTap: onCalendarTap,
),
],
),
);
}
Widget _buildToggleItem({
required IconData icon,
required bool isActive,
VoidCallback? onTap,
}) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(AppRadius.xl),
child: AnimatedContainer(
duration: const Duration(milliseconds: 140),
curve: Curves.easeOut,
width: 44,
height: 44,
decoration: BoxDecoration(
color: isActive ? AppColors.todoToggleActiveBg : Colors.transparent,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(
color: isActive
? AppColors.todoToggleActiveBorder
: Colors.transparent,
),
),
child: Icon(
icon,
size: 20,
color: isActive ? AppColors.blue600 : AppColors.slate700,
),
),
),
);
}
Widget _buildHomeBtn() {
return Material(
color: Colors.transparent,
child: InkWell(
key: const ValueKey('bottom_dock_home_button'),
onTap: onHomeTap,
borderRadius: BorderRadius.circular(AppRadius.xl),
child: Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: AppColors.todoToggleBg,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.todoToggleBorder),
boxShadow: [
BoxShadow(
color: AppColors.slate200.withValues(alpha: 0.42),
blurRadius: AppRadius.sm,
offset: const Offset(0, AppSpacing.xs / 2),
),
],
),
child: const Icon(
LucideIcons.home,
size: 20,
color: AppColors.slate700,
),
),
),
);
}
}
@@ -32,7 +32,9 @@ class CalendarShareDialog extends StatefulWidget {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
backgroundColor: Theme.of(
context,
).colorScheme.surface.withValues(alpha: 0),
builder: (context) => CalendarShareDialog(
eventId: eventId,
eventTitle: eventTitle,
@@ -108,13 +110,14 @@ class _CalendarShareDialogState extends State<CalendarShareDialog> {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final colorScheme = Theme.of(context).colorScheme;
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
decoration: BoxDecoration(
color: AppColors.background,
color: colorScheme.surface,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppRadius.lg),
),
@@ -200,6 +203,7 @@ class _CalendarShareDialogState extends State<CalendarShareDialog> {
bool value,
ValueChanged<bool>? onChanged,
) {
final colorScheme = Theme.of(context).colorScheme;
final enabled = onChanged != null;
return Padding(
padding: const EdgeInsets.symmetric(vertical: AppSpacing.xs),
@@ -211,13 +215,17 @@ class _CalendarShareDialogState extends State<CalendarShareDialog> {
children: [
Text(
title,
style: TextStyle(color: enabled ? null : Colors.grey),
style: TextStyle(
color: enabled
? colorScheme.onSurface
: colorScheme.onSurfaceVariant,
),
),
Text(
description,
style: TextStyle(
fontSize: 12,
color: enabled ? Colors.grey : Colors.grey.shade400,
color: colorScheme.onSurfaceVariant,
),
),
],
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:social_app/core/l10n/l10n.dart';
import '../../../../app/di/injection.dart';
import '../../../../features/notification/data/services/local_notification_service.dart';
import '../../../../data/services/local_notification_service.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../../../../shared/widgets/app_selection_sheet.dart';
@@ -11,8 +11,10 @@ import '../../../../shared/widgets/back_title_page_header.dart';
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import 'date_time_picker_sheet.dart';
import '../../data/models/schedule_item_model.dart';
import '../../data/services/calendar_service.dart';
import '../../../../data/repositories/models/schedule_item_model.dart';
import '../../../../data/services/calendar_service.dart';
final _defaultColors = AppColorPalette.light.eventPresetColors;
class CreateEventSheet extends StatefulWidget {
final DateTime? initialDate;
@@ -36,7 +38,9 @@ class CreateEventSheet extends StatefulWidget {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
backgroundColor: Theme.of(
context,
).colorScheme.surface.withValues(alpha: 0),
builder: (context) =>
CreateEventSheet(initialDate: initialDate, onSaved: onSaved),
);
@@ -50,7 +54,9 @@ class CreateEventSheet extends StatefulWidget {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
backgroundColor: Theme.of(
context,
).colorScheme.surface.withValues(alpha: 0),
builder: (context) =>
CreateEventSheet(editingEvent: event, onSaved: onSaved),
);
@@ -149,12 +155,13 @@ class _CreateEventSheetState extends State<CreateEventSheet>
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
if (widget.pageMode) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => FocusScope.of(context).unfocus(),
child: Container(
color: AppColors.background,
color: colorScheme.surface,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -175,8 +182,8 @@ class _CreateEventSheetState extends State<CreateEventSheet>
),
child: Container(
height: MediaQuery.of(context).size.height * 0.85,
decoration: const BoxDecoration(
color: AppColors.white,
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
@@ -188,7 +195,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
width: AppSpacing.xl + AppSpacing.sm,
height: AppSpacing.xs,
decoration: BoxDecoration(
color: AppColors.slate200,
color: colorScheme.outlineVariant,
borderRadius: BorderRadius.circular(AppRadius.full),
),
),
@@ -204,6 +211,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
}
Widget _buildPageHeader() {
final colorScheme = Theme.of(context).colorScheme;
return BackTitlePageHeader(
title: _isEditing
? context.l10n.calendarCreateEditTitle
@@ -226,14 +234,16 @@ class _CreateEventSheetState extends State<CreateEventSheet>
? const AppLoadingIndicator(
variant: AppLoadingVariant.button,
size: 18,
trackColor: AppColors.blue200,
trackColor: null,
)
: Text(
context.l10n.commonSave,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: enabled ? AppColors.blue600 : AppColors.slate400,
color: enabled
? colorScheme.primary
: colorScheme.outline,
),
),
),
@@ -244,6 +254,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
}
Widget _buildHeader() {
final colorScheme = Theme.of(context).colorScheme;
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
@@ -261,10 +272,10 @@ class _CreateEventSheetState extends State<CreateEventSheet>
borderRadius: BorderRadius.circular(AppRadius.full),
),
),
child: const Icon(
child: Icon(
LucideIcons.x,
size: AppSpacing.xxl,
color: AppColors.slate700,
color: colorScheme.onSurfaceVariant,
),
),
),
@@ -272,10 +283,10 @@ class _CreateEventSheetState extends State<CreateEventSheet>
_isEditing
? context.l10n.calendarCreateEditTitle
: context.l10n.calendarCreateNewTitle,
style: const TextStyle(
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: AppColors.slate900,
color: colorScheme.onSurface,
),
),
ValueListenableBuilder<TextEditingValue>(
@@ -297,7 +308,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
? const AppLoadingIndicator(
variant: AppLoadingVariant.button,
size: 18,
trackColor: AppColors.blue200,
trackColor: null,
)
: Text(
context.l10n.commonSave,
@@ -305,8 +316,8 @@ class _CreateEventSheetState extends State<CreateEventSheet>
fontSize: 17,
fontWeight: FontWeight.w600,
color: enabled
? AppColors.blue600
: AppColors.slate400,
? colorScheme.primary
: colorScheme.outline,
),
),
),
@@ -319,15 +330,16 @@ class _CreateEventSheetState extends State<CreateEventSheet>
}
Widget _buildTabBar() {
final colorScheme = Theme.of(context).colorScheme;
return Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: AppColors.border)),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: colorScheme.outlineVariant)),
),
child: TabBar(
controller: _tabController,
labelColor: AppColors.blue600,
unselectedLabelColor: AppColors.slate600,
indicatorColor: AppColors.blue600,
labelColor: colorScheme.primary,
unselectedLabelColor: colorScheme.onSurfaceVariant,
indicatorColor: colorScheme.primary,
tabs: [
Tab(text: context.l10n.calendarCreateTabBasic),
Tab(text: context.l10n.calendarCreateTabAdvanced),
@@ -491,15 +503,16 @@ class _CreateEventSheetState extends State<CreateEventSheet>
bool isOptional = false,
DateTime? minTime,
}) {
final colorScheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isOptional ? context.l10n.calendarCreateOptionalField(label) : label,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
@@ -515,33 +528,33 @@ class _CreateEventSheetState extends State<CreateEventSheet>
child: Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.slate50,
color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderSecondary),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icon(
LucideIcons.calendar,
size: 16,
color: AppColors.slate600,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
_formatDateTimeLabel(date, time),
style: const TextStyle(
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: AppColors.slate900,
color: colorScheme.onSurface,
),
),
),
const Icon(
Icon(
LucideIcons.chevronRight,
size: 16,
color: AppColors.slate400,
color: colorScheme.outline,
),
],
),
@@ -568,7 +581,9 @@ class _CreateEventSheetState extends State<CreateEventSheet>
}) async {
final result = await showModalBottomSheet<(DateTime, DateTime)>(
context: context,
backgroundColor: Colors.transparent,
backgroundColor: Theme.of(
context,
).colorScheme.surface.withValues(alpha: 0),
isScrollControlled: true,
builder: (context) => DateTimePickerSheet(
initialDate: date,
@@ -580,6 +595,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
}
Widget _buildColorPicker() {
final colorScheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -588,15 +604,19 @@ class _CreateEventSheetState extends State<CreateEventSheet>
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Row(
children: defaultColors.map((color) {
children: _defaultColors.map((color) {
final colorHex =
'#${color.toARGB32().toRadixString(16).substring(2).toUpperCase()}';
final isSelected = _selectedColor == colorHex;
final checkColor =
ThemeData.estimateBrightnessForColor(color) == Brightness.dark
? Colors.white
: Colors.black;
return GestureDetector(
onTap: () => setState(() => _selectedColor = colorHex),
child: Container(
@@ -607,11 +627,11 @@ class _CreateEventSheetState extends State<CreateEventSheet>
color: color,
shape: BoxShape.circle,
border: isSelected
? Border.all(color: AppColors.slate900, width: 2)
? Border.all(color: colorScheme.onSurface, width: 2)
: null,
),
child: isSelected
? const Icon(Icons.check, size: 16, color: Colors.white)
? Icon(Icons.check, size: 16, color: checkColor)
: null,
),
);
@@ -622,6 +642,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
}
Widget _buildReminderPicker() {
final colorScheme = Theme.of(context).colorScheme;
String labelOf(int? value) {
if (value == null) {
return context.l10n.calendarCreateReminderNone;
@@ -640,7 +661,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
@@ -666,9 +687,9 @@ class _CreateEventSheetState extends State<CreateEventSheet>
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.slate50,
color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderSecondary),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
@@ -676,17 +697,17 @@ class _CreateEventSheetState extends State<CreateEventSheet>
Expanded(
child: Text(
labelOf(_reminderMinutes),
style: const TextStyle(
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: AppColors.slate900,
color: colorScheme.onSurface,
),
),
),
const Icon(
Icon(
LucideIcons.chevronRight,
size: 16,
color: AppColors.slate400,
color: colorScheme.outline,
),
],
),
@@ -1,8 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:social_app/core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
class DateTimePickerSheet extends StatefulWidget {
final DateTime initialDate;
final DateTime initialTime;
@@ -132,11 +131,12 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final colorScheme = Theme.of(context).colorScheme;
return Container(
height: 420,
decoration: const BoxDecoration(
color: AppColors.white,
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
@@ -172,7 +172,7 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
l10n.calendarDateTimePickerYearUnit,
style: TextStyle(
fontSize: 14,
color: AppColors.slate600,
color: colorScheme.onSurfaceVariant,
),
),
Expanded(
@@ -193,7 +193,7 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
l10n.calendarDateTimePickerMonthUnit,
style: TextStyle(
fontSize: 14,
color: AppColors.slate600,
color: colorScheme.onSurfaceVariant,
),
),
Expanded(
@@ -208,7 +208,7 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
l10n.calendarDateTimePickerDayUnit,
style: TextStyle(
fontSize: 14,
color: AppColors.slate600,
color: colorScheme.onSurfaceVariant,
),
),
],
@@ -217,7 +217,11 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
],
),
),
Container(width: 1, height: 180, color: AppColors.border),
Container(
width: 1,
height: 180,
color: colorScheme.outlineVariant,
),
Expanded(
flex: 2,
child: Column(
@@ -256,12 +260,12 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
itemExtent: 50,
),
),
const Text(
Text(
' : ',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColors.slate600,
color: colorScheme.onSurfaceVariant,
),
),
Expanded(
@@ -289,12 +293,13 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
Widget _buildHeader() {
final l10n = context.l10n;
final colorScheme = Theme.of(context).colorScheme;
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: AppColors.border)),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: colorScheme.outlineVariant)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -304,7 +309,10 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
onTap: () => Navigator.pop(context),
child: Text(
l10n.commonCancel,
style: const TextStyle(fontSize: 17, color: AppColors.slate600),
style: TextStyle(
fontSize: 17,
color: colorScheme.onSurfaceVariant,
),
),
),
Text(
@@ -312,7 +320,7 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: AppColors.slate900,
color: colorScheme.onSurface,
),
),
GestureDetector(
@@ -324,10 +332,10 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
},
child: Text(
l10n.commonConfirm,
style: const TextStyle(
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: AppColors.blue600,
color: colorScheme.primary,
),
),
),
@@ -337,14 +345,15 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
}
Widget _buildPickerLabel(String label) {
final colorScheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
label,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
@@ -358,6 +367,7 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
String Function(int) formatter, {
double itemExtent = 40,
}) {
final colorScheme = Theme.of(context).colorScheme;
return CupertinoPicker(
scrollController: controller,
itemExtent: itemExtent,
@@ -369,7 +379,7 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
decoration: BoxDecoration(
border: Border.symmetric(
horizontal: BorderSide(
color: AppColors.blue100.withValues(alpha: 0.5),
color: colorScheme.primary.withValues(alpha: 0.3),
width: 1,
),
),
@@ -379,7 +389,7 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
return Center(
child: Text(
formatter(items[index]),
style: const TextStyle(fontSize: 18, color: AppColors.slate900),
style: TextStyle(fontSize: 18, color: colorScheme.onSurface),
),
);
}),