Skip to content

Commit a388310

Browse files
Merge pull request #36 from gabrielGavriluta/favourite_websites
add favourite websites card on homepage
2 parents 9a36f8f + d59f9fe commit a388310

File tree

5 files changed

+175
-13
lines changed

5 files changed

+175
-13
lines changed

lib/pages/home/home_page.dart

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@ import 'package:acs_upb_mobile/authentication/service/auth_provider.dart';
33
import 'package:acs_upb_mobile/authentication/view/edit_profile_page.dart';
44
import 'package:acs_upb_mobile/generated/l10n.dart';
55
import 'package:acs_upb_mobile/navigation/routes.dart';
6+
import 'package:acs_upb_mobile/pages/portal/model/website.dart';
7+
import 'package:acs_upb_mobile/pages/portal/service/website_provider.dart';
8+
import 'package:acs_upb_mobile/resources/locale_provider.dart';
9+
import 'package:acs_upb_mobile/resources/storage_provider.dart';
610
import 'package:acs_upb_mobile/resources/utils.dart';
11+
import 'package:acs_upb_mobile/resources/utils.dart';
12+
import 'package:acs_upb_mobile/widgets/circle_image.dart';
713
import 'package:acs_upb_mobile/widgets/scaffold.dart';
814
import 'package:flutter/cupertino.dart';
915
import 'package:flutter/material.dart';
1016
import 'package:provider/provider.dart';
1117

1218
class HomePage extends StatelessWidget {
13-
HomePage({Key key}) : super(key: key);
19+
final TabController tabController;
20+
21+
HomePage({this.tabController, Key key}) : super(key: key);
1422

1523
@override
1624
Widget build(BuildContext context) {
@@ -26,6 +34,9 @@ class HomePage extends StatelessWidget {
2634
body: ListView(
2735
children: [
2836
ProfileCard(),
37+
FavouriteWebsitesCard(
38+
onSeeMore: () => tabController?.animateTo(2),
39+
),
2940
],
3041
),
3142
);
@@ -137,3 +148,129 @@ class ProfileCard extends StatelessWidget {
137148
);
138149
}
139150
}
151+
152+
class FavouriteWebsitesCard extends StatelessWidget {
153+
final Function onSeeMore;
154+
155+
FavouriteWebsitesCard({this.onSeeMore});
156+
157+
@override
158+
Widget build(BuildContext context) {
159+
var websitesFuture =
160+
Provider.of<WebsiteProvider>(context).fetchWebsites(null);
161+
return Padding(
162+
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0),
163+
child: FutureBuilder(
164+
future: websitesFuture,
165+
builder: (_, snapshot) {
166+
if (snapshot.connectionState == ConnectionState.done) {
167+
List<Website> websites = snapshot.data;
168+
websites =
169+
websites.where((w) => w.numberOfVisits > 0).take(3).toList();
170+
return Card(
171+
child: Padding(
172+
padding: const EdgeInsets.all(8.0),
173+
child: Column(
174+
children: <Widget>[
175+
Row(
176+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
177+
children: [
178+
Text(
179+
S.of(context).sectionFrequentlyAccessedWebsites,
180+
style: Theme.of(context)
181+
.textTheme
182+
.headline6
183+
.copyWith(fontSize: 18),
184+
),
185+
GestureDetector(
186+
onTap: () => onSeeMore,
187+
child: Row(
188+
children: <Widget>[
189+
Text(
190+
S.of(context).actionShowMore,
191+
style: Theme.of(context)
192+
.accentTextTheme
193+
.subtitle2
194+
.copyWith(
195+
color: Theme.of(context).accentColor),
196+
),
197+
Icon(
198+
Icons.arrow_forward_ios,
199+
color: Theme.of(context).accentColor,
200+
size: Theme.of(context)
201+
.textTheme
202+
.subtitle2
203+
.fontSize,
204+
)
205+
],
206+
),
207+
),
208+
],
209+
),
210+
SizedBox(height: 12),
211+
if (websites.isEmpty)
212+
noneYet(context)
213+
else
214+
Row(
215+
crossAxisAlignment: CrossAxisAlignment.center,
216+
mainAxisAlignment: MainAxisAlignment.start,
217+
children: websites
218+
.take(3)
219+
.map((website) => Expanded(
220+
child: Padding(
221+
padding: const EdgeInsets.all(8.0),
222+
child:
223+
FutureBuilder<ImageProvider<dynamic>>(
224+
future: Provider.of<StorageProvider>(
225+
context,
226+
listen: false)
227+
.imageFromPath(website.iconPath),
228+
builder: (context, snapshot) {
229+
ImageProvider<dynamic> image = AssetImage(
230+
'assets/icons/websites/globe.png');
231+
if (snapshot.hasData) {
232+
image = snapshot.data;
233+
}
234+
return CircleImage(
235+
label: website.label,
236+
onTap: () {
237+
Provider.of<WebsiteProvider>(
238+
context,
239+
listen: false)
240+
.incrementNumberOfVisits(
241+
website);
242+
Utils.launchURL(website.link,
243+
context: context);
244+
},
245+
image: image,
246+
tooltip: website.infoByLocale[
247+
LocaleProvider.localeString],
248+
);
249+
},
250+
),
251+
),
252+
))
253+
.toList(),
254+
),
255+
],
256+
),
257+
),
258+
);
259+
}
260+
return Center(
261+
child: CircularProgressIndicator(),
262+
);
263+
}),
264+
);
265+
}
266+
267+
Widget noneYet(BuildContext context) => Container(
268+
height: 100,
269+
child: Center(
270+
child: Text(
271+
S.of(context).warningNoneYet,
272+
style: TextStyle(color: Theme.of(context).disabledColor),
273+
),
274+
),
275+
);
276+
}

lib/pages/portal/service/website_provider.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,14 @@ class WebsiteProvider with ChangeNotifier {
132132
/// number of times the user accessed website with ID `websiteIds[i]`.
133133
void incrementNumberOfVisits(Website website) async {
134134
website.numberOfVisits++;
135-
print(website.numberOfVisits);
136135
List<String> websiteIds =
137136
PrefService.sharedPreferences.getStringList('websiteIds') ?? [];
138137
List<String> websiteVisits =
139138
PrefService.sharedPreferences.getStringList('websiteVisits') ?? [];
140139

141140
if (websiteIds.contains(website.id)) {
142141
int index = websiteIds.indexOf(website.id);
143-
websiteVisits.insert(index, websiteVisits.toString());
142+
websiteVisits.insert(index, website.numberOfVisits.toString());
144143
} else {
145144
websiteIds.add(website.id);
146145
websiteVisits.add(website.numberOfVisits.toString());

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ description: A mobile application for students at ACS UPB.
1111
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
1212
# Read more about iOS versioning at
1313
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14-
version: 0.7.5+3
14+
version: 0.7.6+1
1515

1616
environment:
1717
sdk: ">=2.6.0 <3.0.0"

test/authentication_test.dart

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,11 @@ void main() {
7474

7575
group('Login', () {
7676
testWidgets('Anonymous login', (WidgetTester tester) async {
77-
await tester.pumpWidget(ChangeNotifierProvider<AuthProvider>(
78-
create: (_) => mockAuthProvider, child: MyApp()));
77+
await tester.pumpWidget(MultiProvider(providers: [
78+
ChangeNotifierProvider<AuthProvider>(create: (_) => mockAuthProvider),
79+
ChangeNotifierProvider<WebsiteProvider>(
80+
create: (_) => mockWebsiteProvider)
81+
], child: MyApp()));
7982
await tester.pumpAndSettle();
8083

8184
await tester.runAsync(() async {
@@ -98,8 +101,11 @@ void main() {
98101
});
99102

100103
testWidgets('Credential login', (WidgetTester tester) async {
101-
await tester.pumpWidget(ChangeNotifierProvider<AuthProvider>(
102-
create: (_) => mockAuthProvider, child: MyApp()));
104+
await tester.pumpWidget(MultiProvider(providers: [
105+
ChangeNotifierProvider<AuthProvider>(create: (_) => mockAuthProvider),
106+
ChangeNotifierProvider<WebsiteProvider>(
107+
create: (_) => mockWebsiteProvider)
108+
], child: MyApp()));
103109
await tester.pumpAndSettle();
104110

105111
await tester.runAsync(() async {
@@ -325,7 +331,9 @@ void main() {
325331
await tester.pumpWidget(MultiProvider(providers: [
326332
ChangeNotifierProvider<AuthProvider>(create: (_) => mockAuthProvider),
327333
ChangeNotifierProvider<FilterProvider>(
328-
create: (_) => mockFilterProvider)
334+
create: (_) => mockFilterProvider),
335+
ChangeNotifierProvider<WebsiteProvider>(
336+
create: (_) => mockWebsiteProvider)
329337
], child: MyApp(navigationObservers: [mockObserver])));
330338
await tester.pumpAndSettle();
331339

test/settings_test.dart

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:acs_upb_mobile/authentication/service/auth_provider.dart';
22
import 'package:acs_upb_mobile/main.dart';
3+
import 'package:acs_upb_mobile/pages/portal/service/website_provider.dart';
34
import 'package:flutter/material.dart';
45
import 'package:flutter_test/flutter_test.dart';
56
import 'package:mockito/mockito.dart';
@@ -9,8 +10,11 @@ import 'package:shared_preferences/shared_preferences.dart';
910

1011
class MockAuthProvider extends Mock implements AuthProvider {}
1112

13+
class MockWebsiteProvider extends Mock implements WebsiteProvider {}
14+
1215
void main() {
1316
AuthProvider mockAuthProvider;
17+
WebsiteProvider mockWebsiteProvider;
1418

1519
group('Settings', () {
1620
setUpAll(() async {
@@ -30,11 +34,22 @@ void main() {
3034
.thenAnswer((realInvocation) => Future.value(true));
3135
when(mockAuthProvider.currentUser).thenAnswer((_) => Future.value(null));
3236
when(mockAuthProvider.isAnonymous).thenReturn(true);
37+
38+
mockWebsiteProvider = MockWebsiteProvider();
39+
// ignore: invalid_use_of_protected_member
40+
when(mockWebsiteProvider.hasListeners).thenReturn(false);
41+
when(mockWebsiteProvider.deleteWebsite(any, context: anyNamed('context')))
42+
.thenAnswer((realInvocation) => Future.value(true));
43+
when(mockWebsiteProvider.fetchWebsites(any))
44+
.thenAnswer((_) => Future.value([]));
3345
});
3446

3547
testWidgets('Dark Mode', (WidgetTester tester) async {
36-
await tester.pumpWidget(ChangeNotifierProvider<AuthProvider>(
37-
create: (_) => mockAuthProvider, child: MyApp()));
48+
await tester.pumpWidget(MultiProvider(providers: [
49+
ChangeNotifierProvider<AuthProvider>(create: (_) => mockAuthProvider),
50+
ChangeNotifierProvider<WebsiteProvider>(
51+
create: (_) => mockWebsiteProvider)
52+
], child: MyApp()));
3853
await tester.pumpAndSettle();
3954

4055
MaterialApp app = find.byType(MaterialApp).evaluate().first.widget;
@@ -60,8 +75,11 @@ void main() {
6075
});
6176

6277
testWidgets('Language', (WidgetTester tester) async {
63-
await tester.pumpWidget(ChangeNotifierProvider<AuthProvider>(
64-
create: (_) => mockAuthProvider, child: MyApp()));
78+
await tester.pumpWidget(MultiProvider(providers: [
79+
ChangeNotifierProvider<AuthProvider>(create: (_) => mockAuthProvider),
80+
ChangeNotifierProvider<WebsiteProvider>(
81+
create: (_) => mockWebsiteProvider)
82+
], child: MyApp()));
6583
await tester.pumpAndSettle();
6684

6785
// Open settings

0 commit comments

Comments
 (0)