Skip to content

Commit 6049bed

Browse files
committed
feat: remove mock data and get data tags and categories from supabase
1 parent cd35071 commit 6049bed

19 files changed

Lines changed: 902 additions & 589 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import 'package:flutter/material.dart';
2+
3+
extension AdaptiveColorExtension on Color {
4+
Color toAdaptiveColor(BuildContext context) {
5+
final isDark = Theme.of(context).brightness == Brightness.dark;
6+
if (!isDark) return this;
7+
8+
return Color.lerp(this, Colors.white, 0.4) ?? this;
9+
}
10+
}
11+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'package:flutter/material.dart';
2+
3+
class CategoryModel {
4+
final int id;
5+
final String name;
6+
final String colorCode;
7+
final String profileId;
8+
9+
const CategoryModel({
10+
required this.id,
11+
required this.name,
12+
required this.colorCode,
13+
required this.profileId,
14+
});
15+
16+
Color get color => _parseHexColor(colorCode);
17+
18+
factory CategoryModel.fromJson(Map<String, dynamic> json) {
19+
return CategoryModel(
20+
id: (json['id'] as num?)?.toInt() ?? 0,
21+
name: json['name']?.toString() ?? '',
22+
colorCode: json['color_code']?.toString() ?? '#5A8DF3',
23+
profileId: json['profile_id']?.toString() ?? '',
24+
);
25+
}
26+
27+
Map<String, dynamic> toJson() {
28+
return {
29+
'id': id,
30+
'name': name,
31+
'color_code': colorCode,
32+
'profile_id': profileId,
33+
};
34+
}
35+
36+
static Color _parseHexColor(String value) {
37+
var hex = value.trim().replaceFirst('#', '');
38+
if (hex.length == 6) {
39+
hex = 'FF$hex';
40+
}
41+
if (hex.length != 8) {
42+
return const Color(0xFF5A8DF3);
43+
}
44+
45+
final parsed = int.tryParse(hex, radix: 16);
46+
if (parsed == null) {
47+
return const Color(0xFF5A8DF3);
48+
}
49+
return Color(parsed);
50+
}
51+
}
52+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import 'package:supabase_flutter/supabase_flutter.dart';
2+
3+
import '../model/category_model.dart';
4+
5+
class CategoryRepository {
6+
final SupabaseClient _client;
7+
8+
CategoryRepository({SupabaseClient? client})
9+
: _client = client ?? Supabase.instance.client;
10+
11+
Future<List<CategoryModel>> fetchCategories() async {
12+
final user = _client.auth.currentUser;
13+
if (user == null) return [];
14+
15+
final rows = await _client
16+
.from('category')
17+
.select('id, name, color_code, profile_id')
18+
.eq('profile_id', user.id)
19+
.order('name');
20+
21+
return (rows as List<dynamic>)
22+
.map((e) => CategoryModel.fromJson(Map<String, dynamic>.from(e)))
23+
.toList();
24+
}
25+
}
26+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:task_management_app/core/utils/adaptive_color_extension.dart';
3+
4+
import '../../model/category_model.dart';
5+
6+
class CategoryChoiceChips extends StatelessWidget {
7+
const CategoryChoiceChips({
8+
super.key,
9+
required this.categories,
10+
required this.selectedCategoryId,
11+
required this.onSelected,
12+
});
13+
14+
final List<CategoryModel> categories;
15+
final int? selectedCategoryId;
16+
final ValueChanged<CategoryModel> onSelected;
17+
18+
@override
19+
Widget build(BuildContext context) {
20+
return SizedBox(
21+
height: 40,
22+
child: ListView.builder(
23+
scrollDirection: Axis.horizontal,
24+
itemCount: categories.length,
25+
itemBuilder: (context, index) {
26+
final category = categories[index];
27+
final adaptiveColor = category.color.toAdaptiveColor(context);
28+
final isSelected = category.id == selectedCategoryId;
29+
return Padding(
30+
padding: const EdgeInsets.only(right: 10),
31+
child: ChoiceChip(
32+
label: Text(category.name),
33+
selected: isSelected,
34+
onSelected: (selected) {
35+
if (selected) onSelected(category);
36+
},
37+
backgroundColor: adaptiveColor.withValues(alpha: 0.15),
38+
selectedColor: adaptiveColor,
39+
labelStyle: TextStyle(
40+
color: isSelected ? Colors.white : adaptiveColor,
41+
fontSize: 14,
42+
),
43+
shape: RoundedRectangleBorder(
44+
borderRadius: BorderRadius.circular(10),
45+
side: BorderSide(
46+
color: adaptiveColor.withValues(alpha: 0.4),
47+
width: 1,
48+
),
49+
),
50+
showCheckmark: false,
51+
),
52+
);
53+
},
54+
),
55+
);
56+
}
57+
}
58+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import 'package:flutter/foundation.dart';
2+
3+
import '../model/category_model.dart';
4+
import '../repository/category_repository.dart';
5+
6+
class CategoryViewModel extends ChangeNotifier {
7+
final CategoryRepository _repository;
8+
9+
CategoryViewModel({CategoryRepository? repository})
10+
: _repository = repository ?? CategoryRepository();
11+
12+
final List<CategoryModel> _categories = [];
13+
14+
List<CategoryModel> get categories => List.unmodifiable(_categories);
15+
16+
bool _isLoading = false;
17+
bool get isLoading => _isLoading;
18+
19+
String? _error;
20+
String? get error => _error;
21+
22+
Future<void> loadCategories() async {
23+
_isLoading = true;
24+
_error = null;
25+
notifyListeners();
26+
27+
try {
28+
final data = await _repository.fetchCategories();
29+
_categories
30+
..clear()
31+
..addAll(data);
32+
} catch (e) {
33+
_error = e.toString();
34+
_categories.clear();
35+
} finally {
36+
_isLoading = false;
37+
notifyListeners();
38+
}
39+
}
40+
}
41+

src/lib/features/main/view/screens/main_screen.dart

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import 'package:flutter/material.dart';
2+
// import '../../../tasks/view/screens/home_screen.dart';
3+
import 'package:provider/provider.dart';
4+
import 'package:task_management_app/features/chatbot/view/chatbot_view.dart';
25
import 'package:task_management_app/features/statistics/viewmodel/statistics_viewmodel.dart';
36
import 'package:task_management_app/features/tasks/view/screens/home_screen.dart';
4-
import 'package:task_management_app/features/chatbot/view/chatbot_view.dart';
5-
import 'settings_screen.dart';
67
import 'package:task_management_app/features/user/viewmodel/user_profile_viewmodel.dart';
8+
9+
import '../../../category/viewmodel/category_viewmodel.dart';
710
import '../../../note/view/focus_screen.dart';
811
import '../../../note/viewmodel/focus_viewmodel.dart';
912
import '../../../statistics/view/screens/statistics_screen.dart';
10-
// import '../../../tasks/view/screens/home_screen.dart';
11-
import 'package:provider/provider.dart';
12-
13+
import '../../../tag/viewmodel/tag_viewmodel.dart';
1314
import '../../../user/view/user_profile_view.dart';
15+
import 'settings_screen.dart';
1416

1517
class MainScreen extends StatefulWidget {
1618
const MainScreen({super.key});
@@ -30,34 +32,50 @@ class _MainScreenState extends State<MainScreen> {
3032
child: const FocusScreen(),
3133
),
3234
ChangeNotifierProvider(
33-
create: (_) => StatisticsViewmodel(),
34-
child: const StatisticsScreen(),
35+
create: (_) => StatisticsViewmodel(),
36+
child: const StatisticsScreen(),
3537
),
3638
ChangeNotifierProvider(
37-
create: (_) => UserProfileViewModel(useMockData: true)..loadProfile(),
38-
child: const UserProfileView(),
39+
create: (_) => UserProfileViewModel(useMockData: true)..loadProfile(),
40+
child: const UserProfileView(),
3941
),
4042
const SettingsScreen(),
4143
];
4244

45+
@override
46+
void initState() {
47+
super.initState();
48+
49+
WidgetsBinding.instance.addPostFrameCallback((_) {
50+
context.read<CategoryViewModel>().loadCategories();
51+
context.read<TagViewModel>().loadTags();
52+
});
53+
}
54+
4355
@override
4456
Widget build(BuildContext context) {
4557
final isDark = Theme.of(context).brightness == Brightness.dark;
4658

4759
return Scaffold(
4860
extendBody: true,
49-
body: IndexedStack(
50-
index: _currentIndex,
51-
children: _screens,
52-
),
61+
body: IndexedStack(index: _currentIndex, children: _screens),
5362
bottomNavigationBar: Container(
5463
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 15),
5564
decoration: BoxDecoration(
5665
color: isDark
5766
? const Color(0xFF1A2945)
5867
: Theme.of(context).colorScheme.surface,
59-
borderRadius: const BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30)),
60-
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 20, offset: const Offset(0, -5))],
68+
borderRadius: const BorderRadius.only(
69+
topLeft: Radius.circular(30),
70+
topRight: Radius.circular(30),
71+
),
72+
boxShadow: [
73+
BoxShadow(
74+
color: Colors.black.withValues(alpha: 0.05),
75+
blurRadius: 20,
76+
offset: const Offset(0, -5),
77+
),
78+
],
6179
),
6280
child: SafeArea(
6381
child: Row(
@@ -75,20 +93,28 @@ class _MainScreenState extends State<MainScreen> {
7593
);
7694
}
7795

78-
Widget _buildNavItem(BuildContext context, IconData icon, String label, int index) {
96+
Widget _buildNavItem(
97+
BuildContext context,
98+
IconData icon,
99+
String label,
100+
int index,
101+
) {
79102
bool isSelected = _currentIndex == index;
80103

81104
return GestureDetector(
82105
onTap: () => setState(() => _currentIndex = index),
83106
child: AnimatedContainer(
84107
duration: const Duration(milliseconds: 300),
85108
curve: Curves.easeInOut,
86-
padding: EdgeInsets.symmetric(horizontal: isSelected ? 15 : 10, vertical: 10),
109+
padding: EdgeInsets.symmetric(
110+
horizontal: isSelected ? 15 : 10,
111+
vertical: 10,
112+
),
87113
decoration: BoxDecoration(
88114
color: isSelected
89115
? (Theme.of(context).brightness == Brightness.dark
90-
? const Color(0xFF23395D)
91-
: const Color(0xFFE8F0FE))
116+
? const Color(0xFF23395D)
117+
: const Color(0xFFE8F0FE))
92118
: Colors.transparent,
93119
borderRadius: BorderRadius.circular(15),
94120
),
@@ -112,10 +138,10 @@ class _MainScreenState extends State<MainScreen> {
112138
? Theme.of(context).colorScheme.primary
113139
: Theme.of(context).colorScheme.onSurfaceVariant,
114140
),
115-
)
141+
),
116142
],
117143
),
118144
),
119145
);
120146
}
121-
}
147+
}

src/lib/features/statistics/view/screens/statistics_screen.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter/material.dart';
2+
import 'package:task_management_app/features/category/viewmodel/category_viewmodel.dart';
23
import 'package:supabase_flutter/supabase_flutter.dart';
34
import 'package:provider/provider.dart';
45
import '../../viewmodel/statistics_viewmodel.dart';
@@ -23,6 +24,10 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
2324
final userId = Supabase.instance.client.auth.currentUser?.id;
2425
if (userId != null) {
2526
context.read<StatisticsViewmodel>().getStatisticsData(userId);
27+
final categoryViewModel = context.read<CategoryViewModel>();
28+
if (categoryViewModel.categories.isEmpty) {
29+
categoryViewModel.loadCategories();
30+
}
2631
}
2732
});
2833
}

0 commit comments

Comments
 (0)