Skip to content

Commit d2e1917

Browse files
authored
Merge pull request #10 from Anish-Chanda/feat/app-plant-crud
Feat/app plant crud
2 parents c56f07a + 8fe1fe5 commit d2e1917

31 files changed

Lines changed: 3140 additions & 113 deletions

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ test:
1818
@echo "Running Flutter tests..."
1919
cd $(FRONTEND_DIR) && flutter test
2020

21+
install:
22+
@echo "Installing dependencies..."
23+
cd $(FRONTEND_DIR) && flutter pub get
24+
cd $(BACKEND_DIR) && go mod tidy
25+
2126
clean: flutter-clean go-clean
2227

2328
# Flutter (Frontend)
1.39 MB
Loading

app/lib/constants.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const List<String> months = [
2+
'January', 'February', 'March', 'April', 'May', 'June',
3+
'July', 'August', 'September', 'October', 'November', 'December'
4+
];
5+
6+
const List<String> weekdays = [
7+
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
8+
];

app/lib/main.dart

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
import 'package:ferna/providers/auth_provider.dart';
2-
import 'package:ferna/screens/login_screen.dart';
2+
import 'package:ferna/providers/plant_provider.dart';
3+
import 'package:ferna/router.dart';
34
import 'package:ferna/theme/theme_data.dart';
45
import 'package:flutter/material.dart';
56
import 'package:provider/provider.dart';
7+
import 'package:intl/date_symbol_data_local.dart';
68

79
void main() async {
810
WidgetsFlutterBinding.ensureInitialized();
911

10-
// Initialzie auth provider
12+
// Initialize date formatting for table_calendar
13+
await initializeDateFormatting();
14+
15+
// Initialize auth provider
1116
final authProvider = await AuthProvider.initialize();
1217

1318
runApp(
1419
MultiProvider(
1520
providers: [
1621
ChangeNotifierProvider<AuthProvider>.value(value: authProvider),
22+
ChangeNotifierProvider<PlantProvider>(create: (_) => PlantProvider()),
1723
],
18-
child: FernaApp(),
24+
child: const FernaApp(),
1925
),
2026
);
2127
}
@@ -31,18 +37,7 @@ class FernaApp extends StatelessWidget {
3137
theme: FernaTheme.light,
3238
darkTheme: FernaTheme.dark,
3339
themeMode: ThemeMode.system,
34-
// TODO: Replace with router
35-
home: const LoginScreen(),
36-
routes: {'/home': (_) => const HomeScreen()},
40+
home: const AppNavigator(),
3741
);
3842
}
3943
}
40-
41-
class HomeScreen extends StatelessWidget {
42-
const HomeScreen({super.key});
43-
44-
@override
45-
Widget build(BuildContext context) {
46-
return const Placeholder(child: Text(":)"));
47-
}
48-
}

app/lib/models/plant.dart

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
class Plant {
2+
final int id;
3+
final int userId;
4+
final int speciesId;
5+
final String? nickname;
6+
final String? imageUrl;
7+
final int wateringFrequencyDays;
8+
final DateTime? lastWateredAt;
9+
final String? note;
10+
final DateTime createdAt;
11+
final DateTime updatedAt;
12+
13+
Plant({
14+
required this.id,
15+
required this.userId,
16+
required this.speciesId,
17+
this.nickname,
18+
this.imageUrl,
19+
required this.wateringFrequencyDays,
20+
this.lastWateredAt,
21+
this.note,
22+
required this.createdAt,
23+
required this.updatedAt,
24+
});
25+
26+
factory Plant.fromJson(Map<String, dynamic> json) {
27+
return Plant(
28+
id: json['id'] as int,
29+
userId: json['user_id'] as int,
30+
speciesId: json['species_id'] as int,
31+
nickname: json['nickname'] as String?,
32+
imageUrl: json['image_url'] as String?,
33+
wateringFrequencyDays: json['watering_frequency_days'] as int,
34+
lastWateredAt: json['last_watered_at'] != null
35+
? DateTime.parse(json['last_watered_at'] as String)
36+
: null,
37+
note: json['note'] as String?,
38+
createdAt: DateTime.parse(json['created_at'] as String),
39+
updatedAt: DateTime.parse(json['updated_at'] as String),
40+
);
41+
}
42+
43+
Map<String, dynamic> toJson() {
44+
return {
45+
'id': id,
46+
'user_id': userId,
47+
'species_id': speciesId,
48+
'nickname': nickname,
49+
'image_url': imageUrl,
50+
'watering_frequency_days': wateringFrequencyDays,
51+
'last_watered_at': lastWateredAt?.toIso8601String(),
52+
'note': note,
53+
'created_at': createdAt.toIso8601String(),
54+
'updated_at': updatedAt.toIso8601String(),
55+
};
56+
}
57+
58+
Plant copyWith({
59+
int? id,
60+
int? userId,
61+
int? speciesId,
62+
String? nickname,
63+
String? imageUrl,
64+
int? wateringFrequencyDays,
65+
DateTime? lastWateredAt,
66+
String? note,
67+
DateTime? createdAt,
68+
DateTime? updatedAt,
69+
}) {
70+
return Plant(
71+
id: id ?? this.id,
72+
userId: userId ?? this.userId,
73+
speciesId: speciesId ?? this.speciesId,
74+
nickname: nickname ?? this.nickname,
75+
imageUrl: imageUrl ?? this.imageUrl,
76+
wateringFrequencyDays: wateringFrequencyDays ?? this.wateringFrequencyDays,
77+
lastWateredAt: lastWateredAt ?? this.lastWateredAt,
78+
note: note ?? this.note,
79+
createdAt: createdAt ?? this.createdAt,
80+
updatedAt: updatedAt ?? this.updatedAt,
81+
);
82+
}
83+
84+
@override
85+
String toString() {
86+
return 'Plant(id: $id, userId: $userId, speciesId: $speciesId, nickname: $nickname, imageUrl: $imageUrl, wateringFrequencyDays: $wateringFrequencyDays, lastWateredAt: $lastWateredAt, note: $note, createdAt: $createdAt, updatedAt: $updatedAt)';
87+
}
88+
89+
@override
90+
bool operator ==(Object other) {
91+
if (identical(this, other)) return true;
92+
93+
return other is Plant &&
94+
other.id == id &&
95+
other.userId == userId &&
96+
other.speciesId == speciesId &&
97+
other.nickname == nickname &&
98+
other.imageUrl == imageUrl &&
99+
other.wateringFrequencyDays == wateringFrequencyDays &&
100+
other.lastWateredAt == lastWateredAt &&
101+
other.note == note &&
102+
other.createdAt == createdAt &&
103+
other.updatedAt == updatedAt;
104+
}
105+
106+
@override
107+
int get hashCode {
108+
return id.hashCode ^
109+
userId.hashCode ^
110+
speciesId.hashCode ^
111+
nickname.hashCode ^
112+
imageUrl.hashCode ^
113+
wateringFrequencyDays.hashCode ^
114+
lastWateredAt.hashCode ^
115+
note.hashCode ^
116+
createdAt.hashCode ^
117+
updatedAt.hashCode;
118+
}
119+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import '../models/plant.dart';
2+
3+
class PlantDisplayModel {
4+
final Plant plant;
5+
final String? speciesCommonName;
6+
final String? location;
7+
8+
PlantDisplayModel({
9+
required this.plant,
10+
this.speciesCommonName,
11+
this.location,
12+
});
13+
14+
String get displayName => plant.nickname ?? speciesCommonName ?? 'Unknown Plant';
15+
16+
String get locationText => location ?? 'No location';
17+
18+
bool get hasCustomNickname => plant.nickname != null && plant.nickname!.isNotEmpty;
19+
20+
int get daysSinceWatered {
21+
if (plant.lastWateredAt == null) return 0;
22+
return DateTime.now().difference(plant.lastWateredAt!).inDays;
23+
}
24+
25+
int get daysOverdue => daysSinceWatered - plant.wateringFrequencyDays;
26+
27+
bool get needsWatering => daysOverdue > 0;
28+
29+
DateTime? get nextWateringDate {
30+
if (plant.lastWateredAt == null) return null;
31+
return plant.lastWateredAt!.add(Duration(days: plant.wateringFrequencyDays));
32+
}
33+
}

app/lib/models/species.dart

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
class Species {
2+
final int id;
3+
final String commonName;
4+
final String? scientificName;
5+
final int defaultWateringFrequency;
6+
final DateTime createdAt;
7+
final DateTime updatedAt;
8+
9+
Species({
10+
required this.id,
11+
required this.commonName,
12+
this.scientificName,
13+
required this.defaultWateringFrequency,
14+
required this.createdAt,
15+
required this.updatedAt,
16+
});
17+
18+
factory Species.fromJson(Map<String, dynamic> json) {
19+
return Species(
20+
id: json['id'] as int,
21+
commonName: json['common_name'] as String,
22+
scientificName: json['scientific_name'] as String?,
23+
defaultWateringFrequency: json['default_watering_frequency_days'] as int,
24+
createdAt: DateTime.parse(json['created_at'] as String),
25+
updatedAt: DateTime.parse(json['updated_at'] as String),
26+
);
27+
}
28+
29+
Map<String, dynamic> toJson() {
30+
return {
31+
'id': id,
32+
'common_name': commonName,
33+
'scientific_name': scientificName,
34+
'default_watering_frequency_days': defaultWateringFrequency,
35+
'created_at': createdAt.toIso8601String(),
36+
'updated_at': updatedAt.toIso8601String(),
37+
};
38+
}
39+
40+
Species copyWith({
41+
int? id,
42+
String? commonName,
43+
String? scientificName,
44+
int? defaultWateringFrequency,
45+
DateTime? createdAt,
46+
DateTime? updatedAt,
47+
}) {
48+
return Species(
49+
id: id ?? this.id,
50+
commonName: commonName ?? this.commonName,
51+
scientificName: scientificName ?? this.scientificName,
52+
defaultWateringFrequency: defaultWateringFrequency ?? this.defaultWateringFrequency,
53+
createdAt: createdAt ?? this.createdAt,
54+
updatedAt: updatedAt ?? this.updatedAt,
55+
);
56+
}
57+
58+
@override
59+
String toString() {
60+
return 'Species(id: $id, commonName: $commonName, scientificName: $scientificName, defaultWateringFrequency: $defaultWateringFrequency, createdAt: $createdAt, updatedAt: $updatedAt)';
61+
}
62+
63+
@override
64+
bool operator ==(Object other) {
65+
if (identical(this, other)) return true;
66+
67+
return other is Species &&
68+
other.id == id &&
69+
other.commonName == commonName &&
70+
other.scientificName == scientificName &&
71+
other.defaultWateringFrequency == defaultWateringFrequency &&
72+
other.createdAt == createdAt &&
73+
other.updatedAt == updatedAt;
74+
}
75+
76+
@override
77+
int get hashCode {
78+
return id.hashCode ^
79+
commonName.hashCode ^
80+
scientificName.hashCode ^
81+
defaultWateringFrequency.hashCode ^
82+
createdAt.hashCode ^
83+
updatedAt.hashCode;
84+
}
85+
}

0 commit comments

Comments
 (0)