Enhance: Google Photos integration, refinement of Schedule/Todo lists and API fixes

This commit is contained in:
kihong.kim
2026-02-01 01:05:33 +09:00
parent c614c883d4
commit 5fa24d61c9
7 changed files with 56 additions and 40 deletions

View File

@@ -23,7 +23,18 @@ router.get("/", async (req, res) => {
router.get("/today", async (req, res) => { router.get("/today", async (req, res) => {
try { try {
const { start, end } = getDayRange(); const { start, end } = getDayRange();
const todos = await Todo.find({ dueDate: { $gte: start, $lte: end } }).sort({ // Include:
// 1. Tasks due today
// 2. Overdue tasks (due before today) that are not completed
// 3. Tasks with no due date that are not completed
const todos = await Todo.find({
$or: [
{ dueDate: { $gte: start, $lte: end } },
{ dueDate: { $lt: start }, completed: false },
{ dueDate: { $exists: false }, completed: false },
{ dueDate: null, completed: false }
]
}).sort({
dueDate: 1, dueDate: 1,
createdAt: -1, createdAt: -1,
}); });

View File

@@ -45,25 +45,22 @@ router.get("/", async (req, res) => {
const currentData = currentResponse.data; const currentData = currentResponse.data;
// 3. Process Forecast for Today's Min/Max (Seoul Time: UTC+9) // 3. Process Forecast for Today's Min/Max (Rolling 24-hour window for stability)
// OpenWeatherMap returns timestamps in UTC. // We use the first 8 segments (3h * 8 = 24h) to ensure we always have a full
// Seoul is UTC+9. // day-night cycle of temperatures, providing a stable daily range.
const SeoulOffset = 9 * 60 * 60 * 1000; const forecastList = forecastResponse.data.list;
const nowKST = new Date(Date.now() + SeoulOffset); if (forecastList && forecastList.length > 0) {
const todayStr = nowKST.toISOString().split("T")[0]; // YYYY-MM-DD in KST const next24h = forecastList.slice(0, 8);
const todayItems = forecastResponse.data.list.filter((item) => { // Calculate min/max from forecast
const itemDateKST = new Date(item.dt * 1000 + SeoulOffset); let minTemp = Math.min(...next24h.map((item) => item.main.temp_min));
const itemDateStr = itemDateKST.toISOString().split("T")[0]; let maxTemp = Math.max(...next24h.map((item) => item.main.temp_max));
return itemDateStr === todayStr;
});
if (todayItems.length > 0) { // Also compare with current temperature to ensure the range covers current state
// Find min and max from the 3-hour segments const currentTemp = currentData.main.temp;
const minTemp = Math.min(...todayItems.map((item) => item.main.temp_min)); minTemp = Math.min(minTemp, currentTemp);
const maxTemp = Math.max(...todayItems.map((item) => item.main.temp_max)); maxTemp = Math.max(maxTemp, currentTemp);
// Update the response structure
currentData.main.temp_min = minTemp; currentData.main.temp_min = minTemp;
currentData.main.temp_max = maxTemp; currentData.main.temp_max = maxTemp;
} }

View File

@@ -1559,10 +1559,14 @@ class _TodoManagerTabState extends State<TodoManagerTab> {
), ),
), ),
subtitle: todo.dueDate != null subtitle: todo.dueDate != null
? Text( ? Builder(
todo.dueDate!.toIso8601String().split('T')[0], builder: (context) {
style: final dueDate = todo.dueDate!;
const TextStyle(fontSize: 12, color: Colors.grey), return Text(
dueDate.toIso8601String().split('T')[0],
style: const TextStyle(fontSize: 12, color: Colors.grey),
);
},
) )
: null, : null,
trailing: Row( trailing: Row(

View File

@@ -78,13 +78,13 @@ class PhotoService {
} }
Future<String> getGoogleAuthUrl() async { Future<String> getGoogleAuthUrl() async {
final data = await _client.get("${ApiConfig.photos}/auth/url"); final data = await _client.getMap("${ApiConfig.photos}/auth/url");
return data["url"] as String; return data["url"] as String;
} }
Future<bool> getGoogleStatus() async { Future<bool> getGoogleStatus() async {
try { try {
final data = await _client.get("${ApiConfig.photos}/status"); final data = await _client.getMap("${ApiConfig.photos}/status");
return data["connected"] as bool? ?? false; return data["connected"] as bool? ?? false;
} catch (e) { } catch (e) {
return false; return false;
@@ -92,6 +92,6 @@ class PhotoService {
} }
Future<void> disconnectGoogle() async { Future<void> disconnectGoogle() async {
await _client.get("${ApiConfig.photos}/disconnect"); await _client.getMap("${ApiConfig.photos}/disconnect");
} }
} }

View File

@@ -20,14 +20,23 @@ class TodoService {
Future<List<TodoItem>> fetchTodayTodos() async { Future<List<TodoItem>> fetchTodayTodos() async {
if (ApiConfig.useMockData) { if (ApiConfig.useMockData) {
final today = DateTime.now(); final now = DateTime.now();
return MockDataStore.todos.where((todo) => todo.dueDate != null).where(( final today = DateTime(now.year, now.month, now.day);
todo, final tomorrow = today.add(const Duration(days: 1));
) {
final date = todo.dueDate!; return MockDataStore.todos.where((todo) {
return date.year == today.year && if (todo.completed) {
date.month == today.month && // If completed, only show if it was due today
date.day == today.day; if (todo.dueDate == null) return false;
return todo.dueDate!.isAfter(today.subtract(const Duration(milliseconds: 1))) &&
todo.dueDate!.isBefore(tomorrow);
}
// If not completed:
// 1. No due date -> show
// 2. Due today or before -> show
if (todo.dueDate == null) return true;
return todo.dueDate!.isBefore(tomorrow);
}).toList(); }).toList();
} }
final data = await _client.getList("${ApiConfig.todos}/today"); final data = await _client.getList("${ApiConfig.todos}/today");

View File

@@ -112,8 +112,8 @@ class _ScheduleListWidgetState extends State<ScheduleListWidget> {
final endOfWeek = startOfWeek.add(const Duration(days: 6, hours: 23, minutes: 59, seconds: 59)); final endOfWeek = startOfWeek.add(const Duration(days: 6, hours: 23, minutes: 59, seconds: 59));
final filteredSchedules = _schedules.where((item) { final filteredSchedules = _schedules.where((item) {
// Overlap check: schedule starts before week ends AND schedule ends after week starts // Overlap check: schedule starts before week ends AND schedule ends after or on today
return item.startDate.isBefore(endOfWeek) && item.endDate.isAfter(startOfWeek); return item.startDate.isBefore(endOfWeek) && (item.endDate.isAtSameMomentAs(today) || item.endDate.isAfter(today));
}).toList(); }).toList();
if (filteredSchedules.isEmpty) { if (filteredSchedules.isEmpty) {

View File

@@ -219,12 +219,7 @@ class _TodoListWidgetState extends State<TodoListWidget> {
decorationColor: Colors.white54, decorationColor: Colors.white54,
), ),
), ),
subtitle: todo.dueDate != null && (todo.dueDate!.hour != 0 || todo.dueDate!.minute != 0) subtitle: null,
? Text(
DateFormat('HH:mm').format(todo.dueDate!),
style: const TextStyle(color: Colors.white54, fontSize: 12),
)
: null,
trailing: Checkbox( trailing: Checkbox(
value: todo.completed, value: todo.completed,
onChanged: (val) async { onChanged: (val) async {