Initial Közös Tér and JARVIS API codebase
This commit is contained in:
+726
@@ -0,0 +1,726 @@
|
||||
part of 'main.dart';
|
||||
|
||||
const ink = Color(0xFF18332D);
|
||||
const sage = Color(0xFF2F6B5B);
|
||||
const mint = Color(0xFFE7F1EC);
|
||||
const cream = Color(0xFFF7F5EF);
|
||||
const orange = Color(0xFFE89552);
|
||||
const everyonePersonId = '_everyone';
|
||||
const defaultJarvisApiUrl = 'https://api-test.a-web.hu';
|
||||
const legacyJarvisApiUrl = 'http://80.211.202.74';
|
||||
const appVersionLabel = 'v1.0.0+1';
|
||||
|
||||
const avatarPresets = [
|
||||
AvatarPreset('boy', 'Fi\u00fa', '\u{1f9d2}'),
|
||||
AvatarPreset('girl', 'L\u00e1ny', '\u{1f467}'),
|
||||
AvatarPreset('man', 'F\u00e9rfi', '\u{1f468}'),
|
||||
AvatarPreset('woman', 'N\u0151', '\u{1f469}'),
|
||||
AvatarPreset('dog', 'Kutya', '\u{1f436}'),
|
||||
AvatarPreset('cat', 'Macska', '\u{1f431}'),
|
||||
AvatarPreset('snowman', 'H\u00f3ember', '\u2603\ufe0f'),
|
||||
AvatarPreset('strawberry', 'Eper', '\u{1f353}'),
|
||||
AvatarPreset('sun', 'Napocska', '\u2600\ufe0f'),
|
||||
AvatarPreset('flower', 'Vir\u00e1g', '\u{1f338}'),
|
||||
AvatarPreset('car', 'Aut\u00f3', '\u{1f697}'),
|
||||
AvatarPreset('rocket', 'Rak\u00e9ta', '\u{1f680}'),
|
||||
];
|
||||
|
||||
const personColors = [
|
||||
Color(0xFF7189C8),
|
||||
Color(0xFFE89552),
|
||||
Color(0xFF9B78B4),
|
||||
Color(0xFF79A894),
|
||||
Color(0xFFD65A6F),
|
||||
Color(0xFFD6A74D),
|
||||
Color(0xFF4C9BCF),
|
||||
Color(0xFF6A9F58),
|
||||
];
|
||||
|
||||
class AvatarPreset {
|
||||
const AvatarPreset(this.id, this.label, this.symbol);
|
||||
|
||||
final String id;
|
||||
final String label;
|
||||
final String symbol;
|
||||
}
|
||||
|
||||
class AppThemeStyle {
|
||||
const AppThemeStyle({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.seed,
|
||||
required this.surface,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final String description;
|
||||
final Color seed;
|
||||
final Color surface;
|
||||
}
|
||||
|
||||
const appThemes = [
|
||||
AppThemeStyle(
|
||||
id: 'sage',
|
||||
name: 'Zs\u00e1lya otthon',
|
||||
description: 'Nyugodt, term\u00e9szetes csal\u00e1di alapst\u00edlus.',
|
||||
seed: sage,
|
||||
surface: cream,
|
||||
),
|
||||
AppThemeStyle(
|
||||
id: 'berry',
|
||||
name: 'Eperkr\u00e9m',
|
||||
description:
|
||||
'Melegebb, j\u00e1t\u00e9kosabb sz\u00ednek gyerekes csal\u00e1dokhoz.',
|
||||
seed: Color(0xFFD65A6F),
|
||||
surface: Color(0xFFFFF5F6),
|
||||
),
|
||||
AppThemeStyle(
|
||||
id: 'sky',
|
||||
name: 'Tiszta \u00e9g',
|
||||
description:
|
||||
'H\u0171v\u00f6sebb, rendezett, napt\u00e1rk\u00f6zpont\u00fa hangulat.',
|
||||
seed: Color(0xFF4C9BCF),
|
||||
surface: Color(0xFFF1F7FB),
|
||||
),
|
||||
AppThemeStyle(
|
||||
id: 'sunny',
|
||||
name: 'Naps\u00e1rga',
|
||||
description:
|
||||
'Vil\u00e1gos, energikus, konyhai \u00e9s heti tervez\u0151s \u00e9rzet.',
|
||||
seed: Color(0xFFD6A74D),
|
||||
surface: Color(0xFFFFFAED),
|
||||
),
|
||||
];
|
||||
|
||||
class AppJsonStore {
|
||||
Future<File> _file() async {
|
||||
try {
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
return File('${dir.path}${Platform.pathSeparator}kozos_ter.json');
|
||||
} on MissingPluginException {
|
||||
return File(
|
||||
'${Directory.systemTemp.path}${Platform.pathSeparator}kozos_ter.json',
|
||||
);
|
||||
} on UnsupportedError {
|
||||
return File(
|
||||
'${Directory.systemTemp.path}${Platform.pathSeparator}kozos_ter.json',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<AppSettings> load() async {
|
||||
try {
|
||||
final file = await _file();
|
||||
if (!await file.exists()) {
|
||||
final settings = AppSettings(
|
||||
people: defaultPeople(),
|
||||
themeId: 'sage',
|
||||
googleConnections: defaultGoogleConnections(),
|
||||
syncedEvents: const [],
|
||||
todoSuggestions: const [],
|
||||
jarvisSettings: JarvisSettings.defaults(),
|
||||
);
|
||||
await save(settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
final raw = await file.readAsString();
|
||||
final json = jsonDecode(raw) as Map<String, dynamic>;
|
||||
return AppSettings.fromJson(json);
|
||||
} catch (_) {
|
||||
return AppSettings(
|
||||
people: defaultPeople(),
|
||||
themeId: 'sage',
|
||||
googleConnections: defaultGoogleConnections(),
|
||||
syncedEvents: const [],
|
||||
todoSuggestions: const [],
|
||||
jarvisSettings: JarvisSettings.defaults(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> save(AppSettings settings) async {
|
||||
final file = await _file();
|
||||
await file.parent.create(recursive: true);
|
||||
const encoder = JsonEncoder.withIndent(' ');
|
||||
await file.writeAsString(encoder.convert(settings.toJson()));
|
||||
}
|
||||
}
|
||||
|
||||
class AppSettings {
|
||||
AppSettings({
|
||||
required this.people,
|
||||
required this.themeId,
|
||||
required this.googleConnections,
|
||||
required this.syncedEvents,
|
||||
required this.todoSuggestions,
|
||||
required this.jarvisSettings,
|
||||
});
|
||||
|
||||
final List<Person> people;
|
||||
final String themeId;
|
||||
final List<GoogleCalendarConnection> googleConnections;
|
||||
final List<CalendarEvent> syncedEvents;
|
||||
final List<String> todoSuggestions;
|
||||
final JarvisSettings jarvisSettings;
|
||||
|
||||
factory AppSettings.fromJson(Map<String, dynamic> json) {
|
||||
final rawPeople = json['people'];
|
||||
final rawConnections = json['googleConnections'];
|
||||
final rawSyncedEvents = json['syncedEvents'];
|
||||
final rawTodoSuggestions = json['todoSuggestions'];
|
||||
final rawJarvisSettings = json['jarvisSettings'];
|
||||
return AppSettings(
|
||||
people: rawPeople is List
|
||||
? normalizeDefaultPeople(
|
||||
rawPeople
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(Person.fromJson)
|
||||
.toList(),
|
||||
)
|
||||
: defaultPeople(),
|
||||
themeId: json['themeId'] as String? ?? 'sage',
|
||||
googleConnections: rawConnections is List
|
||||
? rawConnections
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(GoogleCalendarConnection.fromJson)
|
||||
.toList()
|
||||
: defaultGoogleConnections(),
|
||||
syncedEvents: rawSyncedEvents is List
|
||||
? rawSyncedEvents
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(CalendarEvent.fromJson)
|
||||
.toList()
|
||||
: const [],
|
||||
todoSuggestions: rawTodoSuggestions is List
|
||||
? rawTodoSuggestions.whereType<String>().toList()
|
||||
: const [],
|
||||
jarvisSettings: rawJarvisSettings is Map<String, dynamic>
|
||||
? JarvisSettings.fromJson(rawJarvisSettings)
|
||||
: JarvisSettings.defaults(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'themeId': themeId,
|
||||
'people': people.map((person) => person.toJson()).toList(),
|
||||
'googleConnections': googleConnections
|
||||
.map((connection) => connection.toJson())
|
||||
.toList(),
|
||||
'syncedEvents': syncedEvents.map((event) => event.toJson()).toList(),
|
||||
'todoSuggestions': todoSuggestions,
|
||||
'jarvisSettings': jarvisSettings.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
class JarvisSettings {
|
||||
JarvisSettings({
|
||||
required this.apiUrl,
|
||||
required this.apiToken,
|
||||
this.deviceName,
|
||||
this.deviceId,
|
||||
this.deviceToken,
|
||||
this.registrationStatus = 'notRegistered',
|
||||
});
|
||||
|
||||
final String apiUrl;
|
||||
final String apiToken;
|
||||
final String? deviceName;
|
||||
final int? deviceId;
|
||||
final String? deviceToken;
|
||||
final String registrationStatus;
|
||||
|
||||
factory JarvisSettings.defaults() =>
|
||||
JarvisSettings(apiUrl: defaultJarvisApiUrl, apiToken: '');
|
||||
|
||||
factory JarvisSettings.fromJson(Map<String, dynamic> json) => JarvisSettings(
|
||||
apiUrl: (json['apiUrl'] as String?) == legacyJarvisApiUrl
|
||||
? defaultJarvisApiUrl
|
||||
: json['apiUrl'] as String? ?? JarvisSettings.defaults().apiUrl,
|
||||
apiToken: json['apiToken'] as String? ?? '',
|
||||
deviceName: json['deviceName'] as String?,
|
||||
deviceId: json['deviceId'] as int?,
|
||||
deviceToken: json['deviceToken'] as String?,
|
||||
registrationStatus:
|
||||
json['registrationStatus'] as String? ?? 'notRegistered',
|
||||
);
|
||||
|
||||
JarvisSettings copyWith({
|
||||
String? apiUrl,
|
||||
String? apiToken,
|
||||
String? deviceName,
|
||||
int? deviceId,
|
||||
String? deviceToken,
|
||||
String? registrationStatus,
|
||||
bool clearDevice = false,
|
||||
}) {
|
||||
return JarvisSettings(
|
||||
apiUrl: apiUrl ?? this.apiUrl,
|
||||
apiToken: apiToken ?? this.apiToken,
|
||||
deviceName: clearDevice ? null : deviceName ?? this.deviceName,
|
||||
deviceId: clearDevice ? null : deviceId ?? this.deviceId,
|
||||
deviceToken: clearDevice ? null : deviceToken ?? this.deviceToken,
|
||||
registrationStatus: clearDevice
|
||||
? 'notRegistered'
|
||||
: registrationStatus ?? this.registrationStatus,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'apiUrl': apiUrl,
|
||||
'apiToken': apiToken,
|
||||
'deviceName': deviceName,
|
||||
'deviceId': deviceId,
|
||||
'deviceToken': deviceToken,
|
||||
'registrationStatus': registrationStatus,
|
||||
};
|
||||
}
|
||||
|
||||
class GoogleCalendarConnection {
|
||||
GoogleCalendarConnection({
|
||||
required this.id,
|
||||
required this.accountEmail,
|
||||
required this.enabledCalendarIds,
|
||||
this.calendarPersonIds = const {},
|
||||
required this.lastSync,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String accountEmail;
|
||||
final List<String> enabledCalendarIds;
|
||||
final Map<String, String> calendarPersonIds;
|
||||
final DateTime? lastSync;
|
||||
|
||||
GoogleCalendarConnection copyWith({
|
||||
String? accountEmail,
|
||||
List<String>? enabledCalendarIds,
|
||||
Map<String, String>? calendarPersonIds,
|
||||
DateTime? lastSync,
|
||||
}) {
|
||||
return GoogleCalendarConnection(
|
||||
id: id,
|
||||
accountEmail: accountEmail ?? this.accountEmail,
|
||||
enabledCalendarIds: enabledCalendarIds ?? this.enabledCalendarIds,
|
||||
calendarPersonIds: calendarPersonIds ?? this.calendarPersonIds,
|
||||
lastSync: lastSync ?? this.lastSync,
|
||||
);
|
||||
}
|
||||
|
||||
factory GoogleCalendarConnection.fromJson(Map<String, dynamic> json) {
|
||||
final rawCalendarIds = json['enabledCalendarIds'];
|
||||
final rawCalendarPersonIds = json['calendarPersonIds'];
|
||||
return GoogleCalendarConnection(
|
||||
id: json['id'] as String? ?? uniqueId(),
|
||||
accountEmail: json['accountEmail'] as String? ?? '',
|
||||
enabledCalendarIds: rawCalendarIds is List
|
||||
? rawCalendarIds.whereType<String>().toList()
|
||||
: const [],
|
||||
calendarPersonIds: rawCalendarPersonIds is Map
|
||||
? rawCalendarPersonIds.map(
|
||||
(key, value) => MapEntry(key.toString(), value.toString()),
|
||||
)
|
||||
: const {},
|
||||
lastSync: DateTime.tryParse(json['lastSync'] as String? ?? ''),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'accountEmail': accountEmail,
|
||||
'enabledCalendarIds': enabledCalendarIds,
|
||||
'calendarPersonIds': calendarPersonIds,
|
||||
'lastSync': lastSync?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
class GoogleCalendarOption {
|
||||
const GoogleCalendarOption({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final String description;
|
||||
final Color color;
|
||||
}
|
||||
|
||||
const availableGoogleCalendars = [
|
||||
GoogleCalendarOption(
|
||||
id: 'primary',
|
||||
name: 'Saj\u00e1t napt\u00e1r',
|
||||
description: 'A f\u0151 Google Calendar esem\u00e9nyeid.',
|
||||
color: Color(0xFF4285F4),
|
||||
),
|
||||
GoogleCalendarOption(
|
||||
id: 'family',
|
||||
name: 'Csal\u00e1di napt\u00e1r',
|
||||
description:
|
||||
'K\u00f6z\u00f6s csal\u00e1di programok \u00e9s eml\u00e9keztet\u0151k.',
|
||||
color: Color(0xFF34A853),
|
||||
),
|
||||
GoogleCalendarOption(
|
||||
id: 'school',
|
||||
name: 'Ovi / iskola',
|
||||
description:
|
||||
'Iskolai, \u00f3vodai \u00e9s k\u00fcl\u00f6n\u00f3r\u00e1s esem\u00e9nyek.',
|
||||
color: Color(0xFFFBBC05),
|
||||
),
|
||||
GoogleCalendarOption(
|
||||
id: 'work',
|
||||
name: 'Munka',
|
||||
description:
|
||||
'Munkahelyi id\u0151pontok, ha szeretn\u00e9d l\u00e1tni \u0151ket.',
|
||||
color: Color(0xFF9B78B4),
|
||||
),
|
||||
];
|
||||
|
||||
List<GoogleCalendarConnection> defaultGoogleConnections() => [];
|
||||
|
||||
class Person {
|
||||
Person({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.colorValue,
|
||||
required this.avatar,
|
||||
this.nextEventLimit = 1,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final int colorValue;
|
||||
final String avatar;
|
||||
final int nextEventLimit;
|
||||
|
||||
Color get color => Color(colorValue);
|
||||
|
||||
Person copyWith({
|
||||
String? name,
|
||||
int? colorValue,
|
||||
String? avatar,
|
||||
int? nextEventLimit,
|
||||
}) {
|
||||
return Person(
|
||||
id: id,
|
||||
name: name ?? this.name,
|
||||
colorValue: colorValue ?? this.colorValue,
|
||||
avatar: avatar ?? this.avatar,
|
||||
nextEventLimit: nextEventLimit ?? this.nextEventLimit,
|
||||
);
|
||||
}
|
||||
|
||||
factory Person.fromJson(Map<String, dynamic> json) {
|
||||
return Person(
|
||||
id: json['id'] as String? ?? uniqueId(),
|
||||
name: json['name'] as String? ?? 'Csal\u00e1dtag',
|
||||
colorValue: json['colorValue'] as int? ?? personColors.first.toARGB32(),
|
||||
avatar: json['avatar'] as String? ?? avatarPresets.first.symbol,
|
||||
nextEventLimit: json['nextEventLimit'] as int? ?? 1,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'colorValue': colorValue,
|
||||
'avatar': avatar,
|
||||
'nextEventLimit': nextEventLimit,
|
||||
};
|
||||
}
|
||||
|
||||
List<Person> defaultPeople() => [];
|
||||
|
||||
List<Person> normalizeDefaultPeople(List<Person> current) {
|
||||
const seededIds = {'zsolt', 'eniko', 'balint', 'emilia', 'noemi'};
|
||||
return current.where((person) => !seededIds.contains(person.id)).toList();
|
||||
}
|
||||
|
||||
class CalendarEvent {
|
||||
CalendarEvent(
|
||||
this.title,
|
||||
this.start,
|
||||
this.end,
|
||||
this.color, {
|
||||
required this.date,
|
||||
this.place = '',
|
||||
this.personId,
|
||||
this.externalId,
|
||||
this.calendarId,
|
||||
this.calendarName,
|
||||
this.note = '',
|
||||
this.hasTodos = false,
|
||||
this.todos = const [],
|
||||
this.source = EventSource.local,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String start;
|
||||
final String end;
|
||||
final Color color;
|
||||
final String place;
|
||||
final String? personId;
|
||||
final DateTime date;
|
||||
final String? externalId;
|
||||
final String? calendarId;
|
||||
final String? calendarName;
|
||||
final String note;
|
||||
final bool hasTodos;
|
||||
final List<String> todos;
|
||||
final EventSource source;
|
||||
|
||||
bool get isSynced => source == EventSource.deviceCalendar;
|
||||
bool get isUncategorized => personId == null;
|
||||
bool get isForEveryone => personId == everyonePersonId;
|
||||
|
||||
CalendarEvent copyWith({
|
||||
String? title,
|
||||
String? start,
|
||||
String? end,
|
||||
Color? color,
|
||||
String? place,
|
||||
String? personId,
|
||||
DateTime? date,
|
||||
String? note,
|
||||
bool? hasTodos,
|
||||
List<String>? todos,
|
||||
bool clearPerson = false,
|
||||
}) {
|
||||
return CalendarEvent(
|
||||
title ?? this.title,
|
||||
start ?? this.start,
|
||||
end ?? this.end,
|
||||
color ?? this.color,
|
||||
date: date ?? this.date,
|
||||
place: place ?? this.place,
|
||||
personId: clearPerson ? null : personId ?? this.personId,
|
||||
externalId: externalId,
|
||||
calendarId: calendarId,
|
||||
calendarName: calendarName,
|
||||
note: note ?? this.note,
|
||||
hasTodos: hasTodos ?? this.hasTodos,
|
||||
todos: todos ?? this.todos,
|
||||
source: source,
|
||||
);
|
||||
}
|
||||
|
||||
factory CalendarEvent.fromJson(Map<String, dynamic> json) {
|
||||
return CalendarEvent(
|
||||
json['title'] as String? ?? 'N\u00e9vtelen esem\u00e9ny',
|
||||
json['start'] as String? ?? '00:00',
|
||||
json['end'] as String? ?? '',
|
||||
Color(json['colorValue'] as int? ?? sage.toARGB32()),
|
||||
date: DateTime.tryParse(json['date'] as String? ?? '') ?? DateTime.now(),
|
||||
place: json['place'] as String? ?? '',
|
||||
personId: json['personId'] as String?,
|
||||
externalId: json['externalId'] as String?,
|
||||
calendarId: json['calendarId'] as String?,
|
||||
calendarName: json['calendarName'] as String?,
|
||||
note: json['note'] as String? ?? '',
|
||||
hasTodos: json['hasTodos'] as bool? ?? false,
|
||||
todos: json['todos'] is List
|
||||
? (json['todos'] as List).whereType<String>().toList()
|
||||
: const [],
|
||||
source: EventSource.values.firstWhere(
|
||||
(source) => source.name == json['source'],
|
||||
orElse: () => EventSource.local,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
factory CalendarEvent.fromDeviceEvent(
|
||||
device_calendar.Event event,
|
||||
device_calendar.Calendar calendar, {
|
||||
String? personId,
|
||||
}) {
|
||||
final startDate = event.start?.toLocal() ?? DateTime.now();
|
||||
final endDate = event.end?.toLocal();
|
||||
return CalendarEvent(
|
||||
event.title?.trim().isNotEmpty == true
|
||||
? event.title!.trim()
|
||||
: 'N\u00e9vtelen napt\u00e1resem\u00e9ny',
|
||||
event.allDay == true ? 'Eg\u00e9sz nap' : formatTime(startDate),
|
||||
event.allDay == true || endDate == null ? '' : formatTime(endDate),
|
||||
Color(calendar.color ?? const Color(0xFF4285F4).toARGB32()),
|
||||
date: DateTime(startDate.year, startDate.month, startDate.day),
|
||||
place: [
|
||||
calendar.name ?? 'Napt\u00e1r',
|
||||
if ((event.location ?? '').trim().isNotEmpty) event.location!.trim(),
|
||||
].join(' \u00b7 '),
|
||||
externalId: event.eventId,
|
||||
calendarId: calendar.id,
|
||||
calendarName: calendar.name,
|
||||
personId: personId,
|
||||
note: event.description ?? '',
|
||||
source: EventSource.deviceCalendar,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'title': title,
|
||||
'start': start,
|
||||
'end': end,
|
||||
'colorValue': color.toARGB32(),
|
||||
'date': date.toIso8601String(),
|
||||
'place': place,
|
||||
'personId': personId,
|
||||
'externalId': externalId,
|
||||
'calendarId': calendarId,
|
||||
'calendarName': calendarName,
|
||||
'note': note,
|
||||
'hasTodos': hasTodos,
|
||||
'todos': todos,
|
||||
'source': source.name,
|
||||
};
|
||||
}
|
||||
|
||||
enum EventSource { local, deviceCalendar }
|
||||
|
||||
class ShoppingItem {
|
||||
ShoppingItem(this.name, this.amount, this.category, {this.done = false});
|
||||
|
||||
final String name;
|
||||
final String amount;
|
||||
final String category;
|
||||
bool done;
|
||||
}
|
||||
|
||||
class StockItem {
|
||||
StockItem(this.name, this.amount, this.unit, this.location, this.expiry);
|
||||
|
||||
String name;
|
||||
int amount;
|
||||
String unit;
|
||||
String location;
|
||||
String expiry;
|
||||
}
|
||||
|
||||
class JarvisProduct {
|
||||
const JarvisProduct({
|
||||
required this.barcode,
|
||||
required this.name,
|
||||
this.brand,
|
||||
this.quantity,
|
||||
this.imageUrl,
|
||||
this.source,
|
||||
this.fromCache = false,
|
||||
});
|
||||
|
||||
final String barcode;
|
||||
final String name;
|
||||
final String? brand;
|
||||
final String? quantity;
|
||||
final String? imageUrl;
|
||||
final String? source;
|
||||
final bool fromCache;
|
||||
|
||||
factory JarvisProduct.fromJson(Map<String, dynamic> json) => JarvisProduct(
|
||||
barcode: json['barcode'] as String? ?? '',
|
||||
name: json['name'] as String? ?? '',
|
||||
brand: json['brand'] as String?,
|
||||
quantity: json['quantity'] as String?,
|
||||
imageUrl: json['imageUrl'] as String?,
|
||||
source: json['source'] as String?,
|
||||
fromCache: json['fromCache'] as bool? ?? false,
|
||||
);
|
||||
|
||||
String get displayName {
|
||||
final parts = [
|
||||
name,
|
||||
if (brand != null && brand!.trim().isNotEmpty) brand!,
|
||||
if (quantity != null && quantity!.trim().isNotEmpty) quantity!,
|
||||
];
|
||||
return parts.join(' \u00b7 ');
|
||||
}
|
||||
}
|
||||
|
||||
class WeatherSnapshot {
|
||||
const WeatherSnapshot({
|
||||
required this.locationName,
|
||||
required this.temperature,
|
||||
required this.description,
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
final String locationName;
|
||||
final double? temperature;
|
||||
final String description;
|
||||
final IconData icon;
|
||||
|
||||
String get temperatureLabel =>
|
||||
temperature == null ? '\u2014' : '${temperature!.round()}\u00b0C';
|
||||
}
|
||||
|
||||
class WeatherService {
|
||||
Future<WeatherSnapshot> loadCurrentWeather() async {
|
||||
try {
|
||||
var permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
}
|
||||
if (permission == LocationPermission.denied ||
|
||||
permission == LocationPermission.deniedForever) {
|
||||
return const WeatherSnapshot(
|
||||
locationName: 'Helymeghat\u00e1roz\u00e1s n\u00e9lk\u00fcl',
|
||||
temperature: null,
|
||||
description: 'id\u0151j\u00e1r\u00e1s nem el\u00e9rhet\u0151',
|
||||
icon: Icons.location_off_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.low,
|
||||
timeLimit: Duration(seconds: 8),
|
||||
),
|
||||
);
|
||||
final locationName = await _locationName(position);
|
||||
final uri = Uri.https('api.open-meteo.com', '/v1/forecast', {
|
||||
'latitude': position.latitude.toStringAsFixed(4),
|
||||
'longitude': position.longitude.toStringAsFixed(4),
|
||||
'current': 'temperature_2m,weather_code',
|
||||
});
|
||||
final response = await http.get(uri).timeout(const Duration(seconds: 8));
|
||||
if (response.statusCode != 200) {
|
||||
throw HttpException('weather ${response.statusCode}');
|
||||
}
|
||||
|
||||
final payload = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final current = payload['current'] as Map<String, dynamic>?;
|
||||
final temperature = (current?['temperature_2m'] as num?)?.toDouble();
|
||||
final code = (current?['weather_code'] as num?)?.toInt();
|
||||
return WeatherSnapshot(
|
||||
locationName: locationName,
|
||||
temperature: temperature,
|
||||
description: weatherDescription(code),
|
||||
icon: weatherIcon(code),
|
||||
);
|
||||
} catch (_) {
|
||||
return const WeatherSnapshot(
|
||||
locationName: 'Offline m\u00f3d',
|
||||
temperature: null,
|
||||
description: 'id\u0151j\u00e1r\u00e1s k\u00e9s\u0151bb friss\u00fcl',
|
||||
icon: Icons.cloud_off_outlined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _locationName(Position position) async {
|
||||
try {
|
||||
final places = await geocoding.Geocoding().placemarkFromCoordinates(
|
||||
position.latitude,
|
||||
position.longitude,
|
||||
);
|
||||
final place = places.firstOrNull;
|
||||
return [
|
||||
place?.locality,
|
||||
if (place?.locality == null) place?.subAdministrativeArea,
|
||||
].whereType<String>().where((part) => part.isNotEmpty).join(', ');
|
||||
} catch (_) {
|
||||
return 'Aktu\u00e1lis hely';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user