diff --git a/android/app/build.gradle b/android/app/build.gradle index 6a6d7bd..1040073 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -43,8 +43,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.ristek.ulaskelas" - minSdkVersion 19 - targetSdkVersion 30 + minSdkVersion 26 + targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName multiDexEnabled true diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bf485dc..2f43e27 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ android:icon="@mipmap/ic_launcher"> { @override void initState() { super.initState(); + MixpanelService.track('open_app'); splashTime(); } diff --git a/lib/authentication_page.dart b/lib/authentication_page.dart index 36d49e7..c496543 100644 --- a/lib/authentication_page.dart +++ b/lib/authentication_page.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:ristek_material_component/ristek_material_component.dart'; import 'package:states_rebuilder/states_rebuilder.dart'; import 'package:ulaskelas/features/matkul/bookmarks/domain/entities/query_bookmark.dart'; +import 'package:ulaskelas/services/_services.dart'; import 'core/bases/states/_states.dart'; import 'core/constants/_constants.dart'; @@ -69,6 +70,7 @@ Aplikasi ulasan mata kuliah Fasilkom UI.\nMasuk dan buat ulasanmu sekarang!''', } Future _ssoLogin() async { + MixpanelService.track('login'); if (authRM.state.isLoading) { return; } @@ -81,6 +83,7 @@ Aplikasi ulasan mata kuliah Fasilkom UI.\nMasuk dan buat ulasanmu sekarang!''', await Future.delayed(const Duration(seconds: 1)); await authRM.setState((s) => s.ssoLogin()); if (authRM.state.isLogin) { + MixpanelService.track('login_success'); await profileRM.state.retrieveData(); await bookmarkRM.state.retrieveData(QueryBookmark()); if (profileRM.state.profile.isBlocked ?? false) { diff --git a/lib/core/bases/states/_states.dart b/lib/core/bases/states/_states.dart index 4de154b..e8e11d4 100644 --- a/lib/core/bases/states/_states.dart +++ b/lib/core/bases/states/_states.dart @@ -33,6 +33,8 @@ import 'package:ulaskelas/onboarding_page.dart'; import 'package:ulaskelas/services/_services.dart'; import 'package:universal_html/html.dart'; +import '../../utils/util.dart'; + part 'auth_state.dart'; part 'cleaner.dart'; part 'global_state.dart'; diff --git a/lib/core/bases/states/navigation_state.dart b/lib/core/bases/states/navigation_state.dart index 6f4e8d3..5d4d12c 100644 --- a/lib/core/bases/states/navigation_state.dart +++ b/lib/core/bases/states/navigation_state.dart @@ -78,6 +78,7 @@ class NavigationServiceState implements Navigation { } Future goToFilterPage() { + MixpanelService.track('open_course_filter'); return nav.push( const FilterPage(), RouteName.mainPage, @@ -88,6 +89,7 @@ class NavigationServiceState implements Navigation { int courseId, String courseCode, ) { + MixpanelService.track('view_course'); return nav.push( DetailMatkulPage( courseId: courseId, @@ -116,7 +118,18 @@ class NavigationServiceState implements Navigation { Future goToAllReviewMatkulPage({ required int courseId, required String courseCode, + required CourseModel course, }) { + MixpanelService.track( + 'view_all_reviews', + params: { + 'course_id': course.code.toString(), + 'course_name': course.name.toString(), + 'review_count': course.reviewCount.toString(), + 'course_rating_avg': + course.ratingAverage.toString(), + }, + ); return nav.push( AllReviewMatkulPage( courseId: courseId, @@ -134,6 +147,7 @@ class NavigationServiceState implements Navigation { } Future goToHomeDaftarMatkul() { + MixpanelService.track('view_this_semester_courses'); return nav.push( const HomeCourseListPage(), RouteName.homeDaftarMatkul, @@ -141,6 +155,7 @@ class NavigationServiceState implements Navigation { } Future goToHomeDaftarUlasan() { + MixpanelService.track('view_all_reviews'); return nav.push( const HomeDaftarUlasanPage(), RouteName.homeDaftarUlasan, @@ -190,6 +205,7 @@ class NavigationServiceState implements Navigation { } Future goToSearchCourseCalculatorPage() { + MixpanelService.track('calculator_add_course'); return nav.push( const SearchCourseCalculator(), RouteName.searchCourseCalculator, @@ -202,6 +218,16 @@ class NavigationServiceState implements Navigation { required double totalScore, required double totalPercentage, }) { + MixpanelService.track( + 'calculator_view_course', + params: { + 'course_id': courseName, + 'final_letter_grade': getFinalGrade( + totalScore, + ), + 'final_grade': totalScore.toString(), + }, + ); return nav.push( CalculatorComponentPage( calculatorId: calculatorId, diff --git a/lib/features/home/presentation/pages/home_page.dart b/lib/features/home/presentation/pages/home_page.dart index 55fd5df..88b6afc 100644 --- a/lib/features/home/presentation/pages/home_page.dart +++ b/lib/features/home/presentation/pages/home_page.dart @@ -219,11 +219,7 @@ class _HomePageState extends BaseStateful { style: FontTheme.poppins14w700black(), ), InkWell( - onTap: () { - nav.goToHomeDaftarUlasan(); - - MixpanelService.track('view_all_reviews'); - }, + onTap: () => nav.goToHomeDaftarUlasan(), child: Text( 'Lihat Semua', style: FontTheme.poppins13w400purple(), diff --git a/lib/features/kalkulator/presentation/pages/kalkulator_page.dart b/lib/features/kalkulator/presentation/pages/kalkulator_page.dart index 682f285..2124a9b 100644 --- a/lib/features/kalkulator/presentation/pages/kalkulator_page.dart +++ b/lib/features/kalkulator/presentation/pages/kalkulator_page.dart @@ -82,11 +82,7 @@ Kamu Belum memiliki kalkulator nilai tersimpan. Silakan tambahkan terlebih dahul width: double.infinity, text: 'Tambah Mata Kuliah', backgroundColor: BaseColors.purpleHearth, - onPressed: () { - nav.goToSearchCourseCalculatorPage(); - // ignore: lines_longer_than_80_chars - MixpanelService.track('calculator_add_course'); - }, + onPressed: () => nav.goToSearchCourseCalculatorPage(), ), ], ), @@ -117,23 +113,12 @@ Kamu Belum memiliki kalkulator nilai tersimpan. Silakan tambahkan terlebih dahul final calculator = calculators[index]; return CardCalculator( model: calculator, - onTap: () { - nav.goToComponentCalculatorPage( - calculatorId: calculator.id!, - courseName: calculator.courseName!, - totalScore: calculator.totalScore!, - totalPercentage: calculator.totalPercentage!, - ); - MixpanelService.track( - 'calculator_view_course', - params: { - 'course_id': calculator.courseName!, - 'final_letter_grade': getFinalGrade( - calculator.totalScore!,), - 'final_grade': calculator.totalScore.toString(), - }, - ); - }, + onTap: () => nav.goToComponentCalculatorPage( + calculatorId: calculator.id!, + courseName: calculator.courseName!, + totalScore: calculator.totalScore!, + totalPercentage: calculator.totalPercentage!, + ), ); }, separatorBuilder: (BuildContext context, int index) => @@ -170,5 +155,4 @@ Kamu Belum memiliki kalkulator nilai tersimpan. Silakan tambahkan terlebih dahul bool scrollCondition() { throw UnimplementedError(); } - } diff --git a/lib/features/kalkulator/presentation/pages/komponen_form_page.dart b/lib/features/kalkulator/presentation/pages/komponen_form_page.dart index 1e592f1..e6719dc 100644 --- a/lib/features/kalkulator/presentation/pages/komponen_form_page.dart +++ b/lib/features/kalkulator/presentation/pages/komponen_form_page.dart @@ -63,7 +63,6 @@ class _ComponentFormPageState extends BaseStateful { text: 'Simpan', onTap: () async { await onSubmitCallBack(context); - MixpanelService.track('calculator_add_course_component'); }, ), ), @@ -91,6 +90,7 @@ class _ComponentFormPageState extends BaseStateful { if (componentFormRM.state.isLoading) { return; } + MixpanelService.track('calculator_add_course_component'); if (componentFormRM.state.formKey.currentState!.validate()) { // progressDialogue(context); await componentFormRM.state.submitForm(widget.calculatorId); diff --git a/lib/features/kalkulator/presentation/pages/komponen_kalkulator_page.dart b/lib/features/kalkulator/presentation/pages/komponen_kalkulator_page.dart index ad1f826..edff3f9 100644 --- a/lib/features/kalkulator/presentation/pages/komponen_kalkulator_page.dart +++ b/lib/features/kalkulator/presentation/pages/komponen_kalkulator_page.dart @@ -183,17 +183,11 @@ class _CalculatorComponentPageState nav.pop(); calculatorRM.setState( (s) => s.deleteCalculator( - QueryCalculator(id: widget.calculatorId), + query: QueryCalculator(id: widget.calculatorId,), + courseName: widget.courseName, + totalScore: widget.totalScore, ), ); - MixpanelService.track( - 'calculator_delete_course_component', - params: { - 'course_id': widget.courseName, - 'final_letter_grade': widget.totalScore.toString(), - 'final_grade': getFinalGrade(widget.totalScore), - }, - ); }, child: Text( 'Hapus Kalkulator Mata Kuliah', diff --git a/lib/features/kalkulator/presentation/states/_states.dart b/lib/features/kalkulator/presentation/states/_states.dart index 731c935..795f199 100644 --- a/lib/features/kalkulator/presentation/states/_states.dart +++ b/lib/features/kalkulator/presentation/states/_states.dart @@ -9,6 +9,9 @@ import 'package:ulaskelas/features/kalkulator/domain/entities/query_calculator.d import 'package:ulaskelas/features/kalkulator/domain/entities/query_component.dart'; import 'package:ulaskelas/features/kalkulator/domain/repositories/_repositories.dart'; +import '../../../../core/utils/util.dart'; +import '../../../../services/_services.dart'; + part 'calculator_state.dart'; part 'component_state.dart'; part 'component_form_state.dart'; diff --git a/lib/features/kalkulator/presentation/states/calculator_state.dart b/lib/features/kalkulator/presentation/states/calculator_state.dart index bbe46ee..06d7234 100644 --- a/lib/features/kalkulator/presentation/states/calculator_state.dart +++ b/lib/features/kalkulator/presentation/states/calculator_state.dart @@ -8,6 +8,7 @@ class CalculatorState { late CalculatorRepository _repo; List? _calculators; + List get calculators => _calculators ?? []; bool hasReachedMax = false; @@ -49,8 +50,20 @@ class CalculatorState { calculatorRM.notify(); } - Future deleteCalculator(QueryCalculator query) async { + Future deleteCalculator({ + required QueryCalculator query, + required String courseName, + required double totalScore, + }) async { final resp = await _repo.deleteCalculator(query); + MixpanelService.track( + 'calculator_delete_course_component', + params: { + 'course_id': courseName, + 'final_letter_grade': totalScore.toString(), + 'final_grade': getFinalGrade(totalScore), + }, + ); await resp.fold((failure) { ErrorMessenger('Kalkulator gagal dihapus').show(ctx!); }, (result) async { diff --git a/lib/features/matkul/bookmarks/presentation/states/_states.dart b/lib/features/matkul/bookmarks/presentation/states/_states.dart index e66b7e0..d59ce46 100644 --- a/lib/features/matkul/bookmarks/presentation/states/_states.dart +++ b/lib/features/matkul/bookmarks/presentation/states/_states.dart @@ -7,4 +7,6 @@ import 'package:ulaskelas/features/matkul/bookmarks/domain/entities/query_bookma import 'package:ulaskelas/features/matkul/bookmarks/domain/repositories/_repositories.dart'; import 'package:ulaskelas/features/matkul/search/data/models/_models.dart'; +import '../../../../../services/_services.dart'; + part 'bookmark_state.dart'; diff --git a/lib/features/matkul/bookmarks/presentation/states/bookmark_state.dart b/lib/features/matkul/bookmarks/presentation/states/bookmark_state.dart index fed6de8..75bd760 100644 --- a/lib/features/matkul/bookmarks/presentation/states/bookmark_state.dart +++ b/lib/features/matkul/bookmarks/presentation/states/bookmark_state.dart @@ -44,6 +44,7 @@ class BookmarkState { /// tap to toggle Bookmark Future toggleBookmark(BookmarkModel bookmark) async { + MixpanelService.track('bookmark_course'); final resp = await _repo.getAllBookmark(QueryBookmark()); resp.fold((failure) { throw failure; diff --git a/lib/features/matkul/detail/presentation/pages/detail_matkul_page.dart b/lib/features/matkul/detail/presentation/pages/detail_matkul_page.dart index 8ddb555..2fb0b2a 100644 --- a/lib/features/matkul/detail/presentation/pages/detail_matkul_page.dart +++ b/lib/features/matkul/detail/presentation/pages/detail_matkul_page.dart @@ -125,22 +125,11 @@ class _DetailMatkulPageState extends BaseStateful { const HeightSpace(16), if (course.reviewCount! > 3) InkWell( - onTap: () { - nav.goToAllReviewMatkulPage( - courseId: widget.courseId, - courseCode: widget.courseCode, - ); - MixpanelService.track( - 'view_all_reviews', - params: { - 'course_id': course.code.toString(), - 'course_name': course.name.toString(), - 'review_count': course.reviewCount.toString(), - 'course_rating_avg': - course.ratingAverage.toString(), - }, - ); - }, + onTap: () => nav.goToAllReviewMatkulPage( + courseId: widget.courseId, + courseCode: widget.courseCode, + course: course, + ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -237,18 +226,7 @@ class _DetailMatkulPageState extends BaseStateful { final review = data.reviews[data.reviews.length - i - 1]; return ReviewCard( review: review, - onLiked: () { - reviewCourseRM.state.like(review); - MixpanelService.track( - 'like_review', - params: { - 'course_id': review.courseCode.toString(), - 'course_name': review.courseName.toString(), - 'review_count': review.likesCount.toString(), - 'course_rating_avg': review.ratingAverage.toString(), - }, - ); - }, + onLiked: () => reviewCourseRM.state.like(review), ); }, ); diff --git a/lib/features/matkul/form/presentation/pages/review_matkul_form_page.dart b/lib/features/matkul/form/presentation/pages/review_matkul_form_page.dart index dabe3c8..accdc72 100644 --- a/lib/features/matkul/form/presentation/pages/review_matkul_form_page.dart +++ b/lib/features/matkul/form/presentation/pages/review_matkul_form_page.dart @@ -130,25 +130,11 @@ class _ReviewMatkulFormPageState extends BaseStateful { reviewFormStateData.ratingFitToStudyBook != null && reviewFormStateData.ratingBeneficial != null && reviewFormStateData.ratingRecommended != null) { - await reviewFormRM.state - .submitForm(widget.course.code!); - await Future.delayed(const Duration(milliseconds: 150)); - MixpanelService.track( - 'write_review', - params: { - 'course_id': widget.course.code.toString(), - 'course_name': widget.course.name.toString(), - 'created_at': DateTime.now().toString(), - 'period_taking': - reviewFormStateData.semester.toString(), - 'year_taking': reviewFormStateData.year.toString(), - 'avg_rating_given': - reviewFormState.getAvgRating().toString(), - 'tags': reviewFormStateData.tagData.toString(), - 'anonymous_review': - reviewFormStateData.isAnonymous.toString(), - }, + await reviewFormRM.state.submitForm( + course: widget.course, ); + await Future.delayed(const Duration(milliseconds: 150)); + reviewFormRM.state.cleanForm(); nav.pop(); await nav.replaceToReviewPendingPage(); diff --git a/lib/features/matkul/form/presentation/states/_states.dart b/lib/features/matkul/form/presentation/states/_states.dart index 8357ac8..464a7f6 100644 --- a/lib/features/matkul/form/presentation/states/_states.dart +++ b/lib/features/matkul/form/presentation/states/_states.dart @@ -16,6 +16,9 @@ import 'package:ulaskelas/features/matkul/form/domain/entities/query_search_tag. import 'package:ulaskelas/features/matkul/form/domain/repositories/_repositories.dart'; import 'package:ulaskelas/features/matkul/search/domain/entities/_entities.dart'; +import '../../../../../services/_services.dart'; +import '../../../search/data/models/_models.dart'; + part 'review_course_form_state.dart'; part 'search_tag_state.dart'; part 'review_course_state.dart'; diff --git a/lib/features/matkul/form/presentation/states/review_course_form_state.dart b/lib/features/matkul/form/presentation/states/review_course_form_state.dart index c1c9df1..06d8421 100644 --- a/lib/features/matkul/form/presentation/states/review_course_form_state.dart +++ b/lib/features/matkul/form/presentation/states/review_course_form_state.dart @@ -18,17 +18,21 @@ class ReviewCourseFormState { final _descController = TextEditingController(); final semesters = ['Semester ganjil', 'Semester genap']; - final years = ['2022', '2021', '2020', '2019', '2018', '2017']; + final years = [ + for (var i = 0; i < 5; i++) (DateTime.now().year - i).toString(), + ]; bool isLoading = false; /// Submitting form data - Future submitForm(String courseCode) async { + Future submitForm({ + required CourseModel course, + }) async { isLoading = true; reviewFormRM.notify(); final result = {}; // TODO(Any): sync with current matkul - result['course_code'] = courseCode; + result['course_code'] = course.code; result['semester'] = _formData.semester! == semesters[0] ? 1 : 2; result['academic_year'] = getPairedYear(result['semester'], _formData.year!); @@ -43,6 +47,19 @@ class ReviewCourseFormState { result['rating_recommended'] = _formData.ratingRecommended; final resp = await _repo.createReview(result); + MixpanelService.track( + 'write_review', + params: { + 'course_id': course.code.toString(), + 'course_name': course.name.toString(), + 'created_at': DateTime.now().toString(), + 'period_taking': _formData.semester.toString(), + 'year_taking': _formData.year.toString(), + 'avg_rating_given': reviewFormRM.state.getAvgRating().toString(), + 'tags': _formData.tagData.toString(), + 'anonymous_review': _formData.isAnonymous.toString(), + }, + ); isLoading = false; reviewFormRM.notify(); resp.fold((failure) { @@ -114,10 +131,11 @@ class ReviewCourseFormState { double getAvgRating() { return ((_formData.ratingBeneficial ?? 0) + - (_formData.ratingFitToCredit ?? 0) + - (_formData.ratingFitToStudyBook ?? 0) + - (_formData.ratingRecommended ?? 0) + - (_formData.ratingUnderstandable ?? 0)) / 5; + (_formData.ratingFitToCredit ?? 0) + + (_formData.ratingFitToStudyBook ?? 0) + + (_formData.ratingRecommended ?? 0) + + (_formData.ratingUnderstandable ?? 0)) / + 5; } /// Cleaning form when success submitting form diff --git a/lib/features/matkul/form/presentation/states/review_course_state.dart b/lib/features/matkul/form/presentation/states/review_course_state.dart index 634ff3a..c8cef0c 100644 --- a/lib/features/matkul/form/presentation/states/review_course_state.dart +++ b/lib/features/matkul/form/presentation/states/review_course_state.dart @@ -111,5 +111,14 @@ class ReviewCourseState { } reviewCourseRM.notify(); }); + MixpanelService.track( + 'like_review', + params: { + 'course_id': review.courseCode.toString(), + 'course_name': review.courseName.toString(), + 'review_count': review.likesCount.toString(), + 'course_rating_avg': review.ratingAverage.toString(), + }, + ); } } diff --git a/lib/features/matkul/search/presentation/pages/search_course_page.dart b/lib/features/matkul/search/presentation/pages/search_course_page.dart index 6bee178..7778a29 100644 --- a/lib/features/matkul/search/presentation/pages/search_course_page.dart +++ b/lib/features/matkul/search/presentation/pages/search_course_page.dart @@ -78,15 +78,8 @@ class _SearchCoursePageState focusNode.unfocus(); searchCourseRM.state.controller.clear(); }, - onFieldSubmitted: (val) { - searchCourseRM.state.addToHistory(val); - MixpanelService.track( - 'search_course', - params: { - 'query': val, - }, - ); - }, + onFieldSubmitted: (val) => + searchCourseRM.state.addToHistory(val), onChange: onQueryChanged, ), ), diff --git a/lib/features/matkul/search/presentation/states/_states.dart b/lib/features/matkul/search/presentation/states/_states.dart index 737f4a4..aa6bd3f 100644 --- a/lib/features/matkul/search/presentation/states/_states.dart +++ b/lib/features/matkul/search/presentation/states/_states.dart @@ -13,5 +13,7 @@ import 'package:ulaskelas/features/matkul/main/domain/repositories/_repositories import 'package:ulaskelas/features/matkul/search/data/models/_models.dart'; import 'package:ulaskelas/features/matkul/search/domain/entities/_entities.dart'; +import '../../../../../services/_services.dart'; + part 'filter_state.dart'; part 'search_course_state.dart'; diff --git a/lib/features/matkul/search/presentation/states/search_course_state.dart b/lib/features/matkul/search/presentation/states/search_course_state.dart index ff5e80d..866924b 100644 --- a/lib/features/matkul/search/presentation/states/search_course_state.dart +++ b/lib/features/matkul/search/presentation/states/search_course_state.dart @@ -161,6 +161,12 @@ class SearchCourseState if (history.length == 11) { _history?.removeLast(); } + MixpanelService.track( + 'search_course', + params: { + 'query': query, + }, + ); // TODO(pawpaw): save to local storage } diff --git a/lib/features/matkul/search/presentation/widgets/search_list_view.dart b/lib/features/matkul/search/presentation/widgets/search_list_view.dart index e4e0a91..059609e 100644 --- a/lib/features/matkul/search/presentation/widgets/search_list_view.dart +++ b/lib/features/matkul/search/presentation/widgets/search_list_view.dart @@ -45,8 +45,6 @@ class SearchListView extends StatelessWidget { if (filterRM.state.hasFilter) { await refreshIndicatorKey.currentState?.show(); } - - MixpanelService.track('open_course_filter'); }, ); }, diff --git a/lib/features/profile/presentation/pages/profile_page.dart b/lib/features/profile/presentation/pages/profile_page.dart index ea4bb4d..0a78a49 100644 --- a/lib/features/profile/presentation/pages/profile_page.dart +++ b/lib/features/profile/presentation/pages/profile_page.dart @@ -91,10 +91,7 @@ class _ProfilePageState extends BaseStateful { const HeightSpace(24), Center( child: InkWell( - onTap: () { - nav.goToHomeDaftarUlasan(); - MixpanelService.track('view_all_reviews'); - }, + onTap: () => nav.goToHomeDaftarUlasan(), child: Text( 'Riwayat Ulasan', style: FontTheme.poppins14w500black().copyWith( diff --git a/lib/main_page.dart b/lib/main_page.dart index 60aabde..149391f 100644 --- a/lib/main_page.dart +++ b/lib/main_page.dart @@ -25,7 +25,10 @@ class _MainPageState extends BaseStateful { void init() { _children = [ HomePage( - onSeeAllCourse: () => setState(() => _selectedIndex = 1), + onSeeAllCourse: () { + setState(() => _selectedIndex = 1); + MixpanelService.track('view_all_courses'); + }, ), const SearchCoursePage(), const LeaderboardPage(),