Enhance: Google Photos integration, refinement of Schedule/Todo lists and API fixes
This commit is contained in:
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user