Add file uploads for photos and family icons

This commit is contained in:
kihong.kim
2026-01-24 22:31:38 +09:00
parent 29881aa442
commit 9e6a265a7a
15 changed files with 761 additions and 133 deletions

View File

@@ -1,4 +1,5 @@
import "dart:convert";
import "dart:typed_data";
import "package:http/http.dart" as http;
import "../config/api_config.dart";
@@ -45,6 +46,30 @@ class ApiClient {
return jsonDecode(response.body) as Map<String, dynamic>;
}
Future<Map<String, dynamic>> postMultipart(
String path, {
required String fieldName,
required Uint8List bytes,
required String filename,
Map<String, String>? fields,
}) async {
final request = http.MultipartRequest("POST", _uri(path));
if (fields != null) {
request.fields.addAll(fields);
}
request.files.add(
http.MultipartFile.fromBytes(
fieldName,
bytes,
filename: filename,
),
);
final streamed = await _client.send(request);
final response = await http.Response.fromStream(streamed);
_ensureSuccess(response);
return jsonDecode(response.body) as Map<String, dynamic>;
}
Future<Map<String, dynamic>> put(
String path,
Map<String, dynamic> body,

View File

@@ -1,3 +1,5 @@
import "dart:typed_data";
import "../config/api_config.dart";
import "../models/family_member.dart";
import "api_client.dart";
@@ -23,6 +25,7 @@ class FamilyService {
final created = FamilyMember(
id: "family-${DateTime.now().millisecondsSinceEpoch}",
name: member.name,
iconUrl: member.iconUrl,
emoji: member.emoji,
color: member.color,
order: member.order,
@@ -51,6 +54,22 @@ class FamilyService {
return FamilyMember.fromJson(data);
}
Future<String> uploadFamilyIcon({
required Uint8List bytes,
required String filename,
}) async {
if (ApiConfig.useMockData) {
return "mock://$filename";
}
final data = await _client.postMultipart(
"${ApiConfig.family}/upload-icon",
fieldName: "file",
bytes: bytes,
filename: filename,
);
return data["url"] as String? ?? "";
}
Future<void> deleteFamilyMember(String id) async {
if (ApiConfig.useMockData) {
MockDataStore.familyMembers.removeWhere((item) => item.id == id);

View File

@@ -11,28 +11,32 @@ class MockDataStore {
const FamilyMember(
id: "family-1",
name: "Dad",
emoji: ":)",
iconUrl: "",
emoji: "",
color: "#0F766E",
order: 1,
),
const FamilyMember(
id: "family-2",
name: "Mom",
emoji: "<3",
iconUrl: "",
emoji: "",
color: "#C2410C",
order: 2,
),
const FamilyMember(
id: "family-3",
name: "Son",
emoji: ":D",
iconUrl: "",
emoji: "",
color: "#1D4ED8",
order: 3,
),
const FamilyMember(
id: "family-4",
name: "Daughter",
emoji: ":-)",
iconUrl: "",
emoji: "",
color: "#7C3AED",
order: 4,
),

View File

@@ -1,3 +1,5 @@
import "dart:typed_data";
import "../config/api_config.dart";
import "../models/photo.dart";
import "api_client.dart";
@@ -38,6 +40,35 @@ class PhotoService {
return Photo.fromJson(data);
}
Future<Photo> uploadPhotoBytes({
required Uint8List bytes,
required String filename,
String caption = "",
bool active = true,
}) async {
if (ApiConfig.useMockData) {
final created = Photo(
id: "photo-${DateTime.now().millisecondsSinceEpoch}",
url: "mock://$filename",
caption: caption,
active: active,
);
MockDataStore.photos.add(created);
return created;
}
final data = await _client.postMultipart(
"${ApiConfig.photos}/upload",
fieldName: "file",
bytes: bytes,
filename: filename,
fields: {
"caption": caption,
"active": active.toString(),
},
);
return Photo.fromJson(data);
}
Future<void> deletePhoto(String id) async {
if (ApiConfig.useMockData) {
MockDataStore.photos.removeWhere((item) => item.id == id);